MS Graph SDK - 在一个请求中向用户授予多个 appRoleAssignments
MS Graph SDK - Grant multiple appRoleAssignments to a user in one request
正在此处查看 'appRoleAssignment' 的 MS 文档:Grant an appRoleAssignment to a user
代码示例显示将用户添加到单个应用程序角色,即每个图形请求分配一个角色:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var appRoleAssignment = new AppRoleAssignment
{
PrincipalId = Guid.Parse("principalId-value"),
ResourceId = Guid.Parse("resourceId-value"),
AppRoleId = Guid.Parse("appRoleId-value")
};
await graphClient.Users["{id}"].AppRoleAssignments
.Request()
.AddAsync(appRoleAssignment);
问。那么我们如何在单个请求中分配多个应用程序角色呢?
假设我们有一个 'Roles' WebbApp 页面,其中包含一个复选框列表,每个 AppRole 都有一个复选框,用户选择他们想要分配给用户的所有相关角色,然后点击保存按钮。我需要弄清楚我们如何一次性分配多个角色。在现实世界中,头脑正常的人不会为了为单个用户配置角色而点击保存按钮二十次。向 Graph 发送多个请求似乎不是预期的解决方案。
问。另一个难题是我们如何在同一请求期间添加和删除用户的 AppRole 分配?即复选框列表代表我们可能希望从用户成员资格中删除的角色,同时将它们添加到新的角色分配中。在我之前的项目中,我使用 Net Core 中的 Microsoft Identity 包很好地工作了这个原因和影响,但是尝试使用 Azure AD 实现相同的原因和影响并不是那么简单...
下图显示了给定用户的 AppRole 选择示例:
感谢
这或多或少适用于你的两个问题,我不完全确定这是否有效,但你可以 Json 图形的批处理端点将多个命令构建为 1 个批处理。 https://docs.microsoft.com/en-us/graph/json-batching
如果这不起作用,恐怕您将不得不多次循环调用图表。
对于第 2 季度,这假定您已经拥有当前角色列表,以便您可以针对特定角色分配专门调用删除端点。如果您没有当前角色列表,https://docs.microsoft.com/en-us/graph/api/serviceprincipal-get?view=graph-rest-1.0&tabs=http this is to get the entire app profile which includes the app roles, then use the user app role assignments endpoint https://docs.microsoft.com/en-us/graph/api/user-list-approleassignments?view=graph-rest-1.0&tabs=http 以获取分配给用户的角色列表。将他们匹配起来,看看他们目前属于哪些角色。
我已经尝试并测试了一个现在有效的解决方案,但由于 Stack Overflow 30,000 个字符的限制,我无法展示我最终所做的完整代码示例,但包含了一些片段下面。
我想要实现的目标的总结:
在 WebApp 中提供 UI,管理员可以 add/modify 将 AppRoleAssingments 分配给 Azure AD 用户,而无需登录到 Azure 门户本身。
使用 .NET Core 3.1 Razor Pages,我创建了一个包含复选框列表的页面,每个 AppRole 都有一个复选框。加载页面时,将检查用户当前所属的每个角色的 AppRoles。然后管理员可以选中或取消选中他们想要为该用户修改的任何角色,当点击保存按钮时,我们将更新的 AppRoles 列表发送回 Graph。
我使用 Javascript 和 Ajax 来 send/receive razor 页面和页面模型之间的数据。
第 1 步 - AJAX 调用 razor 页面模型,该模型又调用 MS Graph 并获取为 Azure AD 中的租户找到的所有 AppRoles 的列表。在同一方法中,我们然后处理对 Graph 的二次调用,以获取所选用户当前所属的 AppRoles 列表。使用 foreach 循环,我们查看用户当前属于哪个 AppRoles,并针对 'Assinged' = true 属性 标记我们 return 到剃刀页面的 AppRoles 列表。
第 2 步 - razor 页面填充了每个 AppRole 的复选框,并选中了用户当前所属的复选框。管理员在点击保存按钮之前为用户选中或取消选中 AppRoles,然后post将更新返回到 razor 页面模型。
第 3 步 - 在我们 post 更新回 MS Graph 之前,我们必须确定用户已经属于哪些 AppRoles,否则我们会在 MS Graph SDK 中遇到服务异常。
这是很难弄清楚的部分,但是在比较了用户已经拥有的内容与我们需要进行的更改之后,我们在 foreach 循环中将更新发送回 Graph,即我们只能添加或一次将用户移至 AppRoleAssignment。
下面的代码示例显示了 razor 页面模型位,不幸的是,razor 页面中的代码和帮助注释太多,无法挤进去。希望它能帮助其他人找到类似的解决方案。
Razor 页面模型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Identity.Web;
using WebApp_RAZOR.Models.Users;
using WebApp_RAZOR.Repository.Users;
using WebApp_RAZOR.Services;
using static WebApp_RAZOR.Repository.Users.CurrentUser;
using Constants = WebApp_RAZOR.Infrastructure.Constants;
namespace WebApp_RAZOR.Pages.AppRoles
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly ITokenAcquisition tokenAcquisition;
private readonly WebOptions webOptions;
public readonly ICurrentUser _currentUser;
public IndexModel(ILogger<IndexModel> logger,
ITokenAcquisition tokenAcquisition,
IOptions<WebOptions> webOptionValue,
ICurrentUser currentUser)
{
_logger = logger;
this.tokenAcquisition = tokenAcquisition;
this.webOptions = webOptionValue.Value;
_currentUser = currentUser;
}
[BindProperty]
public TenantUser TenantUser { get; set; }
[BindProperty]
public List<AppRoles> Roles { get; set; } // Stores the AppRoles
public class AppRoles
{
// Used only for the ajax POST request when sending
// the updated AppRoles list back to the razor page model.
public string UserId { get; set; }
// AppRole Id, see Azure App Manifest File.
// This is the unique ID of the specific application role
// that the assignment grants access to. A string of 0's
// means this is a default access assignment, and anything
// else means there has been a specific role defined by the
// application. This ID can be useful when performing management
// operations against this application using PowerShell or other
// programmatic interfaces.
public Guid? AppRoleId { get; set; }
// Friendly description
public string Description { get; set; }
// Friendly display name
public string DisplayName { get; set; }
// Same as displayName, no spaces
public string Value { get; set; }
// 'true' if the User is assinged the AppRole
// Use a string value as passing the data to Ajax
// will parse a boolean as a string anyway.
public string Assigned { get; set; }
}
// Stores the AppRoles that user already belongs to when querying the MS Graph
// Used in the 'OnPostAddAppRoleAsync' method.
[BindProperty]
public List<AppRoleAssingments> AppRolesAlreadyAssignedToUser { get; set; }
public class AppRoleAssingments
{
// AppRole Id, see Azure App Manifest File.
// This is the unique ID of the specific application role
// that the assignment grants access to. A string of 0's
// means this is a default access assignment, and anything
// else means there has been a specific role defined by the
// application. This ID can be useful when performing management
// operations against this application using PowerShell or other
// programmatic interfaces.
public Guid? AppRoleId { get; set; }
// This is the unique ID of this specific role assignment,
// which is a link between the user or group and the service
// principal object. This ID can be useful when performing
// management operations against this application using
// PowerShell or other programmatic interfaces.
public string AssingmentId { get; set; }
}
/// <summary>
/// Method is run when the razor page is first loaded.
/// The javascript window.onload function then initiates a call to the server using
/// 'OnGetFetchAppRolesAsync' method below to fetch the list of AppRoles
/// as well as the list of AppRoleAssignments for the currentley selected user.
/// </summary>
/// <param name="id"></param>
public void OnGet(string id)
{
// Get the User Id from the URL in the page request.
ViewData["MyRouteId"] = id;
}
/// <summary>
/// Methiod is called from Ajax POST and fetches the list of AppRoles
/// as well as the list of AppRoleAssignments for the currentley selected user.
/// </summary>
/// <param name="UserId"></param>
/// <returns></returns>
public async Task<IActionResult> OnGetFetchAppRolesAsync(string UserId)
{
Roles = new List<AppRoles>(); // Newup the Roles list.
// Get and instance of the graphClient.
GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserReadBasicAll });
try
{
//throw new Exception(); // Testing Only!
var serviceprincipals = await graphClient.ServicePrincipals["36463454-a184-3jf44j-b360-39573950385960"]
.Request()
.GetAsync();
var appRoles = serviceprincipals.AppRoles;
// Iterate through the list of AppRoles returned from MS Graph.
foreach (var role in appRoles)
{
// For each AppRole, add it to the Roles List.
Roles.Add(new AppRoles
{
AppRoleId = role.Id,
DisplayName = role.DisplayName,
Description = role.Description,
Value = role.Value,
// Mark 'Assinged' property as false for now, we'll
// check it against the user in next step.
Assigned = "false"
});
}
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
try
{
//throw new Exception(); // Testing Only!
// Get the list of AppRoles the currently selected user is a member of.
var appRoleAssignments = await graphClient.Users[UserId].AppRoleAssignments
.Request()
.GetAsync();
// For each AppRole the user is a member of, update the
// assigned property in the 'List<AppRoles> Roles' to true.
// When the razor page is returned, each AppRole the user
// is a member of will have the checkbox checked...
foreach (var role in appRoleAssignments)
{
var obj = Roles.FirstOrDefault(x => x.AppRoleId == role.AppRoleId);
if (obj != null) obj.Assigned = "true";
}
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
return new JsonResult(Roles);
}
/// <summary>
/// The method is called by Ajax, the JS code passes the list of the AppRoles from Razor to the page model.
/// We conduct a comparison against MS Graph to determine which AppRoles the user is currently a member of
/// and which ones they require to be removed.
/// </summary>
/// <param name="updatedRolesArrayFromRazorPage"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostAddAppRoleAsync([FromBody]List<AppRoles> updatedRolesArrayFromRazorPage)
{
// Get the first object set from the array received
// from JS AJAX Call and get the details of the user's id.
var firstElement = updatedRolesArrayFromRazorPage.First();
var userId = firstElement.UserId;
// Get and instance of the graphClient.
GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserReadBasicAll });
try
{
//throw new Exception(); // Testing Only!
// Get the list of AppRoles that the current user is a member of.
var appRoleAssignments = await graphClient.Users[userId].AppRoleAssignments
.Request()
.GetAsync();
// For each AppRole the user is a member of, add them to the AppRolesAlreadyAssignedToUser list.
foreach (var role in appRoleAssignments)
{
AppRolesAlreadyAssignedToUser.Add(new AppRoleAssingments
{
AppRoleId = role.AppRoleId,
// 'Assignment ID' blade found in AzureAd/UsersApplications/{thisWebAppName}/{AppRoleName}.
// Go to Azure Active Directory > Users > Select specific User > Applications > Select the
// application to navigate to "Assignment Details" blade.
AssingmentId = role.Id
});
}
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
// Now we have a list of both the AppRoles the current user is a memeber of,
// as well as the updated list of AppRoles that were posted from the Razor Page,
// we perform a comparison in the next code section below so we can determine
// which roles are already assigned i.e we dont need to add them via the MS Graph again
// otherwise we will encounter a ServiceException error advising us the role is already assigned.
// We then check which roles are already assigned to the user that now need to be removed.
// We can only add or remove roles from the user one at a time due to the limitations of MS Graph.
// Iterate through list of the AppRoles received from the Razor Page.
// Note each AppRole will have either a true or false value against the 'Assigned' property.
foreach (var role in updatedRolesArrayFromRazorPage)
{
// ------------------------------------------------------------------------
// Perform the comparison to see which AppRoles we need to add to the user.
// ------------------------------------------------------------------------
if (role.Assigned == "true") // Assigned status from AppRole checkbox selection in Razor Page.
{
// We do a comparison between the tow lists, if the role is not alread present in
// the list from the MS Graph then we know we need to assign the user to this AppRole.
bool exists = AppRolesAlreadyAssignedToUser.Any(r => r.AppRoleId == role.AppRoleId);
// If returns false the we will assign the user to this role via MS Graph.
if (exists == false)
{
// Declare the new appRoleAssingment.
var appRoleAssignment = new AppRoleAssignment
{
// principalId: The id of the user to whom you are assigning the app role.
PrincipalId = Guid.Parse(userId),
// resourceId: The id of the resource servicePrincipal that has defined the app role.
ResourceId = Guid.Parse("6g4656g54g46-a184-4f8a-b360-656h7567567h75"),
// appRoleId: The id of the appRole (defined on the resource service principal) to assign to the user.
AppRoleId = Guid.Parse(role.AppRoleId.ToString())
};
try
{
// Add the above AppRoleAssingment to the user.
await graphClient.Users[userId].AppRoleAssignments
.Request()
.AddAsync(appRoleAssignment);
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
}
}
// -----------------------------------------------------------------------------
// Perform the comparison to see which AppRoles we need to remove from the user.
// -----------------------------------------------------------------------------
else if (role.Assigned == "false")
{
var exists = AppRolesAlreadyAssignedToUser.FirstOrDefault(r => r.AppRoleId == role.AppRoleId);
if (exists != null) // Assigned status from AppRole checkbox selection in Razor Page.
{
var appRoleId = exists.AppRoleId;
var assignmentId = exists.AssingmentId;
try
{
await graphClient.Users[userId].AppRoleAssignments[assignmentId]
.Request()
.DeleteAsync();
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
}
}
}
return new JsonResult(new { Url = "users/index" });
}
private GraphServiceClient GetGraphServiceClient(string[] scopes)
{
return GraphServiceClientFactory.GetAuthenticatedGraphClient(async () =>
{
string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
return result;
}, webOptions.GraphApiUrl);
}
}
}
我们为当前选定的用户更新 AppRoles 的 Razor 页面:
正在此处查看 'appRoleAssignment' 的 MS 文档:Grant an appRoleAssignment to a user
代码示例显示将用户添加到单个应用程序角色,即每个图形请求分配一个角色:
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var appRoleAssignment = new AppRoleAssignment
{
PrincipalId = Guid.Parse("principalId-value"),
ResourceId = Guid.Parse("resourceId-value"),
AppRoleId = Guid.Parse("appRoleId-value")
};
await graphClient.Users["{id}"].AppRoleAssignments
.Request()
.AddAsync(appRoleAssignment);
问。那么我们如何在单个请求中分配多个应用程序角色呢?
假设我们有一个 'Roles' WebbApp 页面,其中包含一个复选框列表,每个 AppRole 都有一个复选框,用户选择他们想要分配给用户的所有相关角色,然后点击保存按钮。我需要弄清楚我们如何一次性分配多个角色。在现实世界中,头脑正常的人不会为了为单个用户配置角色而点击保存按钮二十次。向 Graph 发送多个请求似乎不是预期的解决方案。
问。另一个难题是我们如何在同一请求期间添加和删除用户的 AppRole 分配?即复选框列表代表我们可能希望从用户成员资格中删除的角色,同时将它们添加到新的角色分配中。在我之前的项目中,我使用 Net Core 中的 Microsoft Identity 包很好地工作了这个原因和影响,但是尝试使用 Azure AD 实现相同的原因和影响并不是那么简单...
下图显示了给定用户的 AppRole 选择示例:
感谢
这或多或少适用于你的两个问题,我不完全确定这是否有效,但你可以 Json 图形的批处理端点将多个命令构建为 1 个批处理。 https://docs.microsoft.com/en-us/graph/json-batching
如果这不起作用,恐怕您将不得不多次循环调用图表。
对于第 2 季度,这假定您已经拥有当前角色列表,以便您可以针对特定角色分配专门调用删除端点。如果您没有当前角色列表,https://docs.microsoft.com/en-us/graph/api/serviceprincipal-get?view=graph-rest-1.0&tabs=http this is to get the entire app profile which includes the app roles, then use the user app role assignments endpoint https://docs.microsoft.com/en-us/graph/api/user-list-approleassignments?view=graph-rest-1.0&tabs=http 以获取分配给用户的角色列表。将他们匹配起来,看看他们目前属于哪些角色。
我已经尝试并测试了一个现在有效的解决方案,但由于 Stack Overflow 30,000 个字符的限制,我无法展示我最终所做的完整代码示例,但包含了一些片段下面。
我想要实现的目标的总结:
在 WebApp 中提供 UI,管理员可以 add/modify 将 AppRoleAssingments 分配给 Azure AD 用户,而无需登录到 Azure 门户本身。
使用 .NET Core 3.1 Razor Pages,我创建了一个包含复选框列表的页面,每个 AppRole 都有一个复选框。加载页面时,将检查用户当前所属的每个角色的 AppRoles。然后管理员可以选中或取消选中他们想要为该用户修改的任何角色,当点击保存按钮时,我们将更新的 AppRoles 列表发送回 Graph。
我使用 Javascript 和 Ajax 来 send/receive razor 页面和页面模型之间的数据。
第 1 步 - AJAX 调用 razor 页面模型,该模型又调用 MS Graph 并获取为 Azure AD 中的租户找到的所有 AppRoles 的列表。在同一方法中,我们然后处理对 Graph 的二次调用,以获取所选用户当前所属的 AppRoles 列表。使用 foreach 循环,我们查看用户当前属于哪个 AppRoles,并针对 'Assinged' = true 属性 标记我们 return 到剃刀页面的 AppRoles 列表。
第 2 步 - razor 页面填充了每个 AppRole 的复选框,并选中了用户当前所属的复选框。管理员在点击保存按钮之前为用户选中或取消选中 AppRoles,然后post将更新返回到 razor 页面模型。
第 3 步 - 在我们 post 更新回 MS Graph 之前,我们必须确定用户已经属于哪些 AppRoles,否则我们会在 MS Graph SDK 中遇到服务异常。
这是很难弄清楚的部分,但是在比较了用户已经拥有的内容与我们需要进行的更改之后,我们在 foreach 循环中将更新发送回 Graph,即我们只能添加或一次将用户移至 AppRoleAssignment。
下面的代码示例显示了 razor 页面模型位,不幸的是,razor 页面中的代码和帮助注释太多,无法挤进去。希望它能帮助其他人找到类似的解决方案。
Razor 页面模型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Identity.Web;
using WebApp_RAZOR.Models.Users;
using WebApp_RAZOR.Repository.Users;
using WebApp_RAZOR.Services;
using static WebApp_RAZOR.Repository.Users.CurrentUser;
using Constants = WebApp_RAZOR.Infrastructure.Constants;
namespace WebApp_RAZOR.Pages.AppRoles
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly ITokenAcquisition tokenAcquisition;
private readonly WebOptions webOptions;
public readonly ICurrentUser _currentUser;
public IndexModel(ILogger<IndexModel> logger,
ITokenAcquisition tokenAcquisition,
IOptions<WebOptions> webOptionValue,
ICurrentUser currentUser)
{
_logger = logger;
this.tokenAcquisition = tokenAcquisition;
this.webOptions = webOptionValue.Value;
_currentUser = currentUser;
}
[BindProperty]
public TenantUser TenantUser { get; set; }
[BindProperty]
public List<AppRoles> Roles { get; set; } // Stores the AppRoles
public class AppRoles
{
// Used only for the ajax POST request when sending
// the updated AppRoles list back to the razor page model.
public string UserId { get; set; }
// AppRole Id, see Azure App Manifest File.
// This is the unique ID of the specific application role
// that the assignment grants access to. A string of 0's
// means this is a default access assignment, and anything
// else means there has been a specific role defined by the
// application. This ID can be useful when performing management
// operations against this application using PowerShell or other
// programmatic interfaces.
public Guid? AppRoleId { get; set; }
// Friendly description
public string Description { get; set; }
// Friendly display name
public string DisplayName { get; set; }
// Same as displayName, no spaces
public string Value { get; set; }
// 'true' if the User is assinged the AppRole
// Use a string value as passing the data to Ajax
// will parse a boolean as a string anyway.
public string Assigned { get; set; }
}
// Stores the AppRoles that user already belongs to when querying the MS Graph
// Used in the 'OnPostAddAppRoleAsync' method.
[BindProperty]
public List<AppRoleAssingments> AppRolesAlreadyAssignedToUser { get; set; }
public class AppRoleAssingments
{
// AppRole Id, see Azure App Manifest File.
// This is the unique ID of the specific application role
// that the assignment grants access to. A string of 0's
// means this is a default access assignment, and anything
// else means there has been a specific role defined by the
// application. This ID can be useful when performing management
// operations against this application using PowerShell or other
// programmatic interfaces.
public Guid? AppRoleId { get; set; }
// This is the unique ID of this specific role assignment,
// which is a link between the user or group and the service
// principal object. This ID can be useful when performing
// management operations against this application using
// PowerShell or other programmatic interfaces.
public string AssingmentId { get; set; }
}
/// <summary>
/// Method is run when the razor page is first loaded.
/// The javascript window.onload function then initiates a call to the server using
/// 'OnGetFetchAppRolesAsync' method below to fetch the list of AppRoles
/// as well as the list of AppRoleAssignments for the currentley selected user.
/// </summary>
/// <param name="id"></param>
public void OnGet(string id)
{
// Get the User Id from the URL in the page request.
ViewData["MyRouteId"] = id;
}
/// <summary>
/// Methiod is called from Ajax POST and fetches the list of AppRoles
/// as well as the list of AppRoleAssignments for the currentley selected user.
/// </summary>
/// <param name="UserId"></param>
/// <returns></returns>
public async Task<IActionResult> OnGetFetchAppRolesAsync(string UserId)
{
Roles = new List<AppRoles>(); // Newup the Roles list.
// Get and instance of the graphClient.
GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserReadBasicAll });
try
{
//throw new Exception(); // Testing Only!
var serviceprincipals = await graphClient.ServicePrincipals["36463454-a184-3jf44j-b360-39573950385960"]
.Request()
.GetAsync();
var appRoles = serviceprincipals.AppRoles;
// Iterate through the list of AppRoles returned from MS Graph.
foreach (var role in appRoles)
{
// For each AppRole, add it to the Roles List.
Roles.Add(new AppRoles
{
AppRoleId = role.Id,
DisplayName = role.DisplayName,
Description = role.Description,
Value = role.Value,
// Mark 'Assinged' property as false for now, we'll
// check it against the user in next step.
Assigned = "false"
});
}
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
try
{
//throw new Exception(); // Testing Only!
// Get the list of AppRoles the currently selected user is a member of.
var appRoleAssignments = await graphClient.Users[UserId].AppRoleAssignments
.Request()
.GetAsync();
// For each AppRole the user is a member of, update the
// assigned property in the 'List<AppRoles> Roles' to true.
// When the razor page is returned, each AppRole the user
// is a member of will have the checkbox checked...
foreach (var role in appRoleAssignments)
{
var obj = Roles.FirstOrDefault(x => x.AppRoleId == role.AppRoleId);
if (obj != null) obj.Assigned = "true";
}
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
return new JsonResult(Roles);
}
/// <summary>
/// The method is called by Ajax, the JS code passes the list of the AppRoles from Razor to the page model.
/// We conduct a comparison against MS Graph to determine which AppRoles the user is currently a member of
/// and which ones they require to be removed.
/// </summary>
/// <param name="updatedRolesArrayFromRazorPage"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostAddAppRoleAsync([FromBody]List<AppRoles> updatedRolesArrayFromRazorPage)
{
// Get the first object set from the array received
// from JS AJAX Call and get the details of the user's id.
var firstElement = updatedRolesArrayFromRazorPage.First();
var userId = firstElement.UserId;
// Get and instance of the graphClient.
GraphServiceClient graphClient = GetGraphServiceClient(new[] { Constants.ScopeUserReadBasicAll });
try
{
//throw new Exception(); // Testing Only!
// Get the list of AppRoles that the current user is a member of.
var appRoleAssignments = await graphClient.Users[userId].AppRoleAssignments
.Request()
.GetAsync();
// For each AppRole the user is a member of, add them to the AppRolesAlreadyAssignedToUser list.
foreach (var role in appRoleAssignments)
{
AppRolesAlreadyAssignedToUser.Add(new AppRoleAssingments
{
AppRoleId = role.AppRoleId,
// 'Assignment ID' blade found in AzureAd/UsersApplications/{thisWebAppName}/{AppRoleName}.
// Go to Azure Active Directory > Users > Select specific User > Applications > Select the
// application to navigate to "Assignment Details" blade.
AssingmentId = role.Id
});
}
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
// Now we have a list of both the AppRoles the current user is a memeber of,
// as well as the updated list of AppRoles that were posted from the Razor Page,
// we perform a comparison in the next code section below so we can determine
// which roles are already assigned i.e we dont need to add them via the MS Graph again
// otherwise we will encounter a ServiceException error advising us the role is already assigned.
// We then check which roles are already assigned to the user that now need to be removed.
// We can only add or remove roles from the user one at a time due to the limitations of MS Graph.
// Iterate through list of the AppRoles received from the Razor Page.
// Note each AppRole will have either a true or false value against the 'Assigned' property.
foreach (var role in updatedRolesArrayFromRazorPage)
{
// ------------------------------------------------------------------------
// Perform the comparison to see which AppRoles we need to add to the user.
// ------------------------------------------------------------------------
if (role.Assigned == "true") // Assigned status from AppRole checkbox selection in Razor Page.
{
// We do a comparison between the tow lists, if the role is not alread present in
// the list from the MS Graph then we know we need to assign the user to this AppRole.
bool exists = AppRolesAlreadyAssignedToUser.Any(r => r.AppRoleId == role.AppRoleId);
// If returns false the we will assign the user to this role via MS Graph.
if (exists == false)
{
// Declare the new appRoleAssingment.
var appRoleAssignment = new AppRoleAssignment
{
// principalId: The id of the user to whom you are assigning the app role.
PrincipalId = Guid.Parse(userId),
// resourceId: The id of the resource servicePrincipal that has defined the app role.
ResourceId = Guid.Parse("6g4656g54g46-a184-4f8a-b360-656h7567567h75"),
// appRoleId: The id of the appRole (defined on the resource service principal) to assign to the user.
AppRoleId = Guid.Parse(role.AppRoleId.ToString())
};
try
{
// Add the above AppRoleAssingment to the user.
await graphClient.Users[userId].AppRoleAssignments
.Request()
.AddAsync(appRoleAssignment);
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
}
}
// -----------------------------------------------------------------------------
// Perform the comparison to see which AppRoles we need to remove from the user.
// -----------------------------------------------------------------------------
else if (role.Assigned == "false")
{
var exists = AppRolesAlreadyAssignedToUser.FirstOrDefault(r => r.AppRoleId == role.AppRoleId);
if (exists != null) // Assigned status from AppRole checkbox selection in Razor Page.
{
var appRoleId = exists.AppRoleId;
var assignmentId = exists.AssingmentId;
try
{
await graphClient.Users[userId].AppRoleAssignments[assignmentId]
.Request()
.DeleteAsync();
}
catch (ServiceException ex)
{
// Get the current user properties from the httpcontext currentUser repository for logging.
CurrentUserProperties currentUser = (CurrentUserProperties)_currentUser.GetCurrentUser();
// Graph service exception friendly message.
var errorMessage = ex.Error.Message;
string logEventCategory = "Microsoft Graph";
string logEventType = "Service Exception";
string logEventSource = null;
string logUserId = currentUser.Id;
string logUserName = currentUser.Username;
string logForename = currentUser.Forename;
string logSurname = currentUser.Surname;
string logData = errorMessage;
_logger.LogError(ex,
"{@logEventCategory}" +
"{@logEventType}" +
"{@logEventSource}" +
"{@logUserId}" +
"{@logUsername}" +
"{@logForename}" +
"{@logSurname}" +
"{@logData}",
logEventCategory,
logEventType,
logEventSource,
logUserId,
logUserName,
logForename,
logSurname,
logData);
}
}
}
}
return new JsonResult(new { Url = "users/index" });
}
private GraphServiceClient GetGraphServiceClient(string[] scopes)
{
return GraphServiceClientFactory.GetAuthenticatedGraphClient(async () =>
{
string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
return result;
}, webOptions.GraphApiUrl);
}
}
}
我们为当前选定的用户更新 AppRoles 的 Razor 页面: