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.

Tuesday, September 4, 2018

How to get modern Team or Communication sites using Search API in Sharepoint

In one of my previous posts I wrote how to create modern Sharepoint sites: How to create modern Team or Communication site in Sharepoint (quite basic post but necessary if you just stated to work with modern sites). In this article we will continue exploring modern sites and will see how to get list of modern Team or Communication sites using Search API. Using of Search API is preferable in many scenarios as you have all sites at once with single API call.

Let’s create modern Team site and explore it’s Site Pages doclib. In default list view let’s add additional column “Content Type” to see what content type is used for the default front page:

image

As you can see it uses “Site Page” content type. Modern Communication site also uses the same content type for front page.

So in order to get list of all modern sites we may query for pages created with “Site Page” content type. In order to make our search query language-independent we will use content type id instead of the name. In order to get it go to Site Pages doclib settings and click Site Page content type. Then copy content type id from query string. You will have something like that:

0x0101009D1CB255DA76424F860D91F20E6C4118…

where the rest will be unique for your doclib. Our query string will look like that then:

ContentTypeId:0x0101009D1CB255DA76424F860D91F20E6C4118*

which means return all pages which content type starts with specified id. If we will test it in the Search Query Tool we will have list of all modern Team and Communication sites in the tenant:

image

In order to get distinct list don’t forget to check “Trim duplicates” option and select at least Title and SPWebUrl managed properties which contain site title and url.

Monday, September 3, 2018

How to explore Sharepoint REST API endpoints

Sometimes you need to get list of available operations in the Sharepoint REST API endpoint. Let’s say we want to check operations available for /_api/SitePages endpoint.

First of all we need to get authentication cookies. In order to get them lunch Fiddler and open e.g. Sharepoint landing page (/_layouts/15/Sharepoint.aspx) which is opened from App launcher > Sharepoint. On this page there will be several REST API calls which will contain Cookies header. E.g. /_api/GroupSiteManager/CanUserCreateGroup:

image

From this Fiddler view copy value of Cookie header.

After that launch Postman and create request on endpoint in question: /_api/SitePages. In Headers section add Cookie, put value copied from Fiddler and click Send:

image

In the response it will return list of relative endpoint operations available under selected endpoint. In this example they are:

  • /_api/SitePages/CommunicationSite
  • /_api/SitePages/Pages
  • /_api/SitePages/PublishingSite

Note that this method doesn’t return POST/PUT endpoints unfortunately.

How to create modern Team or Communication site in Sharepoint

Modern Team/Communication sites are not displayed on the classic create site page in Sharepoint together with other “classic” web templates. In order to create them you need first click App launcher icon in top left corner and choose Sharepoint link there:

image

It will open Sharepoint landing page which will have Create site icon on the top:

image

After clicking on this icon you will be able to choose which modern site to create: Team or Communication:

image

And on the last step you have to specify site name, privacy and classification (later one is shown if classifications are configured for your tenant):

image

After clicking Next your modern Team or Classification site will be created.