Karine Bosch’s Blog

On SharePoint

Microsoft Flow – Get to the documents from a Teams library in a dynamic way

Last week I had to build a Flow that copies signed documents from a Microsoft Team library to a document library of a SharePoint team site. The customer wants to keep all signed contracts into one place before archiving the Microsoft Team.

The input of the Flow is the name of the Team that needs to be inspected for signed documents.

The Get files action requires following input:

  • The URL to the SharePoint site where the files are stored
  • The guid of the library where the files are stored

HTTP request to get the Teams

To retrieve the Teams from Flow, we can use the Microsoft Graph API. Teams can be retrieved by using the following endpoint:

https://graph.microsoft.com/beta/groups

To retrieve one single Team, we use a filter:

$filter=displayName eq'variables(TeamsName)'

Before the HTTP request will execute successfully, we need to foresee authentication. I registered an app in Azure Active Directory and granted application rights to at least “Read all groups”.

There are a lot of tutorials online that explain how to register an app with Azure AD and how to grant permissions to the several APIs.

I stored the following data needed by the HTTP request in variables:

  • The tenant ID of your O365 tenant
  • The Client ID (of the app registered in Azure AD)
  • The Client Secret (which is the key that has been granted to your app during registration)

The action I used, is an HTTP request:

When this action executes successfully, I can parse the resulting JSON that contains the Id of the Team:

And store the Id of the Team into a variable.

HTTP request to get to the URL of the SharePoint site

The Get Files action requires two parameters: the Site Address and the ID of the document library.

Executing the following call to the Graph API will return the URL to the SharePoint site behind the Teams:

https://graph.microsoft.com/beta/groups/variables(TeamsId)/sites/root/WebUrl

We execute this call in a second HTTP request action:

We parse the JSON to isolate the URL of the SharePoint site behind the Team.

HTTP request to get the ID of the document library of the Team

Now we need to get to the document library behind the Team. Therefore we first need to retrieve the ID of the SharePoint site. (We cannot get to the document library based on the URL of the SharePoint site, wich we retrieved in the previous step).

Executing the following call to the Graph API will return the URL to the SharePoint site behind the Teams:

https://graph.microsoft.com/beta/groups/variables(TeamsId)/sites/root/Id

We execute this call in a HTTP request action:

Now we have the id of the SharePoint site behind the Teams. This ID looks something like this:

<tenant>.sharepoint.com,776e26ce-c949-4d33-a6e2-90dbe4c45f04,10a23c2e-49ee-4e3d-80f9-f534b978dbfe

(Not a single GUID we are used to).

Again we need to parse the resulting JSON in order to isolate the id of the SharePoint site.

With this ID, we can execute another call to the Graph API to get to the document library:

https://graph.microsoft.com/v1.0/sites/output('<site id>') /lists?$filter=displayName eq 'Documents'

The HTTP action looks like this:

Where value is the outcome of the Parse JSON action right before.

The resulting JSON contains the Id and the URL of the document library. We parse the resulting JSON again in order to isolate the ID of the document library.

Now we have all the variables to configure the Get Files action to retrieve the documents in the document library of the Teams.

Executing the Get Files action will also return the folders in the document library. How to filter out the files, and how to filter out the name of the channel to which the document belongs, will be subject to a next post.

November 18, 2018 Posted by | Flow, Graph, Teams | Leave a comment

Working with Microsoft Graph to access o365 Planner

I’m currently working on a project where I have to migrate classic SharePoint tasks lists to Planner in O365. It cannot be done using CSOM, but there is a Planner REST API you can use.

This post does not contain new stuff, but I had to read trough hundreds of posts to get it all together. Therefore, I decided to write a post for whom it may be helpful.

I wrote a simple MVC-based Azure web site to do the work, nothing fancy, as it is just to migrate existing tasks lists. But you cannot communicate with your O365 tenant through Graph using SharePointOnlineCredentials. Therefore, I first registered an Azure App to have a ClientID/ClientSecret combination:

The Redirect URL is the URL to where I deployed my Azure web site (which is not described in this blog post)

