среда, 8 октября 2014 г.

SharePoint Multi-tenant App: Get Azure Security Group Members Programmatically

We are developing SharePoint provider hosted application for SharePoint Online. This application is going to be multitenant and used by different customers with Office 365 subscriptions. Previously we developed SharePoint solutions on-prem and new app model brings us a lot of challenges and new tasks.

One of them is the following:
The provider hosted app needs to programmatically get members of security group from Office365 tenant. Business case is pretty simple we need to know all members of SharePoint group to make a report per each user. and if our SP group contains Security group we need to resolve members somehow...

Solutions Overview
Before start digging into code, let's find out what Security Group is? Where is it resided, managed?
Does it depends on Office 365 plan?

Security Group is Azure Active Directory security group. When you create some O365 subscription, MS creates Azure AD instance where your users/groups are stored. So you can manage the users and groups from this AD by different ways:

1) Office365 admin portal https://portal.microsoft.com
2) Azure portal https://manage.windowsazure.com, but you need to create additional subscription to windows Azure and I suppose not all customers, especially midsize, have this subscription.
3) Microsoft Azure Active Directory Module for Windows PowerShell commands http://msdn.microsoft.com/library/azure/jj151815.aspx
All these ways make changes to the same Azure AD instance. and if you change something via Office365 portal, you will see the changes in Azure portal immediately.
Good article gives overview what's going on behind the scene http://technet.microsoft.com/en-us/library/hh967611.aspx

So base questions are the following:
1) How to authenticate and authorize to Azure AD from the SharePoint App
2) How to get group identification to pass it to Graph API
3) Customer's installation use cases: What target customers, tenant's owners should do to make their Azure AD available for the SharePoint App.


How to authenticate and authorize

In my mind it's key question in this task... how to provide secure way of communication between the multi-tenant web app and customer's Azure AD.

Basic solutions is the following:
We need to make customer to create some service principal in customer's Azure AD via New-MsolServicePrincipal with specific client id and client secret(symmetric or asymmetric key). It allows us acquire auth token from our application(in my case from the app azure web site). Gives that service principal permissions to read directory data and then just used OAuth2.0 mechanisms and be happy.
But the problem in the client secret. it should be unique per each customer or single client secret but no customers should know it and should not be able to get it!
Else one customer may have access to the Azure AD instance of another customer, using known client id and client secret :)
Client Secret should be available for the software provider only, it shouldn't be shared to anyone!

After some investigation we found: After our SharePoint App is installed and trusted, it's available as service principal. And you can add service principal to the Directory Readers role.

 $appPrincipal = Get-MsolServicePrincipal -AppPrincipalId

Add-MsolRoleMember -RoleMemberType ServicePrincipal -RoleName "Directory Readers" -RoleMemberObjectId $appPrincipal .ObjectId

So this script make Azure AD to trust the SharePoint App to read Directory Data under app credentials(ClinetID and Client Secret), and it's will work despite of having Azure subscription or not, it requires O365 subscription only.

So after this step is done we are authorized and can get access token via ADAL library. I use Azure code sample from github to get access token


            string tenantName = "tenantdomain";
            string authString = "https://login.windows.net/" + tenantName;
   
            AuthenticationContext authenticationContext = new AuthenticationContext(authString,false);

            // Config for OAuth client credentials
            string clientId = "App Client ID";
            string clientSecret = "App Client Secret";
            ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
            string resource = "https://graph.windows.net";
            string token;
            try
            {
                AuthenticationResult authenticationResult = authenticationContext.AcquireToken(resource, clientCred);
                token = authenticationResult.AccessToken;
            }              
            catch (AuthenticationException ex)
            {            
                Console.WriteLine("Acquiring a token failed with the following error: {0}", ex.Message);
            }

How to get group identification to pass it to Graph API


So after we acquired access token we can make REST Graph API request to get group object and it's members:

            var group = graphConnection.Get("");
            IList membersOfGroup = graphConnection.GetAllDirectLinks(group, LinkProperty.Members);
            foreach (var graphObject in membersOfGroup)
            {
                Console.WriteLine(graphObject.ODataTypeName);
            }
the bit of code taken from the following example Azure code sample

The another question how to get from SharePoint group login real Azure Object id of the group.
For now I don't know answer to this question. but will investigate this.

Now we can pick group by the group name:

            FilterGenerator groupFilter = new FilterGenerator();
            groupFilter.QueryFilter = ExpressionHelper.CreateEqualsExpression(typeof(Group), GraphProperty.DisplayName, "TEST Graph API");
            var queryResult= graphConnection.List(null, groupFilter);


Customer's installation use cases


Why it's important? Basically, because if customer buy O365 subscription he should do no maintenance stuff. he gets already configured application and he has to create content only.
And if third party software provider tells him "Hey you need to run some PS to get it working" the reaction could be like "Are you kidding me? I need to run PS script? What is it?":)  
So we need to keep our solutions as simple as possible...
But in this case I haven't found any easy way to do it. So in this case we have to run PowerShell script to get it.

That's all,
Thanks for reading.



Комментариев нет:

Отправить комментарий