Wednesday, August 31, 2016

Get title of the current list in Sharepoint via javascript object model

In basic Sharepoint object model we can get title of current list (list which is currently opened in browser) by calling SPContext.Current.List.Title. However in Sharepoint Online we need to use javascript object model where the same task is done little bit more tricky. Here is the code:

   1: SP.SOD.executeFunc("sp.js", "SP.ClientContext", function () {
   2:     SP.SOD.executeFunc("sp.core.js", "SP.ListOperation.Selection",
   3:         function () {
   4:         var listId = SP.ListOperation.Selection.getSelectedList();
   5:         if (typeof (listId) == "undefined" || listId == null || listId == "") {
   6:             return;
   7:         }
   8:         var ctx = SP.ClientContext.get_current();
   9:         var list = ctx.get_web().get_lists().getById(listId);
  10:         ctx.load(list);
  11:         ctx.executeQueryAsync(
  12:             Function.createDelegate(this, function (sender, args) {
  13:                 console.log(list.get_title());
  14:             }),
  15:             Function.createDelegate(this, function (sender, args) {
  16:                 console.log(args.get_message());
  17:             })
  18:         );
  19:     });
  20: });

I.e. at first we get id of the current list by calling SP.ListOperation.Selection.getSelectedList() (line 4) and then get list instance by this id and load it from context (lines 8-18). In success handler list.get_title() is available.

Disable Sync link and ribbon button for document libraries in Sharepoint via javascript

Offline sync of document libraries is new feature of Sharepoint 2013 which allows users to sync documents from doclibs on their PC:

There are 2 links: in QCB (quick control block) and in ribbon. In some cases organizations want to disable this functionality. From UI it can be done by going to Site settings > Search and Offilne availability and setting Offline Client availability > Allow items from this site to be downloaded to offilne clients to No:

If we want to do it programmatically we need to set SPWeb.ExcludeFromOfflineClient property to true via basic Sharepoint object model. However if we will need to do that for Sharepoint Online, we will face with the issue, because currently client-side object model doesn’t support it (see web.ExcludeFromOfflineClient property with CMOS discussion). So at least for now we have to do that via javascript.

The tricky part here is that ribbon is not loaded automatically when page is loaded, i.e. appropriate elements won’t be available in DOM. But we may use workaround: create function which will call itself after some time interval (e.g. each 2 seconds) until it will find Sync button which means that user opens the ribbon. Here is the code:

   1: disableSynButtonInDocLibs: function () {
   2:     // qcb (above the list view)
   3:     var qcbButton = $("button[class*='js-listview-qcbSyncButton']");
   4:     if (!qcbButton.hasClass("ms-disabled")) {
   5:         qcbButton.attr("disabled", true).addClass("ms-disabled");
   6:         qcbButton.children().each(function () {
   7:             $(this).addClass("ms-disabled");
   8:         });
   9:     }
  10:  
  11:     // ribbon Sync button
  12:     var syncLink = $("a[id='Ribbon.Library.Actions.Sync-Large']");
  13:     if (syncLink.length == 0) {
  14:         // ribbon is not loaded yet
  15:         setTimeout(disableSynButtonInDocLibs, 2000);
  16:     }
  17:  
  18:     syncLink.each(function () {
  19:         var this_ = $(this);
  20:         if (this_.hasClass("ms-cui-disabled")) {
  21:             return;
  22:         }
  23:         this_.attr("unselectable", "on");
  24:         this_.attr("aria-disabled", "true");
  25:         this_.addClass("ms-cui-disabled", "true");
  26:         this_.click(function (e) {
  27:             e.stopPropagation();
  28:         });
  29:         this_.children().each(function () {
  30:             $(this).click(function (e) {
  31:                 e.stopPropagation();
  32:             });
  33:         });
  34:     });
  35: }

On lines 2-9 we disable QCB Sync link, and on lines 11-34 – Sync ribbon button. On lines 12-16 we check is the button already loaded and if not call itself after 2 seconds. Note how we disable onclick event for the ribbon button: lines 26-33. In order to do that we attach new event handler to the link, which represents ribbon button, and its child elements and stop event propagation to prevent default event handler to be triggered.

