Skip to content

Commit 79c5f20

Browse files
committed
Groups Overage Claim Processing
1 parent 6d6c16f commit 79c5f20

16 files changed

+253
-663
lines changed

5-WebApp-AuthZ/5-2-Groups/Controllers/UserProfileController.cs

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
using Microsoft.AspNetCore.Authorization;
2-
using Microsoft.AspNetCore.Mvc;
1+
using Microsoft.AspNetCore.Mvc;
32
using Microsoft.Graph;
43
using Microsoft.Identity.Web;
5-
using System.Collections.Generic;
64
using System.Threading.Tasks;
7-
using WebApp_OpenIDConnect_DotNet.Services.MicrosoftGraph;
85
using Constants = WebApp_OpenIDConnect_DotNet.Infrastructure.Constants;
96

107
namespace WebApp_OpenIDConnect_DotNet.Controllers
@@ -13,39 +10,28 @@ namespace WebApp_OpenIDConnect_DotNet.Controllers
1310
//[Authorize(Roles = "8873daa2-17af-4e72-973e-930c94ef7549")]
1411
public class UserProfileController : Controller
1512
{
16-
private readonly ITokenAcquisition tokenAcquisition;
17-
private readonly IMSGraphService graphService;
13+
private readonly GraphServiceClient graphServiceClient;
1814

19-
public UserProfileController(ITokenAcquisition tokenAcquisition, IMSGraphService MSGraphService)
15+
public UserProfileController(GraphServiceClient graphServiceClient)
2016
{
21-
this.tokenAcquisition = tokenAcquisition;
22-
this.graphService = MSGraphService;
17+
this.graphServiceClient= graphServiceClient;
2318
}
2419

25-
[AuthorizeForScopes(Scopes = new[] { Constants.ScopeUserRead, Constants.ScopeDirectoryReadAll })]
20+
[AuthorizeForScopes(Scopes = new[] { Constants.ScopeUserRead })]
2621
public async Task<IActionResult> Index()
2722
{
28-
// This is how group ids/names are used in the IsInRole method
29-
// var isinrole = User.IsInRole("8873daa2-17af-4e72-973e-930c94ef7549");
30-
31-
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { Constants.ScopeUserRead, Constants.ScopeDirectoryReadAll });
32-
33-
User me = await graphService.GetMeAsync(accessToken);
23+
User me = await graphServiceClient.Me.Request().GetAsync();
3424
ViewData["Me"] = me;
3525

3626
try
3727
{
38-
var photo = await graphService.GetMyPhotoAsync(accessToken);
28+
var photo = await graphServiceClient.Me.Photo.Request().GetAsync();
3929
ViewData["Photo"] = photo;
4030
}
4131
catch
4232
{
4333
//swallow
4434
}
45-
46-
IList<Group> groups = await graphService.GetMyMemberOfGroupsAsync(accessToken);
47-
ViewData["Groups"] = groups;
48-
4935
return View();
5036
}
5137
}

5-WebApp-AuthZ/5-2-Groups/Infrastructure/Constants.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ namespace WebApp_OpenIDConnect_DotNet.Infrastructure
33
public static class Constants
44
{
55
public const string ScopeUserRead = "User.Read";
6-
public const string ScopeDirectoryReadAll = "GroupMember.Read.All";
76

87
public const string BearerAuthorizationScheme = "Bearer";
98
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Graph;
8+
using Microsoft.Identity.Web;
9+
10+
namespace WebAppCallsMicrosoftGraph
11+
{
12+
public static class MicrosoftGraphServiceExtensions
13+
{
14+
/// <summary>
15+
/// Adds the Microsoft Graph client as a singleton.
16+
/// </summary>
17+
/// <param name="services">Service collection.</param>
18+
/// <param name="configuration">Configuration for Microsoft Graph.</param>
19+
/// <param name="initialScopes">Initial scopes.</param>
20+
/// <param name="graphBaseUrlKey">Base URL for Microsoft graph. This can be
21+
/// changed for instance for applications running in national clouds</param>
22+
public static void AddMicrosoftGraph(this IServiceCollection services,
23+
IConfiguration configuration,
24+
IEnumerable<string> initialScopes,
25+
string graphBaseUrlKey = "MicrosoftGraphBaseUrl")
26+
{
27+
// Graph base URL
28+
string graphBaseUrl = configuration.GetValue<string>(graphBaseUrlKey);
29+
30+
services.AddTokenAcquisition(true);
31+
services.AddSingleton<GraphServiceClient, GraphServiceClient>(serviceProvider =>
32+
{
33+
var tokenAquisitionService = serviceProvider.GetService<ITokenAcquisition>();
34+
GraphServiceClient client = string.IsNullOrWhiteSpace(graphBaseUrl) ?
35+
new GraphServiceClient(new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes)) :
36+
new GraphServiceClient(graphBaseUrl, new TokenAcquisitionCredentialProvider(tokenAquisitionService, initialScopes));
37+
return client;
38+
});
39+
}
40+
}
41+
}

