簡體   English   中英

MS Graph SDK - 在一個請求中向用戶授予多個 appRoleAssignments

[英]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 頁面中擠入了太多代碼和幫助說明。 希望它可以幫助其他人找到類似的解決方案。


Razor 頁 Model

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM