Monday, May 22, 2017

Set owners and members for existing Azure AD groups via MS Graph client library

In one of my previous posts I showed how to create new Azure AD group and add owners via MS Graph client library: Create Azure AD group and set group owner using Microsoft Graph Client library. In this post I will also show how to set owners for existing groups, which will also include deleting of previous owners. The same approach may be used for adding and deleting group members (instead of Owners use Members property of Group class). In order to synchronize Azure AD group owners use the following method:

   1: public static void SetAzureGroupOwners(Group group, List<User> newOwners)
   2: {
   3:     if (group == null || newOwners == null || newOwners.Count == 0)
   4:     {
   5:         return;
   6:     }
   7:  
   8:     var graph = new GraphServiceClient(new AzureAuthenticationProvider());
   9:  
  10:     var existingOwners = GetAzureGroupOwners(group.Id);
  11:     foreach (User newOwner in newOwners)
  12:     {
  13:         if (!existingOwners.Any(u => string.Compare(u.UserPrincipalName,
  14:             newOwner.UserPrincipalName, true) == 0))
  15:         {
  16:             var result = graph.Groups[group.Id].Owners.
  17:                 References.Request().AddAsync(newOwner);
  18:             result.Wait();
  19:             while (result.Status == TaskStatus.WaitingForActivation)
  20:             {
  21:                 Thread.Sleep(1000);
  22:             }
  23:         }
  24:     }
  25:  
  26:     existingOwners = GetAzureGroupOwners(group.Id);
  27:     foreach (User existingOwner in existingOwners)
  28:     {
  29:         if (!newOwners.Any(u => string.Compare(u.UserPrincipalName,
  30:             existingOwner.UserPrincipalName, true) == 0))
  31:         {
  32:             var result = graph.Groups[group.Id].Owners[existingOwner.Id].
  33:                 Reference.Request().DeleteAsync();
  34:             result.Wait();
  35:             while (result.Status == TaskStatus.WaitingForActivation)
  36:             {
  37:                 Thread.Sleep(1000);
  38:             }
  39:         }
  40:     }
  41: }

In this method we use helper function GetAzureGroupOwners:

   1: public static List<User> GetAzureGroupOwners(string groupId)
   2: {
   3:     try
   4:     {
   5:         var graph = new GraphServiceClient(new AzureAuthenticationProvider());
   6:         var result = new List<User>();
   7:         var owners = graph.Groups[groupId].Owners.Request().GetAsync();
   8:         while (owners.Result.Count > 0)
   9:         {
  10:             foreach (var owner in owners.Result)
  11:             {
  12:                 if (!(owner is User))
  13:                 {
  14:                     continue;
  15:                 }
  16:  
  17:                 result.Add((User)owner);
  18:             }
  19:  
  20:             if (owners.Result.NextPageRequest != null)
  21:             {
  22:                 owners = owners.Result.NextPageRequest.GetAsync();
  23:             }
  24:             else
  25:             {
  26:                 break;
  27:             }
  28:         }
  29:         return result;
  30:     }
  31:     catch (Exception x)
  32:     {
  33:         Console.WriteLine("Error occured when getting Azure " +
  34:             "group's owners: '{0}'\n{1}", x.Message, x.StackTrace);
  35:         return new List<User>();
  36:     }
  37: }

and AzureAuthenticationProvider from previous post Work with Azure AD via Microsoft Graph API.

Method SetAzureGroupOwners() receives reference to the Azure AD group and list of users which should be set as owners. Please note that we are talking here about replacing existing owners on new ones, i.e. we need not only to add new owners but also delete those existing owners which don’t exist in this list. So at first we add new owners (lines 10-24) and then remove missing existing owners (lines 26-40). At the end group will only have those owners which are passed in the list to the function.

Tuesday, May 9, 2017

Update Azure AD group via MS Graph client library

In my previous posts I showed several scenarios of using MS Graph client library, see:

Work with Azure AD via Microsoft Graph API
Create Azure AD group and set group owner using Microsoft Graph Client library
Retrieve paginated data from Azure AD via Microsoft Graph Client library

In this article we will continue to get familiar with the MS Graph client library and see how to update Azure AD group programmatically. Examples in this post will use the same AzureAuthenticationProvider class for authenticating against Azure AD as in examples provided above so I won’t duplicate it here.