Saturday, August 27, 2016

One issue with custom login page with claims-based authentication in Sharepoint

One of our sites was migrated from Sharepoint 2007 to Sharepoint 2013. In 2007 it used FBA with custom login page. After migration user noticed strange behavior: sometime Sharepoint returned empty page or HTTP 500 Internal server error. During checking Sharepoint logs the following error was found there:

Exception of type 'System.ArgumentException' was thrown.  Parameter name: encodedValue 
at Microsoft.SharePoint.Administration.Claims.SPClaimEncodingManager.DecodeClaimFromFormsSuffix(String encodedValue)   
at Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager.GetProviderUserKey(IClaimsIdentity claimsIdentity, String encodedIdentityClaimSuffix)   
at Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager.GetProviderUserKey(String encodedIdentityClaimSuffix)   
at Microsoft.SharePoint.Utilities.SPUtility.GetFullUserKeyFromLoginName(String loginName)   
at Microsoft.SharePoint.SPGlobal.CreateSPRequestAndSetIdentity(SPSite site, String name, Boolean bNotGlobalAdminCode, String strUrl, Boolean bNotAddToContext, Byte[] UserToken, SPAppPrincipalToken appPrincipalToken, String userName, Boolean bIgnoreTokenTimeout, Boolean bAsAnonymous)   
at Microsoft.SharePoint.SPWeb.InitializeSPRequest()   
at Microsoft.SharePoint.SPWeb.EnsureSPRequest()   
at Microsoft.SharePoint.WebControls.SPControl.EnsureSPWebRequest(SPWeb web)   
at Microsoft.SharePoint.WebControls.SPControl.SPWebEnsureSPControl(HttpContext context)   
at Microsoft.SharePoint.ApplicationRuntime.BaseApplication.Application_PreRequestHandlerExecute(Object sender, EventArgs e)   
at Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.PreRequestExecuteAppHandler(Object oSender, EventArgs ea)   
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()   
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

When web application was migrated to 2013 it started to use claims-based authentication. Custom login page was re-implemented using the following authentication logic (there are many examples of custom login page for FBA when claims-based authentication is used. Most of them use more or less the same logic):

   1: var formsAuthOption = SPFormsAuthenticationOption.None;
   2: var tokenType = SPSessionTokenWriteType.WriteSessionCookie;
   3: if (rememberMe)
   4: {
   5:     formsAuthOption = SPFormsAuthenticationOption.PersistentSignInRequest;
   6:     tokenType = SPSessionTokenWriteType.WritePersistentCookie;
   7: }
   8:  
   9: var settings = site.WebApplication.IisSettings[SPContext.Current.Site.Zone];
  10: var authProvider = settings.FormsClaimsAuthenticationProvider;
  11: var securityToken =
  12:     SPSecurityContext.SecurityTokenForFormsAuthentication(
  13:     context,
  14:     authProvider.MembershipProvider,
  15:     authProvider.RoleProvider,
  16:     username,
  17:     pwd,
  18:     formsAuthOption);
  19:  
  20: var fam = SPFederationAuthenticationModule.Current;
  21: fam.SetPrincipalAndWriteSessionToken(securityToken, tokenType);
  22:  
  23: FormsAuthentication.SetAuthCookie(username, false);

On lines 9-21 we authenticate user via FBA claims authentication provider (SPIisSettings.FormsClaimsAuthenticationProvider). If provided user credentials are incorrect, call to SPSecurityContext.SecurityTokenForFormsAuthentication() method will throw exception. If credentials are correct it will set FedAuth authentication cookies.

On line 23 there is call to FormsAuthentication.SetAuthCookie() method which sets previously used .ASPXAUTH cookies used by ASP.Net FBA authentication. Developer who implemented login page told that it was left there for backward compatibility, but was not able to remember which exact components required this cookies.

And as it turned out presence of both FedAuth and .ASPXAUTH cookies caused mentioned exception in SPClaimEncodingManager.DecodeClaimFromFormsSuffix(). When call to FormsAuthentication.SetAuthCookie() was removed, error disappeared. Hope that information will help someone.