Thursday, December 27, 2018

Call Azure AD secured Azure functions from C#

In this post I will show how to call Azure functions which are secured with AAD from C#. Let’s assume that you already created Function app and secured it with AAD. In this example I will use Advanced AAD authentication configuration of the Function app which may be used e.g. when Function app is created in one tenant but it’s Azure functions supposed to be used in another tenant (and thus should be secured against this other tenant):

image

Briefly in order to secure Azure function with AAD we need to register new app in the directory against which we want to authenticate users which will use Azure function (Azure Active Directory > App registrations > New app registration); in this example let’s call it test-secured-functions for reference. Then in authentication settings of the Function app: in Client ID specify app id of test-secured-functions, Client Secret – client secret of test-secured-functions (can be generated from app’s Settings > Keys) and in Issuer Url – url of the form https://sts.windows.net/{tenantId} where tenantId is id of the tenant where test-secured-functions is registered (can be found in Azure Active Directory > Properties > Directory ID).

After that copy base url of any Azure function (e.g. https://{azurefuncname}.azurewebsites.net – those which comes before /api/… part) in the Function app and add it to Allowed token audiences. Otherwise you will get HTTP 401 Unauthorized when will try to get access token for calling Azure function from C#.

Now the tricky part; go to test-secured-functions > Settings > Properties and change “Home page URL” and “App ID URI” properties to the base url of Azure function – same which was added to Allowed token audiences on previous step (https://{azurefuncname}.azurewebsites.net). It is more important to set “Home page URL”  - it will be used as app id when we will get access token:

image

Now we may call our AAD secured Azure function from C#:

string aadInstance = "https://login.windows.net/{0}";
string tenant = "{tenant}.onmicrosoft.com";
string serviceResourceId = "https://{azurefuncname}.azurewebsites.net";
string clientId = "{clientId}";
string appKey = "{clientSecret}";

var authContext = new AuthenticationContext(string.Format(CultureInfo.InvariantCulture, aadInstance, tenant));
var clientCredential = new ClientCredential(clientId, appKey);
AuthenticationResult result = authContext.AcquireTokenAsync(serviceResourceId, clientCredential).Result;
Console.WriteLine(result.AccessToken);

Here you need to replace {tenant} with your tenant name, {azurefuncname} with name of your Azure function, {clientId} and {clientSecret} are from test-secured-functions app – same which were used in Fucntion app > Active Directory Authentication above. Notice that serviceResourceId variable contains base url of our Azure functions. I.e. we ask access token for scope = url of our Azure function. If we won’t set this property we will get the following error when try to call above code:

AADSTS50001: The application named https://{azurefuncname}.azurewebsites.net was not found in the tenant named {tenant}.onmicrosoft.com.  This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant.  You might have sent your authentication request to the wrong tenant

If we will decode token (e.g. on https://jwt.io/) we will see that it’s aud property is set exactly to requested serviceResourceId, i.e. base url of Azure function. This is why we set this base url to the app’s “Home page url” above – API searched for the app by this url during the call of AuthenticationContext.AcquireTokenAsync() method:

image

Once token is obtained we may call Azure function using regular way by providing Authorization HTTP header with Bearer token:

var request = WebRequest.Create("https://{azurefuncname}.azurewebsites.net/api/test") as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/json";
request.Headers.Add("Authorization", "Bearer" + " " + accessToken);

string data = ...;
var byteData = Encoding.UTF8.GetBytes(data);
request.ContentLength = byteData.Length;
var stream = request.GetRequestStream();
stream.Write(byteData, 0, byteData.Length);

using (var response = request.GetResponse() as HttpWebResponse)
{
    ...
}

Using this method you will be able to call your AAD secured Azure functions from C# or PowerShell. Hope this information will help someone.

Monday, December 17, 2018

Sharepoint CAML: query against UserMulti field types using Camlex

Sometime we need to query list items against fields which have UserMulti type. In order to do that with Camlex we will use support of LookupMulti field types added few years ago in Camlex (see Camlex 4.2 and Camlex.Client 2.2 are released) and knowledge about behavior of Eq operator when it is used with multi lookup fields: Multi lookup fields in CAML queries. Eq vs Contains. Briefly when Eq operator is used with LookupMulti field types it has different semantic: instead of matching list items which actually equal to specified value it will match items which include this value. Consider the following example:

User user = web.EnsureUser(userName);
var query = new CamlQuery();
query.ViewXml = Camlex.Query().Where(x => x["TestField"] == (DataTypes.LookupMultiId)user.Id.ToString()).ToString(true);
var listItems = list.GetItems(query);
ctx.Load(listItems);
ctx.ExecuteQueryRetry();

It will produce the following Caml query:

<View>
  <Query>
    <Where>
      <Eq>
        <FieldRef Name="TestField" LookupId="True" />
        <Value Type="LookupMulti">4/Value>
      </Eq>
    </Where>
  </Query>
</View>

At first it searches for the user by user name and then uses userId for creating the query. This query will return all items which include user with specific user id (in example above userId = 4) in TestField which has UserMulti type. This field may contain only this user or it may contain several users including those which we are searching for – in both cases list item will be returned. This was one of the reasons of why support of Includes keyword was not added to Camlex – there is no need for that as the same result may be achieved with simple Eq operator.

Friday, December 14, 2018

Camlex 5.0 and Camlex.Client 3.0 for .Net 4.5 are released

Today Camlex 5.0 and Camlex.Client 3.0 were released. In this release target framework version of both libraries has been changed from .Net 3.5 to .Net 4.5 in order to simplify usage of the libraries in the newer Sharepoint versions running on CLR 4.0 (SP2013/SP2016/SP2019) and Sharepoint Online. Also with this change it became possible to use Camlex.Client in Azure functions (V1 which use .Net 4.5) – earlier it was needed to recompile source code with targeted .Net 4.5 which was not convenient. Source code of basic Camlex library is avaialble in Github master branch, and source code of Camlex.Client is in client branch.

Also both Nuget packages have been updated: Camlex.NET.dll and Camlex.Client.dll. With this fix it will be easier to use Camlex with newer Sharepoint versions.

Update: after performing release described above I realized that it will be more convenient for Azure functions to reference Microsoft.SharePoint.Client.dll of v16.1 (Sharepoint Online). For console apps it won’t add complexity since it is quite easy to redirect assembly binding there via app.config. So I released new Camlex.Client 3.1 which is targeted for .Net 4.5 and references Microsoft.SharePoint.Client.dll of v16.1.

Tuesday, December 11, 2018

MS Graph API: how to list all groups where user is an owner using single API call

Sometime we need to get a list of all Office 365 groups where user is owner. It is relatively easy to get list of groups where user is a member using the following endpoints:

https://graph.microsoft.com/v1.0/users/{id}/memberOf

or

https://graph.microsoft.com/beta/users/{id}?$expand=memberOf

or

https://graph.microsoft.com/beta/me/joinedgroups

(First 2 end points work with app permissions while last endpoint works with delegated permissions). Unfortunately the same methods don’t work for owners. If you will try to user “ownerOf” in endpoints the following error will be shown:

BadRequest
Unsupported segment type. ODataQuery: users/{id}/ownerOf

However there is a method which allows to load all groups with owners using single API call:

https://graph.microsoft.com/v1.0/groups?$expand=owners

Note that we’ve added “?$expand=owners” to the query string. With this additional param each group will be returned with list of it’s owners. After that yo may filter groups and include only those where current users is an owner. This is of course not so convenient and fast as above methods for owners but better than nothing.

Monday, December 10, 2018

How to create online html documentation from Word document

Suppose that you have your product’s documentation in the Word format and at some point decide to create online version of this documentation. Built-in convert to html works not very well so what other ways are available? Below you will find several possible ways to convert product documentation from Word to online version. These methods are based on Pandoc project which may convert document between many popular formats.

1. Word to EPUB and then to html

This method is based on the fact that EPUB format is internally based on html. As a bonus it splits the Word document into separate html documents per chapter. So if you have single big Word document with many images it will be divided into several html chapters which is better for online version than single big html page.

At first we need to convert Word to EPUB:

pandoc -f docx -t epub –o output.epub input.docx

After that you will have output.epub ebook. Change extension from epub to zip and unzip the file to the local folder. In EPUB subfolder of this folder you will find the following files structure:

image

Here media folder will contain all exported images from the Word file and nav.xhtml will contain clickable table of contents. And text subfolder will contain html files for particular chapters:

image

2. Word to Markdown and then visualize with MkDocs site generator

With this method we at first generate Word document to Markdown format using the same Pandoc tool:

pandoc -f docx -t markdown –o output.md input.docx --extract-media media

Here we explicitly specified folder where Pandoc should extract images from Word document. When we have got Markdown file we may create static site for it using MkDocs tool. With this tool we at first need to create new project folder and put mardown file with images there:

python -m mkdocs new test

It will also put the following mkdocs.yml file to site’s root folder:

site_name: My Docs

and then run:

python -m mkdocs serve

which will launch local web server which will host your online documentation. Also it is possible to choose different UI themes from the list of themes available on MkDocs site.

Tuesday, December 4, 2018

Get current user’s principal in Azure function both in v1 and v2 runtimes

Sometimes we need to get current user’s principal in Azure function in order to perform does user has permissions to perform requested action (of course when call to Azure function is done with user context). Recently MS announced feature called ClaimsPrincipal binding data for Azure Functions. With this feature it should be possible to inject client principal as function parameter:

public static IActionResult Run(HttpRequest req, ClaimsPrincipal principal, ILogger log)
{
     // ...
     return new OkResult();
}

Note that according to documentation this feature will be only available for Azure functions which use v2 runtime (which also means that they use .Net Core instead of .Net Framework). I tested it and at least currently this feature is not available for my dev tenant.

Fortunately there is a way to read current user’s principal which works both for v1 and v2. It is based on using special HTTP header X-MS-CLIENT-PRINCIPAL-NAME which contains user name (see Access user claims):

image

So we can read current user’s principal name in Azure function like this:

var headerValues = req.Headers.GetValues("X-MS-CLIENT-PRINCIPAL-NAME");
return headerValues.FirstOrDefault();

and after that perform necessary authorization checks.

Update 2018-12-28: above method with using HTTP headers works but it is possible to replace X-MS-CLIENT-PRINCIPAL-NAME header with other user id and perform calls from behalf of this user. Here is how you may get current user principal using object model:

public static string GetUsernameFromClaim()
{
	if (ClaimsPrincipal.Current == null || ClaimsPrincipal.Current.Identity == null ||
		string.IsNullOrEmpty(ClaimsPrincipal.Current.Identity.Name))
	{
		return string.Empty;
	}

	return ClaimsPrincipal.Current.Identity.Name;
}

s