Here is how we can rename Azure AD group programmatically using MS Graph client library:

   1: renameGroup("oldGroupName", "newGroupName");
   2:  
   3: ...
   4:  
   5: static void renameGroup(string oldName, string newName)
   6: {
   7:     var graph = new GraphServiceClient(new AzureAuthenticationProvider());
   8:     var group = getGroup(oldName);
   9:     group.DisplayName = newName;
  10:  
  11:     var groupReq = new GroupRequest(graph.Groups[group.Id].Request().RequestUrl,
  12:         graph, new List<Option>());
  13:     var result = groupReq.UpdateAsync(group);
  14:  
  15:     do
  16:     {
  17:         Console.WriteLine("Result status: {0}", result.Status);
  18:         Thread.Sleep(5000);
  19:     } while (result.Status == TaskStatus.WaitingForActivation);
  20: }
  21:  
  22: static Group getGroup(string name)
  23: {
  24:     var graph = new GraphServiceClient(new AzureAuthenticationProvider());
  25:     try
  26:     {
  27:         var groups = graph.Groups.Request().GetAsync();
  28:         int requestNumber = 1;
  29:         while (groups.Result.Count > 0)
  30:         {
  31:             foreach (var g in groups.Result)
  32:             {
  33:                 if (string.Compare(g.DisplayName, name, true) == 0)
  34:                 {
  35:                     return g;
  36:                 }
  37:             }
  38:  
  39:             if (groups.Result.NextPageRequest != null)
  40:             {
  41:                 groups = groups.Result.NextPageRequest.GetAsync();
  42:             }
  43:             else
  44:             {
  45:                 break;
  46:             }
  47:         }
  48:         return null;
  49:     }
  50:     catch (ServiceException x)
  51:     {
  52:         Console.WriteLine("Exception occured: {0}", x.Error);
  53:         return null;
  54:     }
  55: }

At first in method retrieveGroup() we get reference on the Group object and update group’s DisplayName property (lines 7-9). Then we create GroupRequest object and call it’s UpdateAsync method (lines 11-13) and wait until request will be processed (lines 15-19). After that group will appear in Azure portal with new name. But note that if group was already used in Sharepoint Online site (e.g. for granting permissions on some site) changes won’t be synced here automatically – you will need to sync user profiles and then update user data in User information list.

Thursday, May 4, 2017

Fix problem with Access denied exception in provider-hosted apps for Sharepoint

In one of our projects we used provider-hosted app which runs on Azure web site and accesses data in Sharepoint Online site (host web). In this host web there is a list with unique permissions and app needed to make changes in the list items there. Users which used the app not always had edit rights on this list and attempts to change list item caused the following exception:

Access denied. You don't have permissions to perform this action or access this resource.

Problem was caused by the app’s code which uses user client context token:

   1: var spContext = SharePointContextProvider.Current.GetSharePointContext(context);
   2: using (var ctx = spContext.CreateUserClientContextForSPHost())
   3: {
   4:     ...
   5: }

With client context app’s code is only allowed to perform actions which are allowed for the current user. I.e. if user doesn’t have permissions to edit list items in the list, app’s code will fail with Access denied exception shown above.

In order to fix the issue we need to run our code under “elevated privileges”, which in case of app development model means that we need to use app only permissions. SharePointContext class has different method for obtaining app-only client context:

   1: var spContext = SharePointContextProvider.Current.GetSharePointContext(context);
   2: using (var ctx = spContext.CreateAppOnlyClientContextForSPHost())
   3: {
   4:     ...
   5: }

But it is only half. Second step is to allow our app to use app-only permissions. Without that changes described above won’t give any effect. In order to allow the app to use app-only permissions we need to add AllowAppOnlyPolicy="true" attribute to AppPermissionRequests tag inside app manifest and re-install the app through App catalog. Also it is possible to update AppPermissionRequests for already installed app without re-installation. In order to do that go to http://example.com/_layouts/15/appinv.aspx page (where instead of http://example.com you should use your Sharepoint Online tenant), specify app id in the textbox, click Lookup button and specify the following permissions xml:

   1: <AppPermissionRequests AllowAppOnlyPolicy="true">
   2:   <AppPermissionRequest Scope="http://sharepoint/content/sitecollection"
   3: Right="FullControl" />
   4: </AppPermissionRequests>

(in this example apps get Full control over whole site collection. You need to use your own permissions there. The important part now is that AppPermissionRequests tag has AllowAppOnlyPolicy="true" attribute). After that click Create button and then Trust it on the opened window.