5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/Bootstrapper.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/GraphApiOperationService.cs

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.Graph;
3+
using System;
4+
using System.Collections.Generic;
5+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using System.IdentityModel.Tokens.Jwt;
8+
using System.Security.Claims;
9+
using System.Linq;
10+
11+
namespace WebApp_OpenIDConnect_DotNet.Services.GroupProcessing
12+
{
13+
public class GraphHelper
14+
{
15+
/// <summary>
16+
/// Adds groups claim for group overage
17+
/// </summary>
18+
/// <param name="context">TokenValidatedContext</param>
19+
public static async Task ProcessGroupsClaimforAccessToken(TokenValidatedContext context)
20+
{
21+
try
22+
{
23+
//Checks if the token contains 'Group Overage' Claim.
24+
if (context.Principal.Claims.Any(x => x.Type == "hasgroups" || (x.Type == "_claim_names" && x.Value == "{\"groups\":\"src1\"}")))
25+
{
26+
//This API should have permission set for Microsoft graph: 'GroupMember.Read.All'
27+
var graph = context.HttpContext.RequestServices.GetService<GraphServiceClient>();
28+
29+
if (graph == null)
30+
{
31+
Console.WriteLine("No service for type 'Microsoft.Graph.GraphServiceClient' has been registered.");
32+
}
33+
else if (context.SecurityToken != null)
34+
{
35+
if (!context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI"))
36+
{
37+
//Added current access token in below key to get Access Token on-behalf of user.
38+
context.HttpContext.Items.Add("JwtSecurityTokenUsedToCallWebAPI", context.SecurityToken as JwtSecurityToken);
39+
}
40+
//Specify the property names in the 'select' variable to get values for the specified properties.
41+
string select = "id,displayName,onPremisesNetBiosName,onPremisesDomainName,onPremisesSamAccountNameonPremisesSecurityIdentifier";
42+
43+
//Request to get groups and directory roles that the user is a direct member of.
44+
var memberPage = await graph.Me.MemberOf.Request().Select(select).GetAsync().ConfigureAwait(false);
45+
46+
if (memberPage?.Count > 0)
47+
{
48+
//There is a limit to number of groups returned, below method make calls to Microsoft graph to get all the groups.
49+
var allgroups = ProcessIGraphServiceMemberOfCollectionPage(memberPage);
50+
51+
if (allgroups?.Count > 0)
52+
{
53+
var identity = (ClaimsIdentity)context.Principal.Identity;
54+
55+
if (identity != null)
56+
{
57+
//Remove existing groups claims
58+
RemoveExistingClaims(context, identity);
59+
60+
List<Claim> groupClaims = new List<Claim>();
61+
62+
foreach (Group group in allgroups)
63+
{
64+
//Claim is added in list and it can be used by saving the groups in session or as per project implementation.
65+
//Adds group id as 'groups' claim. But it can be changed as per requirment.
66+
//For instance if the required format is 'NetBIOSDomain\sAMAccountName' then the code is as commented below:
67+
//groupClaims.AddClaim(new Claim("groups", group.OnPremisesNetBiosName+"\\"+group.OnPremisesSamAccountName));
68+
groupClaims.Add(new Claim("groups", group.Id));
69+
}
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
catch (Exception ex)
77+
{
78+
Console.WriteLine(ex.Message);
79+
}
80+
finally
81+
{
82+
if (context.HttpContext.Items.ContainsKey("JwtSecurityTokenUsedToCallWebAPI"))
83+
{
84+
//Remove the key as Microsoft.Identity.Web library utilizes this key.
85+
//If not removed then it can cause failure to the application.
86+
context.HttpContext.Items.Remove("JwtSecurityTokenUsedToCallWebAPI");
87+
}
88+
}
89+
}
90+
91+
/// <summary>
92+
/// Remove groups claims if already exists.
93+
/// </summary>
94+
/// <param name="context"></param>
95+
/// <param name="identity"></param>
96+
private static void RemoveExistingClaims(TokenValidatedContext context, ClaimsIdentity identity)
97+
{
98+
//clear existing claim
99+
List<Claim> existingGroupsClaims = context.Principal.Claims.Where(x => x.Type == "groups").ToList();
100+
if (existingGroupsClaims?.Count > 0)
101+
{
102+
foreach (Claim groupsClaim in existingGroupsClaims)
103+
{
104+
identity.RemoveClaim(groupsClaim);
105+
}
106+
}
107+
}
108+
109+
/// <summary>
110+
/// Returns all the groups that the user is a direct member of.
111+
/// </summary>
112+
/// <param name="membersCollectionPage">First page having collection of directory roles and groups</param>
113+
/// <returns>List of groups</returns>
114+
private static List<Group> ProcessIGraphServiceMemberOfCollectionPage(IUserMemberOfCollectionWithReferencesPage membersCollectionPage)
115+
{
116+
List<Group> allGroups = new List<Group>();
117+
118+
try
119+
{
120+
if (membersCollectionPage != null)
121+
{
122+
do
123+
{
124+
// Page through results
125+
foreach (DirectoryObject directoryObject in membersCollectionPage.CurrentPage)
126+
{
127+
//Collection contains directory roles and groups of the user.
128+
//Checks and adds groups only to the list.
129+
if (directoryObject is Group)
130+
{
131+
allGroups.Add(directoryObject as Group);
132+
}
133+
}
134+
135+
// are there more pages (Has a @odata.nextLink ?)
136+
if (membersCollectionPage.NextPageRequest != null)
137+
{
138+
membersCollectionPage = membersCollectionPage.NextPageRequest.GetAsync().Result;
139+
}
140+
else
141+
{
142+
membersCollectionPage = null;
143+
}
144+
} while (membersCollectionPage != null);
145+
}
146+
}
147+
catch (ServiceException ex)
148+
{
149+
Console.WriteLine($"We could not process the groups list: {ex}");
150+
return null;
151+
}
152+
return allGroups;
153+
}
154+
}
155+
}

5-WebApp-AuthZ/5-2-Groups/Services/MicrosoftGraph-Rest/IGraphApiOperations.cs

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)