diff --git a/src/lib/PnP.Framework/Graph/GraphUtility.cs b/src/lib/PnP.Framework/Graph/GraphUtility.cs index 76e1ef47b..cc0ee409d 100644 --- a/src/lib/PnP.Framework/Graph/GraphUtility.cs +++ b/src/lib/PnP.Framework/Graph/GraphUtility.cs @@ -1,11 +1,28 @@ using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Models.ODataErrors; +using Microsoft.Kiota.Abstractions.Authentication; using PnP.Framework.Diagnostics; using System; -using System.Net.Http.Headers; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace PnP.Framework.Graph { + public class TokenProvider : IAccessTokenProvider + { + public Task GetAuthorizationTokenAsync(Uri uri, Dictionary additionalAuthenticationContext = default, + CancellationToken cancellationToken = default) + { + var token = AccessToken; + // get the token and return it in your own way + return Task.FromResult(token); + } + public string AccessToken { get; set; } + public AllowedHostsValidator AllowedHostsValidator { get; } + } + /// /// Utility class to perform Graph operations. /// @@ -30,18 +47,24 @@ public static GraphServiceClient CreateGraphClient(string accessToken, int retry // Creates a new GraphServiceClient instance using a custom PnPHttpProvider // which natively supports retry logic for throttled requests // Default are 10 retries with a base delay of 500ms - var result = new GraphServiceClient(baseUrl, new DelegateAuthenticationProvider( - async (requestMessage) => - { - await Task.Run(() => - { - if (!string.IsNullOrEmpty(accessToken)) - { - // Configure the HTTP bearer Authorization Header - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); - } - }); - }), new PnPHttpProvider(retryCount, delay)); + //var result = new GraphServiceClient(baseUrl, new DelegateAuthenticationProvider( + // async (requestMessage) => + // { + // await Task.Run(() => + // { + // if (!string.IsNullOrEmpty(accessToken)) + // { + // // Configure the HTTP bearer Authorization Header + // requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); + // } + // }); + // }), new PnPHttpProvider(retryCount, delay)); + + var tokenProvider = new TokenProvider(); + tokenProvider.AccessToken = accessToken; + + var authenticationProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider); + var result = new GraphServiceClient(authenticationProvider, baseUrl); return (result); } @@ -86,9 +109,9 @@ public static Invitation InviteGuestUser(string accessToken, string guestUserEma // Create the graph client and send the invitation. GraphServiceClient graphClient = CreateGraphClient(accessToken, azureEnvironment: azureEnvironment); - inviteUserResponse = graphClient.Invitations.Request().AddAsync(invite).Result; + inviteUserResponse = graphClient.Invitations.PostAsync(invite).Result; } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; diff --git a/src/lib/PnP.Framework/Graph/GroupsUtility.cs b/src/lib/PnP.Framework/Graph/GroupsUtility.cs index d028267bf..dbd5dac40 100644 --- a/src/lib/PnP.Framework/Graph/GroupsUtility.cs +++ b/src/lib/PnP.Framework/Graph/GroupsUtility.cs @@ -1,4 +1,6 @@ using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Models.ODataErrors; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PnP.Framework.Diagnostics; @@ -7,6 +9,7 @@ using PnP.Framework.Utilities.Graph; using System; using System.Collections.Generic; +using System.Diagnostics.Metrics; using System.Linq; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -30,23 +33,24 @@ public static class GroupsUtility /// Azure environment to use, needed to get the correct Microsoft Graph URL /// private static GraphServiceClient CreateGraphClient(string accessToken, int retryCount = defaultRetryCount, int delay = defaultDelay, AzureEnvironment azureEnvironment = AzureEnvironment.Production) - { + { // Creates a new GraphServiceClient instance using a custom PnPHttpProvider // which natively supports retry logic for throttled requests // Default are 10 retries with a base delay of 500ms - var result = new GraphServiceClient($"{AuthenticationManager.GetGraphBaseEndPoint(azureEnvironment)}v1.0", new DelegateAuthenticationProvider( - async (requestMessage) => - { - await Task.Run(() => - { - if (!String.IsNullOrEmpty(accessToken)) - { - // Configure the HTTP bearer Authorization Header - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); - } - }); - }), new PnPHttpProvider(retryCount, delay)); - + //var result = new GraphServiceClient($"{AuthenticationManager.GetGraphBaseEndPoint(azureEnvironment)}v1.0", new DelegateAuthenticationProvider( + // async (requestMessage) => + // { + // await Task.Run(() => + // { + // if (!String.IsNullOrEmpty(accessToken)) + // { + // // Configure the HTTP bearer Authorization Header + // requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); + // } + // }); + // }), new PnPHttpProvider(retryCount, delay)); + + var result = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); return (result); } @@ -69,7 +73,7 @@ public static GroupEntity CreateGroup(string displayName, string description, st string accessToken, string[] owners = null, string[] members = null, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { GroupEntity result = null; - + var baseUrl = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}/v1.0"; if (String.IsNullOrEmpty(displayName)) { throw new ArgumentNullException(nameof(displayName)); @@ -109,7 +113,7 @@ public static GroupEntity CreateGroup(string displayName, string description, st var users = GetUsers(graphClient, owners); if (users != null && users.Count > 0) { - newGroup.OwnersODataBind = users.Select(u => string.Format("{1}/users/{0}", u.Id, graphClient.BaseUrl)).ToArray(); + newGroup.OwnersODataBind = users.Select(u => string.Format("{1}/users/{0}", u.Id, baseUrl)).ToArray(); } } @@ -118,12 +122,12 @@ public static GroupEntity CreateGroup(string displayName, string description, st var users = GetUsers(graphClient, members); if (users != null && users.Count > 0) { - newGroup.MembersODataBind = users.Select(u => string.Format("{1}/users/{0}", u.Id, graphClient.BaseUrl)).ToArray(); + newGroup.MembersODataBind = users.Select(u => string.Format("{1}/users/{0}", u.Id, baseUrl)).ToArray(); } } // Create the group - Microsoft.Graph.Group addedGroup = await graphClient.Groups.Request().AddAsync(newGroup); + Microsoft.Graph.Models.Group addedGroup = await graphClient.Groups.PostAsync(newGroup); if (addedGroup != null) { @@ -141,7 +145,7 @@ public static GroupEntity CreateGroup(string displayName, string description, st }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -156,24 +160,30 @@ public static GroupEntity CreateGroup(string displayName, string description, st /// GraphClient instance to use to communicate with the Microsoft Graph /// Id of the group which needs the owners added /// If set to true, all existing members which are not specified through will be removed as a member from the group - private static async Task UpdateMembers(string[] members, GraphServiceClient graphClient, string groupId, bool removeOtherMembers) + private static async Task UpdateMembers(string[] members, GraphServiceClient graphClient, string groupId, bool removeOtherMembers, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { + var baseUrl = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}/v1.0"; foreach (var m in members) { // Search for the user object - var memberQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'") - .GetAsync(); + var memberQuery = await graphClient.Users + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'"; + }); - var member = memberQuery.FirstOrDefault(); + var member = memberQuery.Value.FirstOrDefault(); if (member != null) { try - { + { + var expectedRequestBody = new ReferenceCreate() + { + OdataId = string.Format("{0}/directoryObjects/{1}", string.Format(baseUrl, "v1.0"), member.Id), + }; // And if any, add it to the collection of group's owners - await graphClient.Groups[groupId].Members.References.Request().AddAsync(member); + await graphClient.Groups[groupId].Members.Ref.PostAsync(expectedRequestBody); } catch (Exception ex) { @@ -199,23 +209,28 @@ private static async Task UpdateMembers(string[] members, GraphServiceClient gra } // Remove any leftover member - var fullListOfMembers = await graphClient.Groups[groupId].Members.Request().Select("userPrincipalName, Id").GetAsync(); + var fullListOfMembers = await graphClient.Groups[groupId].Members + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = new string[] { "userPrincipalName", "Id" }; + }); + var pageExists = true; while (pageExists) { - foreach (var member in fullListOfMembers) + foreach (var member in fullListOfMembers.Value) { - var currentMemberPrincipalName = (member as Microsoft.Graph.User)?.UserPrincipalName; + var currentMemberPrincipalName = (member as Microsoft.Graph.Models.User)?.UserPrincipalName; if (!string.IsNullOrEmpty(currentMemberPrincipalName) && !members.Contains(currentMemberPrincipalName, StringComparer.InvariantCultureIgnoreCase)) { try { // If it is not in the list of current owners, just remove it - await graphClient.Groups[groupId].Members[member.Id].Reference.Request().DeleteAsync(); + await graphClient.Groups[groupId].Members[member.Id].Ref.DeleteAsync(); } - catch (ServiceException ex) + catch (ODataError ex) { if (ex.Error.Code == "Request_BadRequest") { @@ -229,9 +244,9 @@ private static async Task UpdateMembers(string[] members, GraphServiceClient gra } } - if (fullListOfMembers.NextPageRequest != null) + if (fullListOfMembers.OdataNextLink != null) { - fullListOfMembers = await fullListOfMembers.NextPageRequest.GetAsync(); + //fullListOfMembers = await fullListOfMembers.OdataNextLink.GetAsync(); } else { @@ -247,24 +262,30 @@ private static async Task UpdateMembers(string[] members, GraphServiceClient gra /// GraphClient instance to use to communicate with the Microsoft Graph /// Id of the group which needs the owners added /// If set to true, all existing owners which are not specified through will be removed as an owner from the group - private static async Task UpdateOwners(string[] owners, GraphServiceClient graphClient, string groupId, bool removeOtherOwners) + private static async Task UpdateOwners(string[] owners, GraphServiceClient graphClient, string groupId, bool removeOtherOwners, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { + var baseUrl = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}/1.0"; foreach (var o in owners) { // Search for the user object var ownerQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(o.Replace("'", "''"))}'") - .GetAsync(); - - var owner = ownerQuery.FirstOrDefault(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(o.Replace("'", "''"))}'"; + }); + + var owner = ownerQuery?.Value.FirstOrDefault(); if (owner != null) { try { + var expectedRequestBody = new ReferenceCreate() + { + OdataId = string.Format("{0}/directoryObjects/{1}", string.Format(baseUrl, "v1.0"), owner.Id), + }; // And if any, add it to the collection of group's owners - await graphClient.Groups[groupId].Owners.References.Request().AddAsync(owner); + await graphClient.Groups[groupId].Owners.Ref.PostAsync(expectedRequestBody); } catch (Exception ex) { @@ -290,45 +311,38 @@ private static async Task UpdateOwners(string[] owners, GraphServiceClient graph } // Remove any leftover owner - var fullListOfOwners = await graphClient.Groups[groupId].Owners.Request().Select("userPrincipalName, Id").GetAsync(); - var pageExists = true; + var fullListOfOwners = await graphClient.Groups[groupId].Owners + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = new string[] { "userPrincipalName", "Id" }; + }); - while (pageExists) + var pageIterator = PageIterator.CreatePageIterator(graphClient, fullListOfOwners, (owner) => { - foreach (var owner in fullListOfOwners) + var currentOwnerPrincipalName = (owner as Microsoft.Graph.Models.User)?.UserPrincipalName; + if (!string.IsNullOrEmpty(currentOwnerPrincipalName) && + !owners.Contains(currentOwnerPrincipalName, StringComparer.InvariantCultureIgnoreCase)) { - var currentOwnerPrincipalName = (owner as Microsoft.Graph.User)?.UserPrincipalName; - if (!string.IsNullOrEmpty(currentOwnerPrincipalName) && - !owners.Contains(currentOwnerPrincipalName, StringComparer.InvariantCultureIgnoreCase)) + try { - try + // If it is not in the list of current owners, just remove it + graphClient.Groups[groupId].Owners[owner.Id].Ref.DeleteAsync().GetAwaiter().GetResult(); + } + catch (ODataError ex) + { + if (ex.Error.Code == "Request_BadRequest") { - // If it is not in the list of current owners, just remove it - await graphClient.Groups[groupId].Owners[owner.Id].Reference.Request().DeleteAsync(); + // Skip any failing removal } - catch (ServiceException ex) + else { - if (ex.Error.Code == "Request_BadRequest") - { - // Skip any failing removal - } - else - { - throw ex; - } + throw ex; } } } - - if (fullListOfOwners.NextPageRequest != null) - { - fullListOfOwners = await fullListOfOwners.NextPageRequest.GetAsync(); - } - else - { - pageExists = false; - } - } + return true; + }); + await pageIterator.IterateAsync(); } /// @@ -371,7 +385,7 @@ public static void SetGroupVisibility(string groupId, string accessToken, bool? contentType: "application/json", accessToken: accessToken); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -405,8 +419,7 @@ public static bool UpdateGroup(string groupId, { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - var groupToUpdate = await graphClient.Groups[groupId] - .Request() + var groupToUpdate = await graphClient.Groups[groupId] .GetAsync(); // Workaround for the PATCH request, needed after update to Graph Library @@ -467,9 +480,7 @@ public static bool UpdateGroup(string groupId, // If the Group has to be updated, just do it if (updateGroup) { - var updatedGroup = await graphClient.Groups[groupId] - .Request() - .UpdateAsync(clonedGroup); + var updatedGroup = await graphClient.Groups[groupId].PatchAsync(groupToUpdate); groupUpdated = true; } @@ -481,7 +492,7 @@ public static bool UpdateGroup(string groupId, }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -514,11 +525,11 @@ public static void DeleteGroup(string groupId, string accessToken, int retryCoun Task.Run(async () => { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - await graphClient.Groups[groupId].Request().DeleteAsync(); + await graphClient.Groups[groupId].DeleteAsync(); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -556,7 +567,7 @@ public static GroupEntity GetGroup(string groupId, string accessToken, int retry var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - var g = await graphClient.Groups[groupId].Request().GetAsync(); + var g = await graphClient.Groups[groupId].GetAsync(); group = new GroupEntity { @@ -574,7 +585,7 @@ public static GroupEntity GetGroup(string groupId, string accessToken, int retry }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -620,54 +631,35 @@ public static List GetGroups(string accessToken, var mailNicknameFilter = !string.IsNullOrEmpty(mailNickname) ? $"(MailNickname eq '{Uri.EscapeDataString(mailNickname.Replace("'", "''"))}')" : string.Empty; var pagedGroups = await graphClient.Groups - .Request() - .Filter($"{displayNameFilter}{(!string.IsNullOrEmpty(displayNameFilter) && !string.IsNullOrEmpty(mailNicknameFilter) ? " and " : "")}{mailNicknameFilter}") - .Top(pageSize) - .GetAsync(); - - Int32 pageCount = 0; - Int32 currentIndex = 0; + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"{displayNameFilter}{(!string.IsNullOrEmpty(displayNameFilter) && !string.IsNullOrEmpty(mailNicknameFilter) ? " and " : "")}{mailNicknameFilter}"; + requestConfiguration.QueryParameters.Top = pageSize; + }); - while (true) + var pageIterator = PageIterator.CreatePageIterator(graphClient, pagedGroups, (g) => { - pageCount++; - - foreach (var g in pagedGroups) + var group = new GroupEntity { - currentIndex++; - - if (currentIndex >= startIndex) - { - var group = new GroupEntity - { - GroupId = g.Id, - DisplayName = g.DisplayName, - Description = g.Description, - Mail = g.Mail, - MailNickname = g.MailNickname, - MailEnabled = g.MailEnabled, - SecurityEnabled = g.SecurityEnabled, - GroupTypes = g.GroupTypes != null ? g.GroupTypes.ToArray() : null - }; - - groups.Add(group); - } - } - - if (pagedGroups.NextPageRequest != null && (endIndex == null || groups.Count < endIndex)) - { - pagedGroups = await pagedGroups.NextPageRequest.GetAsync(); - } - else - { - break; - } - } + GroupId = g.Id, + DisplayName = g.DisplayName, + Description = g.Description, + Mail = g.Mail, + MailNickname = g.MailNickname, + MailEnabled = g.MailEnabled, + SecurityEnabled = g.SecurityEnabled, + GroupTypes = g.GroupTypes != null ? g.GroupTypes.ToArray() : null + }; + groups.Add(group); + return true; + }); + + await pageIterator.IterateAsync(); return (groups); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -686,9 +678,7 @@ public static List GetGroups(string accessToken, /// Members of an Azure Active Directory group public static List GetGroupMembers(GroupEntity group, string accessToken, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { - List groupUsers = null; - List groupGraphUsers = null; - IGroupMembersCollectionWithReferencesPage groupUsersCollection = null; + List groupUsers = new List(); if (String.IsNullOrEmpty(accessToken)) { @@ -705,60 +695,40 @@ public static List GetGroupMembers(GroupEntity group, string accessTo { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - // Get the members of the group - groupUsersCollection = await graphClient.Groups[group.GroupId].Members.Request().GetAsync(); - if (groupUsersCollection.CurrentPage != null && groupUsersCollection.CurrentPage.Count > 0) - { - groupGraphUsers = new List(); - groupGraphUsers.AddRange(groupUsersCollection.CurrentPage); - //GenerateGraphUserCollection(groupUsersCollection.CurrentPage, groupGraphUsers); - } - - // Retrieve users when the results are paged. - while (groupUsersCollection.NextPageRequest != null) - { - groupUsersCollection = groupUsersCollection.NextPageRequest.GetAsync().GetAwaiter().GetResult(); - if (groupUsersCollection.CurrentPage != null && groupUsersCollection.CurrentPage.Count > 0) - { - groupGraphUsers.AddRange(groupUsersCollection.CurrentPage); - //GenerateGraphUserCollection(groupUsersCollection.CurrentPage, groupGraphUsers); - } - } + var groupUsersCollection = await graphClient.Groups[group.GroupId].Members.GetAsync(); - // Create the collection of type OfficeDevPnP groupuser after all users are retrieved, including paged data. - if (groupGraphUsers != null && groupGraphUsers.Count > 0) + var iterator = PageIterator.CreatePageIterator(graphClient, groupUsersCollection, (usr) => { - groupUsers = new List(); - foreach (DirectoryObject usr in groupGraphUsers) + switch (usr) { - switch(usr) - { - case Microsoft.Graph.User userType: - groupUsers.Add(new GroupUser - { - UserPrincipalName = userType.UserPrincipalName != null ? userType.UserPrincipalName : string.Empty, - DisplayName = userType.DisplayName != null ? userType.DisplayName : string.Empty, - Type = Enums.GroupUserType.User - }); + case Microsoft.Graph.Models.User userType: + groupUsers.Add(new GroupUser + { + UserPrincipalName = userType.UserPrincipalName != null ? userType.UserPrincipalName : string.Empty, + DisplayName = userType.DisplayName != null ? userType.DisplayName : string.Empty, + Type = Enums.GroupUserType.User + }); break; - case Microsoft.Graph.Group groupType: - groupUsers.Add(new GroupUser - { - UserPrincipalName = groupType.Id != null ? groupType.Id : string.Empty, - DisplayName = groupType.DisplayName != null ? groupType.DisplayName : string.Empty, - Type = Enums.GroupUserType.Group - }); - break; - } - + case Microsoft.Graph.Models.Group groupType: + groupUsers.Add(new GroupUser + { + UserPrincipalName = groupType.Id != null ? groupType.Id : string.Empty, + DisplayName = groupType.DisplayName != null ? groupType.DisplayName : string.Empty, + Type = Enums.GroupUserType.Group + }); + break; } - } + return true; + }); + + await iterator.IterateAsync(); + return groupUsers; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -793,7 +763,7 @@ public static void AddGroupOwners(string groupId, string[] owners, string access }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -827,7 +797,7 @@ public static void AddGroupMembers(string groupId, string[] members, string acce }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -860,20 +830,21 @@ public static void RemoveGroupMembers(string groupId, string[] members, string a { // Search for the user object var memberQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'") - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'"; + }); - var member = memberQuery.FirstOrDefault(); + var member = memberQuery?.Value?.FirstOrDefault(); if (member != null) { try { // If it is not in the list of current members, just remove it - await graphClient.Groups[groupId].Members[member.Id].Reference.Request().DeleteAsync(); + await graphClient.Groups[groupId].Members[member.Id].Ref.DeleteAsync(); } - catch (ServiceException ex) + catch (ODataError ex) { if (ex.Error.Code == "Request_BadRequest") { @@ -889,7 +860,7 @@ public static void RemoveGroupMembers(string groupId, string[] members, string a }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -922,22 +893,23 @@ public static void RemoveGroupOwners(string groupId, string[] owners, string acc { // Search for the user object var memberQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'") - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'"; + }); - var member = memberQuery.FirstOrDefault(); + var member = memberQuery?.Value?.FirstOrDefault(); if (member != null) { // If it is not in the list of current owners, just remove it - await graphClient.Groups[groupId].Owners[member.Id].Reference.Request().DeleteAsync(); + await graphClient.Groups[groupId].Owners[member.Id].Ref.DeleteAsync(); } } }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -963,7 +935,7 @@ public static void ClearGroupOwners(string groupId, string accessToken, int retr var currentOwners = GetGroupOwners(new GroupEntity { GroupId = groupId }, accessToken, retryCount, delay); RemoveGroupOwners(groupId, currentOwners.Select(o => o.UserPrincipalName).ToArray(), accessToken, retryCount, delay); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -991,7 +963,7 @@ public static void ClearGroupMembers(string groupId, string accessToken, int ret RemoveGroupMembers(groupId, currentMembers.Select(o => o.UserPrincipalName).ToArray(), accessToken, retryCount, delay); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1010,8 +982,6 @@ public static void ClearGroupMembers(string groupId, string accessToken, int ret public static List GetGroupOwners(GroupEntity group, string accessToken, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { List groupUsers = null; - List groupGraphUsers = null; - IGroupOwnersCollectionWithReferencesPage groupUsersCollection = null; if (String.IsNullOrEmpty(accessToken)) { @@ -1025,57 +995,40 @@ public static List GetGroupOwners(GroupEntity group, string accessTok var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); // Get the owners of an Office 365 group. - groupUsersCollection = await graphClient.Groups[group.GroupId].Owners.Request().GetAsync(); - if (groupUsersCollection.CurrentPage != null && groupUsersCollection.CurrentPage.Count > 0) - { - groupGraphUsers = new List(); - GenerateGraphUserCollection(groupUsersCollection.CurrentPage, groupGraphUsers); - } + var groupUsersCollection = await graphClient.Groups[group.GroupId].Owners.GetAsync(); - // Retrieve users when the results are paged. - while (groupUsersCollection.NextPageRequest != null) + var pageIterator = PageIterator.CreatePageIterator(graphClient, groupUsersCollection, (usr) => { - groupUsersCollection = groupUsersCollection.NextPageRequest.GetAsync().GetAwaiter().GetResult(); - if (groupUsersCollection.CurrentPage != null && groupUsersCollection.CurrentPage.Count > 0) + switch (usr) { - GenerateGraphUserCollection(groupUsersCollection.CurrentPage, groupGraphUsers); - } - } + case Microsoft.Graph.Models.User userType: + groupUsers.Add(new GroupUser + { + UserPrincipalName = userType.UserPrincipalName != null ? userType.UserPrincipalName : string.Empty, + DisplayName = userType.DisplayName != null ? userType.DisplayName : string.Empty, + Type = Enums.GroupUserType.User + }); + break; - // Create the collection of type OfficeDevPnP 'UnifiedGroupUser' after all users are retrieved, including paged data. - if (groupGraphUsers != null && groupGraphUsers.Count > 0) - { - groupUsers = new List(); - foreach (DirectoryObject usr in groupGraphUsers) - { - switch(usr) - { - case Microsoft.Graph.User userType: - groupUsers.Add(new GroupUser - { - UserPrincipalName = userType.UserPrincipalName != null ? userType.UserPrincipalName : string.Empty, - DisplayName = userType.DisplayName != null ? userType.DisplayName : string.Empty, - Type = Enums.GroupUserType.User - }); + case Microsoft.Graph.Models.Group groupType: + groupUsers.Add(new GroupUser + { + UserPrincipalName = groupType.Id != null ? groupType.Id : string.Empty, + DisplayName = groupType.DisplayName != null ? groupType.DisplayName : string.Empty, + Type = Enums.GroupUserType.Group + }); break; + } + return true; + }); - case Microsoft.Graph.Group groupType: - groupUsers.Add(new GroupUser - { - UserPrincipalName = groupType.Id != null ? groupType.Id : string.Empty, - DisplayName = groupType.DisplayName != null ? groupType.DisplayName : string.Empty, - Type = Enums.GroupUserType.Group - }); - break; - } + await pageIterator.IterateAsync(); - } - } return groupUsers; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1185,19 +1138,20 @@ private static List GetUsers(GraphServiceClient graphClient, string[] grou try { // Search for the user object - IGraphServiceUsersCollectionPage userQuery = await graphClient.Users - .Request() - .Select("Id") - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(groupUser.Replace("'", "''"))}'") - .GetAsync(); - - User user = userQuery.FirstOrDefault(); + var userQuery = await graphClient.Users + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = new string[] { "Id" }; + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(groupUser.Replace("'", "''"))}'"; + }); + + User user = userQuery?.Value.FirstOrDefault(); if (user != null) { usersResult.Add(user); } } - catch (ServiceException) + catch (ODataError) { // skip, group provisioning shouldnt stop because of error in user object } diff --git a/src/lib/PnP.Framework/Graph/PnPHttpProvider.cs b/src/lib/PnP.Framework/Graph/PnPHttpProvider.cs index 8a4ab3e66..ee5500757 100644 --- a/src/lib/PnP.Framework/Graph/PnPHttpProvider.cs +++ b/src/lib/PnP.Framework/Graph/PnPHttpProvider.cs @@ -1,52 +1,52 @@ -using Microsoft.Graph; -using PnP.Framework.Utilities; -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; +//using Microsoft.Graph; +//using PnP.Framework.Utilities; +//using System; +//using System.Net.Http; +//using System.Threading; +//using System.Threading.Tasks; -namespace PnP.Framework.Graph -{ - /// - /// Class that deals with PnPHttpProvider methods - /// - public class PnPHttpProvider : HttpProvider, IHttpProvider - { - private readonly string _userAgent; - private readonly PnPHttpRetryHandler _retryHandler; +//namespace PnP.Framework.Graph +//{ +// /// +// /// Class that deals with PnPHttpProvider methods +// /// +// public class PnPHttpProvider : HttpProvider, IHttpProvider +// { +// private readonly string _userAgent; +// private readonly PnPHttpRetryHandler _retryHandler; - /// - /// Constructor for the PnPHttpProvider class - /// - /// Maximum retry Count - /// Delay Time - /// User-Agent string to set - public PnPHttpProvider(int retryCount = 10, int delay = 500, string userAgent = null) : base() - { - if (retryCount <= 0) - throw new ArgumentException("Provide a retry count greater than zero."); +// /// +// /// Constructor for the PnPHttpProvider class +// /// +// /// Maximum retry Count +// /// Delay Time +// /// User-Agent string to set +// public PnPHttpProvider(int retryCount = 10, int delay = 500, string userAgent = null) : base() +// { +// if (retryCount <= 0) +// throw new ArgumentException("Provide a retry count greater than zero."); - if (delay <= 0) - throw new ArgumentException("Provide a delay greater than zero."); +// if (delay <= 0) +// throw new ArgumentException("Provide a delay greater than zero."); - this._userAgent = userAgent; - this._retryHandler = new PnPHttpRetryHandler(retryCount, delay); - } +// this._userAgent = userAgent; +// this._retryHandler = new PnPHttpRetryHandler(retryCount, delay); +// } - /// - /// Custom implementation of the IHttpProvider.SendAsync method to handle retry logic - /// - /// The HTTP Request Message - /// The completion option - /// The cancellation token - /// The result of the asynchronous request - /// See here for further details: https://graph.microsoft.io/en-us/docs/overview/errors - async Task IHttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) - { - // Add the PnP User Agent string - request.Headers.UserAgent.TryParseAdd(string.IsNullOrEmpty(_userAgent) ? $"{PnPCoreUtilities.PnPCoreUserAgent}" : _userAgent); +// /// +// /// Custom implementation of the IHttpProvider.SendAsync method to handle retry logic +// /// +// /// The HTTP Request Message +// /// The completion option +// /// The cancellation token +// /// The result of the asynchronous request +// /// See here for further details: https://graph.microsoft.io/en-us/docs/overview/errors +// async Task IHttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) +// { +// // Add the PnP User Agent string +// request.Headers.UserAgent.TryParseAdd(string.IsNullOrEmpty(_userAgent) ? $"{PnPCoreUtilities.PnPCoreUserAgent}" : _userAgent); - return await _retryHandler.SendRetryAsync(request, (r) => base.SendAsync(r, completionOption, cancellationToken), cancellationToken); - } - } -} +// return await _retryHandler.SendRetryAsync(request, (r) => base.SendAsync(r, completionOption, cancellationToken), cancellationToken); +// } +// } +//} diff --git a/src/lib/PnP.Framework/Graph/SubscriptionsUtility.cs b/src/lib/PnP.Framework/Graph/SubscriptionsUtility.cs index 026d7dfc8..c5124c36e 100644 --- a/src/lib/PnP.Framework/Graph/SubscriptionsUtility.cs +++ b/src/lib/PnP.Framework/Graph/SubscriptionsUtility.cs @@ -1,4 +1,6 @@ using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Models.ODataErrors; using PnP.Framework.Diagnostics; using System; using System.Collections.Generic; @@ -33,7 +35,6 @@ public static Model.Subscription GetSubscription(string accessToken, Guid subscr var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, azureEnvironment: azureEnvironment); var subscription = await graphClient.Subscriptions[subscriptionId.ToString()] - .Request() .GetAsync(); var subscriptionModel = MapGraphEntityToModel(subscription); @@ -42,7 +43,7 @@ public static Model.Subscription GetSubscription(string accessToken, Guid subscr return result; } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -76,42 +77,22 @@ public static Model.Subscription GetSubscription(string accessToken, Guid subscr var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, azureEnvironment: azureEnvironment); - var pagedSubscriptions = await graphClient.Subscriptions - .Request() - .GetAsync(); - - int pageCount = 0; - int currentIndex = 0; + var pagedSubscriptions = await graphClient.Subscriptions + .GetAsync(); - while (true) + var pageIterator = PageIterator.CreatePageIterator(graphClient, pagedSubscriptions, (subscription) => { - pageCount++; - - foreach (var s in pagedSubscriptions) - { - currentIndex++; - - if (currentIndex >= startIndex) - { - var subscription = MapGraphEntityToModel(s); - subscriptions.Add(subscription); - } - } - - if (pagedSubscriptions.NextPageRequest != null && currentIndex < endIndex) - { - pagedSubscriptions = await pagedSubscriptions.NextPageRequest.GetAsync(); - } - else - { - break; - } - } + var subscriptionModel = MapGraphEntityToModel(subscription); + subscriptions.Add(subscriptionModel); + return true; + }); + + await pageIterator.IterateAsync(); return subscriptions; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -165,9 +146,8 @@ public static Model.Subscription CreateSubscription(Enums.GraphSubscriptionChang ClientState = clientState }; - var subscription = await graphClient.Subscriptions - .Request() - .AddAsync(newSubscription); + var subscription = await graphClient.Subscriptions + .PostAsync(newSubscription); if (subscription == null) { @@ -179,7 +159,7 @@ public static Model.Subscription CreateSubscription(Enums.GraphSubscriptionChang }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -219,9 +199,8 @@ public static Model.Subscription UpdateSubscription(string subscriptionId, DateT ExpirationDateTime = expirationDateTime }; - var subscription = await graphClient.Subscriptions[subscriptionId] - .Request() - .UpdateAsync(updatedSubscription); + var subscription = await graphClient.Subscriptions[subscriptionId] + .PatchAsync(updatedSubscription); if (subscription == null) { @@ -233,7 +212,7 @@ public static Model.Subscription UpdateSubscription(string subscriptionId, DateT }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -263,13 +242,12 @@ public static void DeleteSubscription(string subscriptionId, { var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay); - await graphClient.Subscriptions[subscriptionId] - .Request() + await graphClient.Subscriptions[subscriptionId] .DeleteAsync(); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; diff --git a/src/lib/PnP.Framework/Graph/UnifiedGroupsUtility.cs b/src/lib/PnP.Framework/Graph/UnifiedGroupsUtility.cs index e6cbc9822..0dc8b8b7b 100644 --- a/src/lib/PnP.Framework/Graph/UnifiedGroupsUtility.cs +++ b/src/lib/PnP.Framework/Graph/UnifiedGroupsUtility.cs @@ -1,4 +1,6 @@ using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Graph.Models.ODataErrors; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PnP.Framework.Diagnostics; @@ -43,22 +45,9 @@ private static GraphServiceClient CreateGraphClient(String accessToken, int retr { // Creates a new GraphServiceClient instance using a custom PnPHttpProvider // which natively supports retry logic for throttled requests - // Default are 10 retries with a base delay of 500ms + // Default are 10 retries with a base delay of 500ms - var baseUrl = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}/v1.0"; - - var result = new GraphServiceClient(baseUrl, new DelegateAuthenticationProvider( - async (requestMessage) => - { - await Task.Run(() => - { - if (!String.IsNullOrEmpty(accessToken)) - { - // Configure the HTTP bearer Authorization Header - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); - } - }); - }), new PnPHttpProvider(retryCount, delay)); + var result = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, azureEnvironment: azureEnvironment); return (result); } @@ -91,7 +80,7 @@ public static string GetUnifiedGroupSiteUrl(string groupId, string accessToken, result = Convert.ToString(response["webUrl"]); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -141,7 +130,7 @@ public static UnifiedGroupEntity CreateUnifiedGroup(string displayName, string d } var labels = new List(); - if(assignedLabels != null) + if (assignedLabels != null) { foreach (var label in assignedLabels) { @@ -154,7 +143,7 @@ public static UnifiedGroupEntity CreateUnifiedGroup(string displayName, string d } } } - + try { @@ -175,7 +164,7 @@ public static UnifiedGroupEntity CreateUnifiedGroup(string displayName, string d SecurityEnabled = false, Visibility = isPrivate == true ? "Private" : "Public", GroupTypes = new List { "Unified" } - }; + }; if (labels.Any()) { @@ -214,13 +203,13 @@ public static UnifiedGroupEntity CreateUnifiedGroup(string displayName, string d newGroup.AdditionalData.Add("resourceBehaviorOptions", new string[] { "WelcomeEmailDisabled" }); } - Microsoft.Graph.Group addedGroup = null; + Microsoft.Graph.Models.Group addedGroup = null; string modernSiteUrl = null; // Add the group to the collection of groups (if it does not exist) if (addedGroup == null) { - addedGroup = await graphClient.Groups.Request().AddAsync(newGroup); + addedGroup = await graphClient.Groups.PostAsync(newGroup); if (addedGroup != null) { @@ -307,7 +296,7 @@ public static UnifiedGroupEntity CreateUnifiedGroup(string displayName, string d }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -322,26 +311,32 @@ public static UnifiedGroupEntity CreateUnifiedGroup(string displayName, string d /// GraphClient instance to use to communicate with the Microsoft Graph /// Id of the group which needs the owners added /// If set to true, all existing members which are not specified through will be removed as a member from the group - private static async Task UpdateMembers(string[] members, GraphServiceClient graphClient, string groupId, bool removeOtherMembers) + private static async Task UpdateMembers(string[] members, GraphServiceClient graphClient, string groupId, bool removeOtherMembers, AzureEnvironment azureEnvironment) { + var baseUrl = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}/v1.0"; if (members != null && members.Length > 0) { foreach (var m in members) { // Search for the user object var memberQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'") - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'"; + }); - var member = memberQuery.FirstOrDefault(); + var member = memberQuery?.Value.FirstOrDefault(); if (member != null) { try - { + { + var expectedRequestBody = new ReferenceCreate() + { + OdataId = string.Format("{0}/directoryObjects/{1}", string.Format(baseUrl, "v1.0"), member.Id), + }; // And if any, add it to the collection of group's owners - await graphClient.Groups[groupId].Members.References.Request().AddAsync(member); + await graphClient.Groups[groupId].Members.Ref.PostAsync(expectedRequestBody); } catch (Exception ex) { @@ -367,45 +362,39 @@ private static async Task UpdateMembers(string[] members, GraphServiceClient gra } // Remove any leftover member - var fullListOfMembers = await graphClient.Groups[groupId].Members.Request().Select("userPrincipalName, Id").GetAsync(); - var pageExists = true; + var fullListOfMembers = await graphClient.Groups[groupId].Members + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = new string[] { "userPrincipalName", "Id" }; + }); - while (pageExists) + var pageIterator = PageIterator.CreatePageIterator(graphClient, fullListOfMembers, (member) => { - foreach (var member in fullListOfMembers) + var currentMemberPrincipalName = (member as Microsoft.Graph.Models.User)?.UserPrincipalName; + if (!string.IsNullOrEmpty(currentMemberPrincipalName) && + !members.Contains(currentMemberPrincipalName, StringComparer.InvariantCultureIgnoreCase)) { - var currentMemberPrincipalName = (member as Microsoft.Graph.User)?.UserPrincipalName; - if (!string.IsNullOrEmpty(currentMemberPrincipalName) && - !members.Contains(currentMemberPrincipalName, StringComparer.InvariantCultureIgnoreCase)) + try { - try + // If it is not in the list of current owners, just remove it + graphClient.Groups[groupId].Members[member.Id].Ref.DeleteAsync().GetAwaiter().GetResult(); + } + catch (ODataError ex) + { + if (ex.Error.Code == "Request_BadRequest") { - // If it is not in the list of current owners, just remove it - await graphClient.Groups[groupId].Members[member.Id].Reference.Request().DeleteAsync(); + // Skip any failing removal } - catch (ServiceException ex) + else { - if (ex.Error.Code == "Request_BadRequest") - { - // Skip any failing removal - } - else - { - throw ex; - } + throw ex; } } } + return true; + }); - if (fullListOfMembers.NextPageRequest != null) - { - fullListOfMembers = await fullListOfMembers.NextPageRequest.GetAsync(); - } - else - { - pageExists = false; - } - } + await pageIterator.IterateAsync(); } /// @@ -415,24 +404,30 @@ private static async Task UpdateMembers(string[] members, GraphServiceClient gra /// GraphClient instance to use to communicate with the Microsoft Graph /// Id of the group which needs the owners added /// If set to true, all existing owners which are not specified through will be removed as an owner from the group - private static async Task UpdateOwners(string[] owners, GraphServiceClient graphClient, string groupId, bool removeOtherOwners) + private static async Task UpdateOwners(string[] owners, GraphServiceClient graphClient, string groupId, bool removeOtherOwners, AzureEnvironment azureEnvironment) { + var baseUrl = $"https://{AuthenticationManager.GetGraphEndPoint(azureEnvironment)}/v1.0"; foreach (var o in owners) { // Search for the user object var ownerQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(o.Replace("'", "''"))}'") - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(o.Replace("'", "''"))}'"; + }); - var owner = ownerQuery.FirstOrDefault(); + var owner = ownerQuery?.Value.FirstOrDefault(); if (owner != null) { try { + var expectedRequestBody = new ReferenceCreate() + { + OdataId = string.Format("{0}/directoryObjects/{1}", string.Format(baseUrl, "v1.0"), owner.Id), + }; // And if any, add it to the collection of group's owners - await graphClient.Groups[groupId].Owners.References.Request().AddAsync(owner); + await graphClient.Groups[groupId].Owners.Ref.PostAsync(expectedRequestBody); } catch (Exception ex) { @@ -458,45 +453,39 @@ private static async Task UpdateOwners(string[] owners, GraphServiceClient graph } // Remove any leftover owner - var fullListOfOwners = await graphClient.Groups[groupId].Owners.Request().Select("userPrincipalName, Id").GetAsync(); - var pageExists = true; + var fullListOfOwners = await graphClient.Groups[groupId].Owners + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = new string[] { "userPrincipalName", "Id" }; + }); - while (pageExists) + var pageIterator = PageIterator.CreatePageIterator(graphClient, fullListOfOwners, (owner) => { - foreach (var owner in fullListOfOwners) + var currentOwnerPrincipalName = (owner as Microsoft.Graph.Models.User)?.UserPrincipalName; + if (!string.IsNullOrEmpty(currentOwnerPrincipalName) && + !owners.Contains(currentOwnerPrincipalName, StringComparer.InvariantCultureIgnoreCase)) { - var currentOwnerPrincipalName = (owner as Microsoft.Graph.User)?.UserPrincipalName; - if (!string.IsNullOrEmpty(currentOwnerPrincipalName) && - !owners.Contains(currentOwnerPrincipalName, StringComparer.InvariantCultureIgnoreCase)) + try { - try + // If it is not in the list of current owners, just remove it + graphClient.Groups[groupId].Owners[owner.Id].Ref.DeleteAsync().GetAwaiter().GetResult(); + } + catch (ODataError ex) + { + if (ex.Error.Code == "Request_BadRequest") { - // If it is not in the list of current owners, just remove it - await graphClient.Groups[groupId].Owners[owner.Id].Reference.Request().DeleteAsync(); + // Skip any failing removal } - catch (ServiceException ex) + else { - if (ex.Error.Code == "Request_BadRequest") - { - // Skip any failing removal - } - else - { - throw ex; - } + throw ex; } - } + }; } + return true; + }); - if (fullListOfOwners.NextPageRequest != null) - { - fullListOfOwners = await fullListOfOwners.NextPageRequest.GetAsync(); - } - else - { - pageExists = false; - } - } + await pageIterator.IterateAsync(); } /// @@ -540,7 +529,7 @@ public static void SetUnifiedGroupVisibility(string groupId, string accessToken, contentType: "application/json", accessToken: accessToken); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -565,13 +554,10 @@ public static void RenewUnifiedGroup(string groupId, { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - await graphClient.Groups[groupId] - .Renew() - .Request() - .PostAsync(); + await graphClient.Groups[groupId].Renew.PostAsync(); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -608,7 +594,6 @@ public static bool UpdateUnifiedGroup(string groupId, var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); var groupToUpdate = await graphClient.Groups[groupId] - .Request() .GetAsync(); // Workaround for the PATCH request, needed after update to Graph Library @@ -648,7 +633,7 @@ public static bool UpdateUnifiedGroup(string groupId, if (owners != null && owners.Length > 0) { // For each and every owner - await UpdateOwners(owners, graphClient, groupToUpdate.Id, true); + await UpdateOwners(owners, graphClient, groupToUpdate.Id, true, azureEnvironment); updateGroup = true; } @@ -656,7 +641,7 @@ public static bool UpdateUnifiedGroup(string groupId, if (members != null && members.Length > 0) { // For each and every owner - await UpdateMembers(members, graphClient, groupToUpdate.Id, true); + await UpdateMembers(members, graphClient, groupToUpdate.Id, true, azureEnvironment); updateGroup = true; } @@ -670,8 +655,7 @@ public static bool UpdateUnifiedGroup(string groupId, if (updateGroup) { var updatedGroup = await graphClient.Groups[groupId] - .Request() - .UpdateAsync(clonedGroup); + .PatchAsync(clonedGroup); groupUpdated = true; } @@ -684,7 +668,7 @@ public static bool UpdateUnifiedGroup(string groupId, if (groupLogo != null) { - await graphClient.Groups[groupId].Photo.Content.Request().PutAsync(groupLogo); + await graphClient.Groups[groupId].Photo.Content.PutAsync(groupLogo); logoUpdated = true; } @@ -695,7 +679,7 @@ public static bool UpdateUnifiedGroup(string groupId, }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -798,11 +782,11 @@ public static void DeleteUnifiedGroup(string groupId, string accessToken, int re Task.Run(async () => { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - await graphClient.Groups[groupId].Request().DeleteAsync(); + await graphClient.Groups[groupId].DeleteAsync(); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -842,7 +826,7 @@ public static UnifiedGroupEntity GetUnifiedGroup(string groupId, string accessTo var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - var g = await graphClient.Groups[groupId].Request().GetAsync(); + var g = await graphClient.Groups[groupId].GetAsync(); if (g.GroupTypes.Contains("Unified")) { @@ -861,7 +845,7 @@ public static UnifiedGroupEntity GetUnifiedGroup(string groupId, string accessTo { group.SiteUrl = GetUnifiedGroupSiteUrl(groupId, accessToken); } - catch (ServiceException e) + catch (ODataError e) { group.SiteUrl = e.Error.Message; } @@ -882,130 +866,13 @@ public static UnifiedGroupEntity GetUnifiedGroup(string groupId, string accessTo }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; } return (result); - } - - /// - /// Returns all the Office 365 Groups in the current Tenant based on a startIndex. IncludeSite adds additional properties about the Modern SharePoint Site backing the group - /// - /// The OAuth 2.0 Access Token to use for invoking the Microsoft Graph - /// The DisplayName of the Office 365 Group - /// The MailNickname of the Office 365 Group - /// Not relevant anymore - /// Not relevant anymore - /// Defines whether to return details about the Modern SharePoint Site backing the group. Default is true. - /// Number of times to retry the request in case of throttling - /// Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry - /// Defines whether or not to return details about the Modern Site classification value. - /// Defines whether to check for each unified group if it has a Microsoft Team provisioned for it. Default is false. - /// Defines the Azure Cloud Deployment. This is used to determine the MS Graph EndPoint to call which differs per Azure Cloud deployments. Defaults to Production (graph.microsoft.com). - /// An IList of SiteEntity objects - [Obsolete("ListUnifiedGroups is deprecated, please use GetUnifiedGroups instead.")] - public static List ListUnifiedGroups(string accessToken, - string displayName = null, string mailNickname = null, - int startIndex = 0, int endIndex = 999, bool includeSite = true, - int retryCount = 10, int delay = 500, bool includeClassification = false, - bool includeHasTeam = false, AzureEnvironment azureEnvironment = AzureEnvironment.Production) - { - if (string.IsNullOrEmpty(accessToken)) - { - throw new ArgumentNullException(nameof(accessToken)); - } - - List result = null; - try - { - // Use a synchronous model to invoke the asynchronous process - result = Task.Run(async () => - { - List groups = new List(); - - var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - - // Apply the DisplayName filter, if any - var displayNameFilter = !string.IsNullOrEmpty(displayName) ? $" and (DisplayName eq '{Uri.EscapeDataString(displayName.Replace("'", "''"))}')" : string.Empty; - var mailNicknameFilter = !string.IsNullOrEmpty(mailNickname) ? $" and (MailNickname eq '{Uri.EscapeDataString(mailNickname.Replace("'", "''"))}')" : string.Empty; - - var pagedGroups = await graphClient.Groups - .Request() - .Filter($"groupTypes/any(grp: grp eq 'Unified'){displayNameFilter}{mailNicknameFilter}") - .Top(endIndex) - .GetAsync(); - - Int32 pageCount = 0; - Int32 currentIndex = 0; - - while (true) - { - pageCount++; - - foreach (var g in pagedGroups) - { - currentIndex++; - - if (currentIndex >= startIndex) - { - var group = new UnifiedGroupEntity - { - GroupId = g.Id, - DisplayName = g.DisplayName, - Description = g.Description, - Mail = g.Mail, - MailNickname = g.MailNickname, - Visibility = g.Visibility - }; - - if (includeSite) - { - try - { - group.SiteUrl = GetUnifiedGroupSiteUrl(g.Id, accessToken); - } - catch (ServiceException e) - { - group.SiteUrl = e.Error.Message; - } - } - - if (includeClassification) - { - group.Classification = g.Classification; - } - - if (includeHasTeam) - { - group.HasTeam = HasTeamsTeam(group.GroupId, accessToken); - } - - groups.Add(group); - } - } - - if (pagedGroups.NextPageRequest != null && groups.Count < endIndex) - { - pagedGroups = await pagedGroups.NextPageRequest.GetAsync(); - } - else - { - break; - } - } - - return (groups); - }).GetAwaiter().GetResult(); - } - catch (ServiceException ex) - { - Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); - throw; - } - return (result); - } + } /// /// Returns all the Office 365 Groups in the current Tenant based on a startIndex. IncludeSite adds additional properties about the Modern SharePoint Site backing the group @@ -1048,74 +915,56 @@ public static List GetUnifiedGroups(string accessToken, var mailNicknameFilter = !string.IsNullOrEmpty(mailNickname) ? $" and (MailNickname eq '{Uri.EscapeDataString(mailNickname.Replace("'", "''"))}')" : string.Empty; var pagedGroups = await graphClient.Groups - .Request() - .Filter($"groupTypes/any(grp: grp eq 'Unified'){displayNameFilter}{mailNicknameFilter}") - .Top(pageSize) - .GetAsync(); - - Int32 pageCount = 0; - Int32 currentIndex = 0; + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"groupTypes/any(grp: grp eq 'Unified'){displayNameFilter}{mailNicknameFilter}"; + requestConfiguration.QueryParameters.Top = pageSize; + }); - while (true) + var pageIterator = PageIterator.CreatePageIterator(graphClient, pagedGroups, (groupEntity) => { - pageCount++; - - foreach (var g in pagedGroups) + var group = new UnifiedGroupEntity { - currentIndex++; + GroupId = groupEntity.Id, + DisplayName = groupEntity.DisplayName, + Description = groupEntity.Description, + Mail = groupEntity.Mail, + MailNickname = groupEntity.MailNickname, + Visibility = groupEntity.Visibility + }; - if (currentIndex >= startIndex) + if (includeSite) + { + try { - var group = new UnifiedGroupEntity - { - GroupId = g.Id, - DisplayName = g.DisplayName, - Description = g.Description, - Mail = g.Mail, - MailNickname = g.MailNickname, - Visibility = g.Visibility - }; - - if (includeSite) - { - try - { - group.SiteUrl = GetUnifiedGroupSiteUrl(g.Id, accessToken); - } - catch (ServiceException e) - { - group.SiteUrl = e.Error.Message; - } - } - - if (includeClassification) - { - group.Classification = g.Classification; - } - - if (includeHasTeam) - { - group.HasTeam = HasTeamsTeam(group.GroupId, accessToken, azureEnvironment); - } - - groups.Add(group); + group.SiteUrl = GetUnifiedGroupSiteUrl(groupEntity.Id, accessToken); + } + catch (ODataError e) + { + group.SiteUrl = e.Error.Message; } } - if (pagedGroups.NextPageRequest != null && (endIndex == null || groups.Count < endIndex)) + if (includeClassification) { - pagedGroups = await pagedGroups.NextPageRequest.GetAsync(); + group.Classification = groupEntity.Classification; } - else + + if (includeHasTeam) { - break; + group.HasTeam = HasTeamsTeam(group.GroupId, accessToken, azureEnvironment); } - } + + groups.Add(group); + return true; + }); + + await pageIterator.IterateAsync(); return (groups); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1135,8 +984,8 @@ public static List GetUnifiedGroups(string accessToken, public static List GetUnifiedGroupMembers(UnifiedGroupEntity group, string accessToken, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { List unifiedGroupUsers = null; - List unifiedGroupGraphUsers = null; - IGroupMembersCollectionWithReferencesPage groupUsers = null; + List unifiedGroupGraphUsers = new List(); + if (String.IsNullOrEmpty(accessToken)) { @@ -1154,51 +1003,34 @@ public static List GetUnifiedGroupMembers(UnifiedGroupEntity g var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); // Get the members of an Office 365 group. - groupUsers = await graphClient.Groups[group.GroupId].Members.Request().GetAsync(); - if (groupUsers.CurrentPage != null && groupUsers.CurrentPage.Count > 0) - { - unifiedGroupGraphUsers = new List(); + var groupUsers = await graphClient.Groups[group.GroupId].Members.GetAsync(); - GenerateGraphUserCollection(groupUsers.CurrentPage, unifiedGroupGraphUsers); - } - - // Retrieve users when the results are paged. - while (groupUsers.NextPageRequest != null) + var pageIterator = PageIterator.CreatePageIterator(graphClient, groupUsers, (unifiedGroupGraphUser) => { - groupUsers = groupUsers.NextPageRequest.GetAsync().GetAwaiter().GetResult(); - if (groupUsers.CurrentPage != null && groupUsers.CurrentPage.Count > 0) + var usr = unifiedGroupGraphUser as User; + UnifiedGroupUser groupUser = new UnifiedGroupUser { - GenerateGraphUserCollection(groupUsers.CurrentPage, unifiedGroupGraphUsers); - } - } + Id = usr.Id, + UserPrincipalName = usr.UserPrincipalName != null ? usr.UserPrincipalName : string.Empty, + DisplayName = usr.DisplayName != null ? usr.DisplayName : string.Empty, + GivenName = usr.GivenName != null ? usr.GivenName : string.Empty, + Surname = usr.Surname != null ? usr.Surname : string.Empty, + Email = usr.Mail != null ? usr.Mail : string.Empty, + MobilePhone = usr.MobilePhone != null ? usr.DisplayName : string.Empty, + PreferredLanguage = usr.PreferredLanguage != null ? usr.PreferredLanguage : string.Empty, + JobTitle = usr.JobTitle != null ? usr.DisplayName : string.Empty, + BusinessPhones = usr.BusinessPhones != null ? usr.BusinessPhones.ToArray() : null + }; + unifiedGroupUsers.Add(groupUser); + return true; + }); - // Create the collection of type OfficeDevPnP 'UnifiedGroupUser' after all users are retrieved, including paged data. - if (unifiedGroupGraphUsers != null && unifiedGroupGraphUsers.Count > 0) - { - unifiedGroupUsers = new List(); - foreach (User usr in unifiedGroupGraphUsers) - { - UnifiedGroupUser groupUser = new UnifiedGroupUser - { - Id = usr.Id, - UserPrincipalName = usr.UserPrincipalName != null ? usr.UserPrincipalName : string.Empty, - DisplayName = usr.DisplayName != null ? usr.DisplayName : string.Empty, - GivenName = usr.GivenName != null ? usr.GivenName : string.Empty, - Surname = usr.Surname != null ? usr.Surname : string.Empty, - Email = usr.Mail != null ? usr.Mail : string.Empty, - MobilePhone = usr.MobilePhone != null ? usr.DisplayName : string.Empty, - PreferredLanguage = usr.PreferredLanguage != null ? usr.PreferredLanguage : string.Empty, - JobTitle = usr.JobTitle != null ? usr.DisplayName : string.Empty, - BusinessPhones = usr.BusinessPhones != null ? usr.BusinessPhones.ToArray() : null - }; - unifiedGroupUsers.Add(groupUser); - } - } + await pageIterator.IterateAsync(); return unifiedGroupUsers; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1218,8 +1050,7 @@ public static List GetUnifiedGroupMembers(UnifiedGroupEntity g public static List GetNestedUnifiedGroupMembers(UnifiedGroupEntity group, string accessToken, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { List unifiedGroupUsers = new List(); - List unifiedGroupGraphUsers = null; - IGroupMembersCollectionWithReferencesPage groupUsers = null; + List unifiedGroupGraphUsers = new List(); if (String.IsNullOrEmpty(accessToken)) { @@ -1237,50 +1068,34 @@ public static List GetNestedUnifiedGroupMembers(UnifiedGroupEn var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); // Get the members of an Office 365 group. - groupUsers = await graphClient.Groups[group.GroupId].Members.Request().GetAsync(); - if (groupUsers.CurrentPage != null && groupUsers.CurrentPage.Count > 0) - { - unifiedGroupGraphUsers = new List(); + var groupUsers = await graphClient.Groups[group.GroupId].Members.GetAsync(); - GenerateNestedGraphUserCollection(groupUsers.CurrentPage, unifiedGroupGraphUsers, unifiedGroupUsers, accessToken); - } - - // Retrieve users when the results are paged. - while (groupUsers.NextPageRequest != null) + var pageIterator = PageIterator.CreatePageIterator(graphClient, groupUsers, (unifiedGroupGraphUser) => { - groupUsers = groupUsers.NextPageRequest.GetAsync().GetAwaiter().GetResult(); - if (groupUsers.CurrentPage != null && groupUsers.CurrentPage.Count > 0) + var usr = unifiedGroupGraphUser as User; + UnifiedGroupUser groupUser = new UnifiedGroupUser { - GenerateNestedGraphUserCollection(groupUsers.CurrentPage, unifiedGroupGraphUsers, unifiedGroupUsers, accessToken); - } - } + Id = usr.Id, + UserPrincipalName = usr.UserPrincipalName != null ? usr.UserPrincipalName : string.Empty, + DisplayName = usr.DisplayName != null ? usr.DisplayName : string.Empty, + GivenName = usr.GivenName != null ? usr.GivenName : string.Empty, + Surname = usr.Surname != null ? usr.Surname : string.Empty, + Email = usr.Mail != null ? usr.Mail : string.Empty, + MobilePhone = usr.MobilePhone != null ? usr.DisplayName : string.Empty, + PreferredLanguage = usr.PreferredLanguage != null ? usr.PreferredLanguage : string.Empty, + JobTitle = usr.JobTitle != null ? usr.DisplayName : string.Empty, + BusinessPhones = usr.BusinessPhones != null ? usr.BusinessPhones.ToArray() : null + }; + unifiedGroupUsers.Add(groupUser); + return true; + }); - // Create the collection of type OfficeDevPnP 'UnifiedGroupUser' after all users are retrieved, including paged data. - if (unifiedGroupGraphUsers != null && unifiedGroupGraphUsers.Count > 0) - { - foreach (User usr in unifiedGroupGraphUsers) - { - UnifiedGroupUser groupUser = new UnifiedGroupUser - { - Id = usr.Id, - UserPrincipalName = usr.UserPrincipalName != null ? usr.UserPrincipalName : string.Empty, - DisplayName = usr.DisplayName != null ? usr.DisplayName : string.Empty, - GivenName = usr.GivenName != null ? usr.GivenName : string.Empty, - Surname = usr.Surname != null ? usr.Surname : string.Empty, - Email = usr.Mail != null ? usr.Mail : string.Empty, - MobilePhone = usr.MobilePhone != null ? usr.DisplayName : string.Empty, - PreferredLanguage = usr.PreferredLanguage != null ? usr.PreferredLanguage : string.Empty, - JobTitle = usr.JobTitle != null ? usr.DisplayName : string.Empty, - BusinessPhones = usr.BusinessPhones != null ? usr.BusinessPhones.ToArray() : null - }; - unifiedGroupUsers.Add(groupUser); - } - } + await pageIterator.IterateAsync(); return unifiedGroupUsers; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1311,11 +1126,11 @@ public static void AddUnifiedGroupOwners(string groupId, string[] owners, string { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - await UpdateOwners(owners, graphClient, groupId, removeExistingOwners); + await UpdateOwners(owners, graphClient, groupId, removeExistingOwners, azureEnvironment); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1345,11 +1160,11 @@ public static void AddUnifiedGroupMembers(string groupId, string[] members, stri { var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - await UpdateMembers(members, graphClient, groupId, removeExistingMembers); + await UpdateMembers(members, graphClient, groupId, removeExistingMembers, azureEnvironment); }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1382,20 +1197,21 @@ public static void RemoveUnifiedGroupMembers(string groupId, string[] members, s { // Search for the user object var memberQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'") - .GetAsync(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'"; + }); - var member = memberQuery.FirstOrDefault(); + var member = memberQuery.Value.FirstOrDefault(); if (member != null) { try { // If it is not in the list of current members, just remove it - await graphClient.Groups[groupId].Members[member.Id].Reference.Request().DeleteAsync(); + await graphClient.Groups[groupId].Members[member.Id].Ref.DeleteAsync(); } - catch (ServiceException ex) + catch (ODataError ex) { if (ex.Error.Code == "Request_BadRequest") { @@ -1411,7 +1227,7 @@ public static void RemoveUnifiedGroupMembers(string groupId, string[] members, s }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1444,20 +1260,21 @@ public static void RemoveUnifiedGroupOwners(string groupId, string[] owners, str { // Search for the user object var memberQuery = await graphClient.Users - .Request() - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'") - .GetAsync(); - - var member = memberQuery.FirstOrDefault(); + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(m.Replace("'", "''"))}'"; + }); + + var member = memberQuery?.Value?.FirstOrDefault(); if (member != null) { try { // If it is not in the list of current owners, just remove it - await graphClient.Groups[groupId].Owners[member.Id].Reference.Request().DeleteAsync(); + await graphClient.Groups[groupId].Owners[member.Id].Ref.DeleteAsync(); } - catch (ServiceException ex) + catch (ODataError ex) { if (ex.Error.Code == "Request_BadRequest") { @@ -1473,7 +1290,7 @@ public static void RemoveUnifiedGroupOwners(string groupId, string[] owners, str }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1500,7 +1317,7 @@ public static void ClearUnifiedGroupOwners(string groupId, string accessToken, i var currentOwners = GetUnifiedGroupOwners(new UnifiedGroupEntity { GroupId = groupId }, accessToken, retryCount, delay, azureEnvironment); RemoveUnifiedGroupOwners(groupId, currentOwners.Select(o => o.UserPrincipalName).ToArray(), accessToken, retryCount, delay, azureEnvironment); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1527,7 +1344,7 @@ public static void ClearUnifiedGroupMembers(string groupId, string accessToken, var currentMembers = GetUnifiedGroupMembers(new UnifiedGroupEntity { GroupId = groupId }, accessToken, retryCount, delay, azureEnvironment); RemoveUnifiedGroupMembers(groupId, currentMembers.Select(o => o.UserPrincipalName).ToArray(), accessToken, retryCount, delay, azureEnvironment); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1546,8 +1363,7 @@ public static void ClearUnifiedGroupMembers(string groupId, string accessToken, public static List GetUnifiedGroupOwners(UnifiedGroupEntity group, string accessToken, int retryCount = 10, int delay = 500, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { List unifiedGroupUsers = null; - List unifiedGroupGraphUsers = null; - IGroupOwnersCollectionWithReferencesPage groupUsers = null; + List unifiedGroupGraphUsers = new List(); if (String.IsNullOrEmpty(accessToken)) { @@ -1558,53 +1374,37 @@ public static List GetUnifiedGroupOwners(UnifiedGroupEntity gr { var result = Task.Run(async () => { - var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); - + var graphClient = CreateGraphClient(accessToken, retryCount, delay, azureEnvironment); // Get the owners of an Office 365 group. - groupUsers = await graphClient.Groups[group.GroupId].Owners.Request().GetAsync(); - if (groupUsers.CurrentPage != null && groupUsers.CurrentPage.Count > 0) - { - unifiedGroupGraphUsers = new List(); - GenerateGraphUserCollection(groupUsers.CurrentPage, unifiedGroupGraphUsers); - } + var groupUsers = await graphClient.Groups[group.GroupId].Owners.GetAsync(); - // Retrieve users when the results are paged. - while (groupUsers.NextPageRequest != null) + var pageIterator = PageIterator.CreatePageIterator(graphClient, groupUsers, (unifiedGroupGraphUser) => { - groupUsers = groupUsers.NextPageRequest.GetAsync().GetAwaiter().GetResult(); - if (groupUsers.CurrentPage != null && groupUsers.CurrentPage.Count > 0) + var unifiedGroupUser = unifiedGroupGraphUser as User; + UnifiedGroupUser groupUser = new UnifiedGroupUser { - GenerateGraphUserCollection(groupUsers.CurrentPage, unifiedGroupGraphUsers); - } - } + Id = unifiedGroupUser.Id, + UserPrincipalName = unifiedGroupUser.UserPrincipalName != null ? unifiedGroupUser.UserPrincipalName : string.Empty, + DisplayName = unifiedGroupUser.DisplayName != null ? unifiedGroupUser.DisplayName : string.Empty, + GivenName = unifiedGroupUser.GivenName != null ? unifiedGroupUser.GivenName : string.Empty, + Surname = unifiedGroupUser.Surname != null ? unifiedGroupUser.Surname : string.Empty, + Email = unifiedGroupUser.Mail != null ? unifiedGroupUser.Mail : string.Empty, + MobilePhone = unifiedGroupUser.MobilePhone != null ? unifiedGroupUser.DisplayName : string.Empty, + PreferredLanguage = unifiedGroupUser.PreferredLanguage != null ? unifiedGroupUser.PreferredLanguage : string.Empty, + JobTitle = unifiedGroupUser.JobTitle != null ? unifiedGroupUser.DisplayName : string.Empty, + BusinessPhones = unifiedGroupUser.BusinessPhones != null ? unifiedGroupUser.BusinessPhones.ToArray() : null + }; + unifiedGroupUsers.Add(groupUser); + return true; + }); + + await pageIterator.IterateAsync(); - // Create the collection of type OfficeDevPnP 'UnifiedGroupUser' after all users are retrieved, including paged data. - if (unifiedGroupGraphUsers != null && unifiedGroupGraphUsers.Count > 0) - { - unifiedGroupUsers = new List(); - foreach (User usr in unifiedGroupGraphUsers) - { - UnifiedGroupUser groupUser = new UnifiedGroupUser - { - Id = usr.Id, - UserPrincipalName = usr.UserPrincipalName != null ? usr.UserPrincipalName : string.Empty, - DisplayName = usr.DisplayName != null ? usr.DisplayName : string.Empty, - GivenName = usr.GivenName != null ? usr.GivenName : string.Empty, - Surname = usr.Surname != null ? usr.Surname : string.Empty, - Email = usr.Mail != null ? usr.Mail : string.Empty, - MobilePhone = usr.MobilePhone != null ? usr.DisplayName : string.Empty, - PreferredLanguage = usr.PreferredLanguage != null ? usr.PreferredLanguage : string.Empty, - JobTitle = usr.JobTitle != null ? usr.DisplayName : string.Empty, - BusinessPhones = usr.BusinessPhones != null ? usr.BusinessPhones.ToArray() : null - }; - unifiedGroupUsers.Add(groupUser); - } - } return unifiedGroupUsers; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; @@ -1714,19 +1514,20 @@ private static List GetUsers(GraphServiceClient graphClient, string[] grou try { // Search for the user object - IGraphServiceUsersCollectionPage userQuery = await graphClient.Users - .Request() - .Select("Id") - .Filter($"userPrincipalName eq '{Uri.EscapeDataString(groupUser.Replace("'", "''"))}'") - .GetAsync(); + var userQuery = await graphClient.Users + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = new string[] { "Id" }; + requestConfiguration.QueryParameters.Filter = $"userPrincipalName eq '{Uri.EscapeDataString(groupUser.Replace("'", "''"))}'"; + }); - User user = userQuery.FirstOrDefault(); + User user = userQuery?.Value.FirstOrDefault(); if (user != null) { usersResult.Add(user); } } - catch (ServiceException) + catch (ODataError) { // skip, group provisioning shouldnt stop because of error in user object } @@ -1773,7 +1574,7 @@ public static string GetGroupClassification(string groupId, string accessToken, } } - catch (ServiceException e) + catch (ODataError e) { classification = e.Error.Message; } @@ -1821,7 +1622,7 @@ public static bool HasTeamsTeam(string groupId, string accessToken, AzureEnviron } } } - catch (ServiceException ex) + catch (ODataError ex) { Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); throw; diff --git a/src/lib/PnP.Framework/Graph/UsersUtility.cs b/src/lib/PnP.Framework/Graph/UsersUtility.cs index e061d9d2f..d98418810 100644 --- a/src/lib/PnP.Framework/Graph/UsersUtility.cs +++ b/src/lib/PnP.Framework/Graph/UsersUtility.cs @@ -1,4 +1,6 @@ using Microsoft.Graph; +using Microsoft.Graph.Models; +using Microsoft.Kiota.Abstractions; using Newtonsoft.Json; using PnP.Framework.Diagnostics; using System; @@ -114,55 +116,33 @@ public static Model.User GetUser(string accessToken, string userPrincipalName, s var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, useBetaEndPoint: useBetaEndPoint); - IGraphServiceUsersCollectionPage pagedUsers; - // Retrieve the first batch of users. 999 is the maximum amount of users that Graph allows to be trieved in 1 go. Use maximum size batches to lessen the chance of throttling when retrieving larger amounts of users. - pagedUsers = await graphClient.Users.Request() - .Select(string.Join(",", propertiesToSelect)) - .Filter(filter) - .OrderBy(orderby) - .Top(!endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value) - .GetAsync(); - - int pageCount = 0; - int currentIndex = 0; - - while (true) + var pagedUsers = await graphClient.Users + .GetAsync(requestConfiguration => + { + requestConfiguration.QueryParameters.Select = propertiesToSelect.ToArray(); + requestConfiguration.QueryParameters.Filter = filter; + requestConfiguration.QueryParameters.Orderby = new string[] { orderby }; + requestConfiguration.QueryParameters.Top = !endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value; + }); + + var pageIterator = PageIterator.CreatePageIterator(graphClient, pagedUsers, (graphUser) => { - pageCount++; - - foreach (var pagedUser in pagedUsers) + if(graphUser != null) { - currentIndex++; - - if(endIndex.HasValue && endIndex.Value < currentIndex) - { - break; - } - - if (currentIndex >= startIndex) - { - users.Add(MapUserEntity(pagedUser, selectProperties)); - } + users.Add(MapUserEntity(graphUser, selectProperties)); } + return true; + }); - if (pagedUsers.NextPageRequest != null && (!endIndex.HasValue || currentIndex < endIndex.Value)) - { - // Retrieve the next batch of users. The possible oData instructions such as select and filter are already incorporated in the nextLink provided by Graph and thus do not need to be specified again. - pagedUsers = await pagedUsers.NextPageRequest.GetAsync(); - } - else - { - break; - } - } + await pageIterator.IterateAsync(); return users; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (Exception ex) { - Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); + Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Message); throw; } return result; @@ -205,13 +185,6 @@ public static Model.UserDelta ListUserDelta(string accessToken, string deltaToke } } - var queryOptions = new List(); - - if(!string.IsNullOrWhiteSpace(deltaToken)) - { - queryOptions.Add(new QueryOption("$skiptoken", deltaToken)); - } - Model.UserDelta result = null; try { @@ -223,16 +196,22 @@ public static Model.UserDelta ListUserDelta(string accessToken, string deltaToke var graphClient = GraphUtility.CreateGraphClient(accessToken, retryCount, delay, useBetaEndPoint: useBetaEndPoint); - IUserDeltaCollectionPage pagedUsers; - // Retrieve the first batch of users. 999 is the maximum amount of users that Graph allows to be trieved in 1 go. Use maximum size batches to lessen the chance of throttling when retrieving larger amounts of users. - pagedUsers = await graphClient.Users.Delta() - .Request(queryOptions) - .Select(string.Join(",", propertiesToSelect)) - .Filter(filter) - .OrderBy(orderby) - .Top(!endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value) - .GetAsync(); + var pagedUsers = graphClient.Users.Delta + .ToGetRequestInformation((requestConfiguration) => + { + requestConfiguration.QueryParameters.Select = propertiesToSelect.ToArray(); + requestConfiguration.QueryParameters.Filter = filter; + requestConfiguration.QueryParameters.Orderby = new string[] { orderby }; + requestConfiguration.QueryParameters.Top = !endIndex.HasValue ? 999 : endIndex.Value >= 999 ? 999 : endIndex.Value; + }); + + if (!string.IsNullOrEmpty(deltaToken)) + { + pagedUsers.QueryParameters.Add("%24skiptoken", deltaToken); + } + + var result = await graphClient.RequestAdapter.SendAsync(pagedUsers, UserCollectionResponse.CreateFromDiscriminatorValue); int pageCount = 0; int currentIndex = 0; @@ -282,9 +261,9 @@ public static Model.UserDelta ListUserDelta(string accessToken, string deltaToke return usersDelta; }).GetAwaiter().GetResult(); } - catch (ServiceException ex) + catch (Exception ex) { - Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); + Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Message); throw; } return result; @@ -395,9 +374,9 @@ public static Model.TemporaryAccessPassResponse RequestTemporaryAccessPass(strin return accessPassResponse; } - catch (ServiceException ex) + catch (Exception ex) { - Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message); + Log.Error(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Message); throw; } } diff --git a/src/lib/PnP.Framework/PnP.Framework.csproj b/src/lib/PnP.Framework/PnP.Framework.csproj index fca84af58..2e8f81b24 100644 --- a/src/lib/PnP.Framework/PnP.Framework.csproj +++ b/src/lib/PnP.Framework/PnP.Framework.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net6.0;net7.0 + net6.0;net7.0 10.0 PnP.Framework PnP.Framework @@ -185,21 +185,6 @@ SR.Designer.cs - - - - - - - - - - - - - - - @@ -238,24 +223,15 @@ - - + + - - - - - - $(PnPCoreSdkPath) - true - false - - + diff --git a/src/lib/PnP.Framework/Provisioning/ObjectHandlers/TokenParser.cs b/src/lib/PnP.Framework/Provisioning/ObjectHandlers/TokenParser.cs index ddd0667d3..8ce4a2a66 100644 --- a/src/lib/PnP.Framework/Provisioning/ObjectHandlers/TokenParser.cs +++ b/src/lib/PnP.Framework/Provisioning/ObjectHandlers/TokenParser.cs @@ -1,4 +1,5 @@ -using Microsoft.Online.SharePoint.TenantAdministration; +using Microsoft.Graph.Models.ODataErrors; +using Microsoft.Online.SharePoint.TenantAdministration; using Microsoft.SharePoint.Client; using Microsoft.SharePoint.Client.Taxonomy; using Newtonsoft.Json; @@ -502,7 +503,7 @@ private void AddGroupTokens(Web web, ProvisioningTemplateApplyingInformation app } } } - catch (Microsoft.Graph.ServiceException ex) + catch (ODataError ex) { // If we don't have permission to access the O365 groups, just skip it Log.Warning(Constants.LOGGING_SOURCE, CoreResources.GraphExtensions_ErrorOccured, ex.Error.Message);