Note that if you specify AppPermissionRequests with scope starting from site collection like shown above and less (web, list) it is enough to have site collection admin rights for updating the app. But if you change app permissions on the tenant level (Scope=”http://sharepoint/content/tenant”) you should have tenant admin permissions (see permission scopes in the following article: Add-in permissions in SharePoint 2013).

After these steps app should be able to execute code with elevated privileges using app-only permissions.

Thursday, April 27, 2017

SP Editor Chrome extension: free open source alternative to Sharepoint Designer

Did you ever face with situation when you need to debug some javascript code on customer’s Sharepoint environment or change something in css stylesheets, but there is no Sharepoint Designer installed on server. Or when you need to make fixes on Sharepoint Online site, but don’t have Sharepoint Designer on your PC right now? What do we do in such situations: download files, make modifications, upload them, check in, test changes, again download and make modifications, etc. Boring process. Fortunately nowadays there is convenient tool created of one of my work buddies Tomi Tavela: SP Editor. In this post I will show it’s features and what you can do with it.

Once you’ve installed it to the Chrome by clicking F12 there will be new Sharepoint tab in browser Developer tools which looks like this:

There are 2 checkboxes on the start page which are unchecked by default:

  • Update changes to Sharpepoint
  • Publish a Major

If you want to save major changes to Sharepoint by clicking Ctrl-S then check both checkboxes. Otherwise tool won’t make changes on living Sharepoint site.

In order to start working open Sharepoint site in the browser and open Sharepoint tab in developer tools. I personally often use Edit file feature, let’s check how it works. First of all we open File editor tab and select file which we want to edit:

Then we edit selected file in the right frame area and click Ctrl-S when changes are ready in order to save them. File is saved to Sharepoint and checked in with major version so you may test it on the page immediately (refresh the page with Ctrl-F5 in order to get new changes). Moreover as you can see on the screenshot above editor has intellisense.

Let’s briefly check other features.

Scriptlinks – allows to add and remove script links of web and site level:

Files – upload new file to Style library and optionally inject it to the site via script links:

Web properties – add, change or remove web property bag values:

List properties – add, change or remove list property bag values:

Webhooks – simplifies adding of web hooks to the lists or doclibs (see web hooks):

PnP JS Console v2.0.4 – console for simplifying making REST calls to Sharepoint via pnp-js-core library. Also with intellisense:

Page editor – extension for editing web parts on the publishing pages developed by another great guy Andrey Markeev:

As you can see SP Editor is created by developers and for developers for simplifying our everyday tasks. It is open source, source code is available here. So big thanks to Tomi and all other developers who made contributions for such useful tool)

Thursday, April 20, 2017

Overview of item-level permissions in Sharepoint lists

In Sharepoint lists you may configure so called item-level permissions: List settings > Advanced settings > Item-level permissions:

In this article I will show how this setting affects behavior of the lists. In our examples I will use calendar list and 2 different users with Contribute permissions: user1 and user2. user1 created event “test from user1”, and user2 – “test from user2”.

So first of all we need to distinguish 2 concepts:

  1. Item-level security
  2. Item-level permissions

Item-level security means “physical” permissions which you may assign on particular list item (e.g. from context menu shown for this list item Shared With > Advanced) when list items don’t inherit permissions from parent list (and as you probably know list item is minimal securable object in Sharepoint: Site > Web > List > Folder > Item). E.g. you may grant particular permission level to user, AD or Sharepoint group on the list item and list item will become secured by standard Sharepoint security mechanism.

Item-level permissions shown in List settings > Advanced settings don’t use standard Sharepoint security mechanism. It is more like adding filters to the list views to shown only items created by current user ([Me]) and adding additional checks on Save button in Add and Edit list forms (see below).

So let’s start with configuration shown above:

  • Read all items
  • Create and edit all items

In this case both user1 and user2 will see the same list view:

I.e. all users see events created by all users. Also both users are able to modify all events regardless of who created this event.

Now let’s check the following configuration:

  • Read items that were created by the user
  • Create and edit all items

In this case user1 will see only own event:

the same as user2:

In this case even if user1 knows direct url of edit form for event created by user1 (EditForm.aspx?ID=…), it won’t be possible to edit it: Sharepoint will show error “Item not found”. I.e. “Read items that were created by the user” setting is has priority over “Create and edit all items” in this case.

Next combination is this one:

  • Read items that were created by the user
  • Create items and edit items that were created by the user

In this case it will work in the same way as previous combination, i.e. users may see and edit only own events.

Next combination:

  • Read all items
  • Create items and edit items that were created by the user

In this case both users again will see all events:

Also there still will be Edit button available in the ribbon so e.g. user1 may select “test from user2” and click Edit – edit form will be successfully shown. But when user1 will click Save on this form Sharepoint will show error “You do not have access to this page”.

And the last combination when user can’t create or edit items:

  • Read all items/Read items that were created by the user
  • None

In this case Sharepoint will show “You do not have access to this page” error when user will try to create new event or edit any event, including own (which was probably created before this setting was turned on). Although new and edit forms still will be opened, error will be shown on clicking Save button.

And last thing which I would like to mention is that under Item-level permissions section in advanced list setting there is the following note:

Users with the Cancel Checkout permission can read and edit all items.

If you will go to Site settings > Site permissions > Permissions levels and will try to edit existing permission level or create new one, you won’t find exactly “Cancel Checkout” permission there. Instead there will be “Override List Bahaviors”:

Discard or check in a document which is checked out to another user, and change or override settings which allow users to read/edit only their own items

And actually this permission is meant under Item-level permissions instead of “Cancel Checkout”. This is all what I wanted to share about this topic. Hope that this information will help someone.