Planner is part of O365 groups, so you have to give this Azure App at least read permissions to the O365 groups in your tenant. Via the Required permissions setting, I can give access to my application to as many APIs as available.

I selected the Graph API, and gave my application the permission to read from site collections and to read/write O365 groups:

I also had to click the Grant Permissions button. As an you then consent to an application’s delegated permissions on behalf of all the users in your tenant.

Read more Azure App registrations.

This gives me the permission to request data about all O365 groups in my tenant.

Now it is coding time 🙂

First I need to  get an access token based on the client ID and client secret of the Azure App I just registered:

private async Task<string> GetAccessToken(string resourceId)
{
    try
    {
        var authority = ConfigurationManager.AppSettings["ida:AuthorizationUri"] + ConfigurationManager.AppSettings["ida:TenantId"];
        var clientCredential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"],
        ConfigurationManager.AppSettings["ida:ClientSecret"]);

        AuthenticationContext ac = new AuthenticationContext(authority);
        AuthenticationResult result = await ac.AcquireTokenAsync(resourceId, clientCredential);

        return result.AccessToken;
     }
     catch (Exception ex)
     {
         // TODO: log the exception
         return null;
     }
 }
  • The AuthorizationUri is https://login.windows.net/
  • The TenantId is the ID of your O365 tenant. If you don’t know the ID, you can find it in your Azure portal: navigate to Azure Active Directory > Properties, and in the Map ID, you’ll find your tenant ID
  • The ResourceId is the Graph URI: https://graph.microsoft.com

Tip: You can verify if you have a valid access token by going to https://jwt.io/

Next step is to retrieve the O365 group:

 string restUrl = string.Format("https://graph.microsoft.com/v1.0/kboske.com/groups?$filter=displayname eq '{0}'", groupName);
 Task<string> getGroupTask = Task<string>.Run(() => GetResponse(restUrl, accessToken));
 getGroupTask.Wait();

If you get a successful response, you will get something like this:

My GetResponse method is very basic, but I add it in here for completeness:

private async Task<string> GetResponse(string restUrl, string accessToken)
{
    string jsonresult = null;

    try
    {
       using (HttpClient client = new HttpClient())
       {
          var accept = "application/json";
          client.DefaultRequestHeaders.Add("Accept", accept);
          client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
          using (var response = await client.GetAsync(restUrl))
          {
              if (response.IsSuccessStatusCode)
              {
                  jsonresult = await response.Content.ReadAsStringAsync();
              }
              else
              {
                   throw new Exception("Error getting data: " + response.StatusCode.ToString());
              }
          }
       }
    }
    catch (Exception ex)
    {
       // TODO: handle the exception
    }
    return jsonresult;
}

Once I get the group Id, I can get to the plan, using the following REST call, no?

restUrl = string.Format("https://graph.microsoft.com/v1.0/recomatics.com/groups/{0}/planner/plans", groupId);

No, you can not!! You get a

But when I try to access Planner, I get a 401 – Unauthorized error instead.

It seems you can only access the plans by using user credentials because you can only access the plans to which you have permissions.

I changed the GetAccessToken method, in order to authenticate with my user credentials:

private async Task<string> GetAccessToken(string resourceId, string userName, string password)
{
    try
    {
        var authority = ConfigurationManager.AppSettings["ida:AuthorizationLoginUri"] + ConfigurationManager.AppSettings["ida:TenantId"];
        var authContext = new AuthenticationContext(authority);
        var credentials = new UserPasswordCredential(userName, password);
        var authResult = await authContext.AcquireTokenAsync(resourceId, ConfigurationManager.AppSettings["ida:ClientIdNativeClient"], credentials);

        // Get the result
        return authResult.AccessToken;
    }
    catch (Exception ex)
    {
       // TODO: handle the exception
       return;
    }
 }

But then I get a different error:

"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'."

That’s because my Azure App only accepts client id / client secret combination because I registered it as a Web service. To be able to authenticate using my user credentials, I have to register an Azure App as a Native client:

When I now call the REST endpoint to get to the plan of my O365 group, I get a successful response:

Tip: try out your REST calls in the Graph explorer

Helpful posts:

December 18, 2017 Posted by | CSOM, Graph | 1 Comment