Wednesday, October 17, 2018

UnauthorizedAccessException when try to delete alert from Sharepoint site

If you try to delete alerts from the Sharepoint site programmatically:

SPWeb web = ...;
web.Alerts.Delete(alertId);

You may face with UnauthorizedAccessException:

<nativehr>0x80070005</nativehr><nativestack></nativestack>
    at Microsoft.SharePoint.SPGlobal.HandleUnauthorizedAccessException(UnauthorizedAccessException ex)
    at Microsoft.SharePoint.Library.SPRequest.DeleteSubscription(String bstrUrl, String bstrListName, String bstrSubId, Boolean bListItem, UInt32 ulItemId, Boolean bSiteAdmin, Int32 lUserId)
    at Microsoft.SharePoint.SPAlertCollection.Delete(Guid idAlert)

First thing to check is of course that account under which the code above is executed has all necessary permissions on the site. If this is the case but the problem is still there check that your site collection is not in readonly mode. You may do it using the following PowerShell command:

Get-SPSite -Id http://example.com | select ReadOnly,Readlocked,WriteLocked,LockIssue | ft -autosize

If site is in readonly mode result will look like this:

image

and if site is not readonly it will look like this:

image

You may unlock site collection using the following PowerShell command:

Set-SPSite -Id http://example.com -LockState Unlock

And set it to readonly mode like this:

Set-SPSite -Id http://example.com -LockState ReadOnly

Hope that this information will be helpful.

Friday, October 12, 2018

Using of relative and absolute urls when create Modern Sharepoint site via PnP PowerShell

Modern Team and Communication sites can be created through PnP PowerShell cmdlet New-PnPSite. Here are examples how you may create these sites:

Team: New-PnPSite -Type TeamSite -Title Test -Alias test
Communication: New-PnPSite -Type CommunicationSite -Title Test -Url https://{tenant}.sharepoint.com/sites/test

Note that for Team site we use Alias parameter while for Communication site we use Url. It is mandatory to use relative url when you create Team site and absolute url when you create Communication site. If you will try to create Team site with absolute url:

Team: New-PnPSite -Type TeamSite -Title Test -Alias https://{tenant}.sharepoint.com/sites/test

you will get the following error:

Invalid value specified for property 'mailNickname' of resource 'Group'.

And if you will try to create communication site with relative url:

Communication: New-PnPSite -Type CommunicationSite -Title Test -Url test

there will be another error:

This operation is not supported for a relative URI.

Hope that it will help someone.

Wednesday, October 10, 2018

Access denied when try to delete folder in Modern Sharepoint site’s doclib

Recently I faced with the following problem: when you create OTB Modern Team or Communication site (see How to create modern Team or Communication site in Sharepoint) you can create sub folders in document libraries, e.g. in Style library:

image

(BTW with modern experience currently it is possible to create sub folders of only first level. If you want to create sub folders of deeper levels you have to switch to classic experience and create sub folder from there)

However if you will try to delete this folder you will get the following error:

Sorry, something went wrong
The server has encountered the following error(s):
Test
Access denied. You do not have permission to perform this action or access this resource.

image

In order to avoid this error you need to enable customizations of pages and scripts on the site. You may do it with the following PowerShell command:

Connect-SPOService -Url https://{tenant}-admin.sharepoint.com
Set-SPOSite {url} -DenyAddAndCustomizePages 0

After that you will be able to delete sub folders in Style library doclib on the modern site.

Thursday, September 27, 2018

Problem with getting user token for MS Graph using Azure AD app of Web app type registered in v1 portal.azure.com

As you probably know currently it is possible to register Azure AD apps in 2 places:

  1. https://portal.azure.com – v1 portal
  2. https://apps.dev.microsoft.com – v2 portal

There is number of differences between apps registered in these 2 portals – you may check them e.g. here: About v2.0. For this article let’s notice that apps registered in v2 may support both web app and native platforms while apps in v1 may be either web app or native but not both. If you need them both you have to register 2 apps in v1 portal.

Recently we faced with a problem of getting user token for MS Graph i.e. token based on user credentials. We used the following code for that and it works properly for the app registered in v2 portal with native platform support:

var credentials = new Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential("username", "password");       
var token = Task.Run(async () =>
{
    var authContext = new AuthenticationContext(string.Format("https://login.microsoftonline.com/{0}", "mytenant.onmicrosoft.com"));
    var authResult = await authContext.AcquireTokenAsync("https://graph.microsoft.com", appId, credentials);
    return authResult.AccessToken;
}).GetAwaiter().GetResult();

where for appId we used Azure AD app id registered in v2. When we tried to run the same code for the app registered in v1 portal with web app type the following error was shown:

Error: index was outside the bounds of the array

The same code also works properly for the app from v1 portal but with native type. I.e. it looks like AuthenticationContext.AcquireTokenAsync() method may fetch user token only for native app. If you know how to get user token for web app from v1 portal please share it in comments.

Monday, September 24, 2018

Access denied when try to get Azure AD group’s unseencount through Graph API

Some time ago I faced with interesting problem: when tried to get properties of Azure AD group using app token with app permissions (without available user context) through Graph API:

https://graph.microsoft.com/v1.0/groups/{groupId}?$select=visibility,unseencount

the following error was shown:

{
     "error": {
         "code": "ErrorAccessDenied",
         "message": "Access is denied. Check credentials and try again.",
         "innerError": {
             "request-id": "…",
             "date": "…"
         }
     }
}

Here is example from Postman:

01

Investigation showed that problem was caused by unseencount property. When I tried to remove it – another selected property (visibility) was returned successfully:

https://graph.microsoft.com/v1.0/groups/{groupId}?$select=visibility

{
     "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups(visibility)/$entity",
     "visibility": "Public"
}

02

What was even more stranger is that in Graph explorer it worked:

03

Communication with MS support both on forums (see Can't get group's unseenCount) and via Azure support ticket helped to figure out the reason of this problem: in Postman I used app token with app permissions, while in Graph explorer I was authenticated with my own account (see above). I.e. in Graph explorer delegated permissions were used. And there is known issue in MS Graph (see Known issues with Microsoft Graph): unseencount may be retrieved only using delegated permissions:

“Examples of group features that support only delegated permissions:

  • Group conversations, events, photo
  • External senders, accepted or rejected senders, group subscription
  • User favorites and unseen count
  • Microsoft Teams channels and chats”

Hope that this information will help someone.

Tuesday, September 11, 2018

Several problems when follow/unfollow Sharepoint site via REST API

If you need to implement follow/unfollow site functionality via javascript you most probably will find the following MS article Follow documents, sites, and tags by using the REST service in SharePoint which contains the following example which uses REST API:

$.ajax( {
	url: followingManagerEndpoint + "/isfollowed",
	type: "POST",
	data: JSON.stringify( { 
		"actor": {
			"__metadata": {
				"type":"SP.Social.SocialActorInfo"
			},
			"ActorType":2,
			"ContentUri":siteUrl,
			"Id":null
		} 
	}),
	headers: { 
		"accept":"application/json;odata=verbose",
		"content-type":"application/json;odata=verbose",
		"X-RequestDigest":$("#__REQUESTDIGEST").val()
	},
	success: function (responseData) { 
		...
	},
	error: requestFailed
});

However currently if you will try to run it you will get the following 400 Bad request error:

{"error":{"code":"-1, Microsoft.OData.Core.ODataException","message":"The property '__metadata' does not exist on type 'SP.Social.SocialActorInfo'. Make sure to only use property names that are defined by the type."}}

The problem is with __metadata property of the actor which seems to be outdated nowadays. In order to avoid this error just remove or comment __metadata from actor object.

Another problem is related with odata=verbose specified in Accept header. If you will try to follow site with it API will return the following 406 Not acceptable error:

{"error":{"code":"-1, Microsoft.SharePoint.Client.ClientServiceException","message":"The HTTP header ACCEPT is missing or its value is invalid."}}

In order to resolve it change odata=verbose to odata.metadata=minimal in Accept header. Here is the working code written with Typescript and SPFx:

context.spHttpClient.post(context.pageContext.web.absoluteUrl + "/_api/social.following/follow",
  SPHttpClient.configurations.v1, {
	headers: {
	  'Accept': 'application/json;odata.metadata=minimal',
	  'Content-type': 'application/json;odata=verbose',
	},
	body: JSON.stringify({
	  "actor": {
		  /*"__metadata": {
			  "type": "SP.Social.SocialActorInfo"
		  },*/
		  "ActorType": 2,
		  "ContentUri": site.url,
		  "Id": null
	  }
	})
  });

Hope that it will help someone.

Friday, September 7, 2018

How to delete navigation nodes with AuthoringLink types programmatically in Sharepoint

If you use structural navigation on your publishing Sharepoint site and navigation nodes are created as headings (NodeType = Heading), then it is quite straightforward to delete such navigation nodes programmatically. Here is how it can be done:

var web = ...
var pweb = PublishingWeb.GetPublishingWeb(web);
var globalNavigation = pweb.Navigation.GlobalNavigationNodes;
var nodePage = globalNavigation.Cast<SPNavigationNode>().FirstOrDefault(n => (n.Title == "Test"));
if (nodePage != null)
{
	nodePage.Delete();
}

In this example we delete navigation node with title “Test”. However if you will try to delete navigation nodes which were created as AuthoredLink* (see NodeTypes Enum):

  • AuthoredLink
  • AuthoredLinkPlain
  • AuthoredLinkToPage
  • AuthoredLinkToWeb

using the same code you will find that link is not get deleted. The workaround is to change NodeType property first to Heading and then delete the node:

var web = ...
var pweb = PublishingWeb.GetPublishingWeb(web);
var globalNavigation = pweb.Navigation.GlobalNavigationNodes;
var nodePage = globalNavigation.Cast<SPNavigationNode>().FirstOrDefault(n => (n.Title == "Test" &&
	(n.Properties != null && n.Properties["NodeType"] != null && n.Properties["NodeType"] is string &&
		(n.Properties["NodeType"] as string == "AuthoredLink" || (n.Properties["NodeType"] as string).StartsWith("AuthoredLink"))));

if (nodePage != null)
{
	nodePage.Properties["NodeType"] = "Heading";
	nodePage.Update();

	// reinitialize navigation nodes
	pweb = PublishingWeb.GetPublishingWeb(web);
	globalNavigation = pweb.Navigation.GlobalNavigationNodes;
	nodePage = globalNavigation.Cast<SPNavigationNode>().FirstOrDefault(n => (n.Title == "Test);
	if (nodePage != null)
	{
		nodePage.Delete();
	}
}

After that AuthoredLink navigation node will be successfully deleted.