[英]MS Graph SDK - Grant multiple appRoleAssignments to a user in one request
在此處查看“appRoleAssignment”的 MS Docs: 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);
問:那么我們如何在一個請求中分配多個應用角色?
想象一下,我們有一個“角色”WebbApp 頁面,其中包含一個復選框列表,每個 AppRole 都有一個復選框,用戶選擇他們想要分配給用戶的所有相關角色,然后點擊保存按鈕。 我需要弄清楚我們如何在一個 go 中分配多個角色。 在現實世界中,頭腦正常的人不會為了為單個用戶配置角色而按二十次保存按鈕。 向 Graph 發送多個請求似乎不是預期的解決方案。
問:另一個難題是我們如何在同一個請求期間添加和刪除用戶的 AppRole 分配? 即復選框列表表示我們可能希望從用戶的成員資格中刪除的角色,以及同時將它們添加到新的角色分配中。 在我之前的項目中,我在 Net Core 中使用 Microsoft Identity package 有這個原因和影響工作得很好,但是嘗試使用 Azure AD 來實現相同的原因和影響並不是那么簡單......
下圖顯示了給定用戶的 AppRole 選擇示例:
謝謝
這或多或少適用於您的兩個問題,我不完全確定這會起作用,但是您可以使用圖形的 Json 批處理端點將多個命令構建為 1 個批處理。 https://docs.microsoft.com/en-us/graph/json-batching
如果這不起作用,恐怕您將不得不多次循環和調用 graph 。
對於 Q2,這假設您已經擁有一個當前角色列表,以便您可以專門針對特定角色分配調用刪除端點。 如果您沒有當前角色的列表, https://docs.microsoft.com/en-us/graph/api/serviceprincipal-get?view=graph-rest-1.0&tabs=http這是獲取整個包含應用角色的應用配置文件,然后使用用戶應用角色分配端點https://docs.microsoft.com/en-us/graph/api/user-list-approleassignments?view=graph-rest-1.0&tabs=http獲取分配給用戶的角色列表。 匹配他們以查看他們當前屬於哪些角色。
我已經嘗試並測試了一個正在運行的解決方案,但由於堆棧溢出 30,000 個字符的限制,我無法在我最終做的事情中顯示完整的代碼示例,但在下面包含了一些片段。
我想要實現的總結:
在 WebApp 中提供 UI,管理員可以將 AppRoleAssingments 添加/修改到 Azure AD 用戶,而無需登錄 Azure 門戶本身。
使用 .NET Core 3.1 Razor 頁面,我創建了一個帶有復選框列表的頁面,每個 AppRole 一個復選框。 加載頁面時,將為用戶當前所屬的每個角色檢查 AppRoles。 然后管理員可以選中或取消選中他們想要為該用戶修改的任何角色,當點擊保存按鈕時,我們會將更新的 AppRoles 列表發送回 Graph。
我使用 Javascript 和 Ajax 在 razor 頁面和 Z20F35E630DAF49DCZCZF 頁面之間發送/接收數據。
Step 1 - AJAX call to the razor page model which in turn calls the MS Graph and fetches a list of all the AppRoles found for the Tenant within Azure AD. 在同一個方法中,我們然后處理對 Graph 的二次調用,以獲取所選用戶當前所屬的 AppRoles 列表。 使用 foreach 循環,我們查看用戶當前屬於哪個 AppRoles,並針對我們返回 razor 頁面的 AppRoles 列表的 'Assinged' = true 屬性放置一個標志。
第 2 步 - razor 頁面填充了每個 AppRole 的復選框,並檢查了用戶當前所屬的復選框。 管理員在點擊保存按鈕之前檢查或取消選中用戶的 AppRoles,並將更新發布回 razor 頁面 model。
第 3 步 - 在我們將更新發布回 MS Graph 之前,我們必須確定用戶已經屬於哪些 AppRoles,否則我們會在 MS Graph SDK 中遇到服務異常。
這是很難弄清楚的部分,但是在將用戶已經擁有的內容與我們需要進行的更改進行比較之后,我們在 foreach 循環中將更新發送回 Graph,即我們只能將用戶添加或刪除到AppRoleAssignment 一次一個。
下面的代碼示例顯示了 razor 頁面 model 位,不幸的是在 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 頁面:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.