Я пытаюсь использовать GraphAPI v5 в приложении Blazor .NET 8.0, чтобы запросить список всех пользователей, которым назначена определенная роль приложения.
Я настраиваю роли в Entra Admin Center > Корпоративные приложения > (мое приложение) > Пользователи и группы и назначаю пользователям роли, которые я определил при регистрации приложения.
Я попробовал следующий код на странице Razor:
@inject GraphServiceClient GSC
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
// ...
protected async Task AzTest()
{
try
{
var users = await GSC.Users.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Expand = new string[] { "appRoleAssignments" };
});
}
catch(Exception ex)
{
ConsentHandler.HandleException(ex);
}
}
Я также обнаружил (благодаря комментатору), что Directory.Read.All
является необходимой областью действия для работы этого вызова.
Мой вопрос: как мне отфильтровать результат, чтобы найти только пользователей с определенной ролью? В моей организации более 1000 пользователей.
Я пробовал следующие подходы:
Подход 1 — Фильтр
Guid roleIdGuid = new Guid("5f61fba7-ae07-4438-9f41-4c00b2540463");
// ...
requestConfiguration.QueryParameters.Filter = $"appRoleAssignments/any(ar: ar/appRoleId eq {roleIdGuid})";
(это было предложено вторым пилотом). Это дает ошибку времени выполнения:
Microsoft.Graph.Models.ODataErrors.ODataError: Property 'appRoleId' does not exist as a declared property or extension property.
что, по мнению Copilot, означает, что API фактически не поддерживает фильтрацию по идентификатору роли приложения.
Подход 2 — Пагинация
Другой подход — получить всех пользователей через разбиение на страницы, а затем просмотреть их на C# для проверки идентификатора роли. Однако нумерация страниц, похоже, не работает. Используя точный код из документации V5:
var usersResponse = await graphServiceClient
.Users
.GetAsync(requestConfiguration => {
requestConfiguration.QueryParameters.Expand = new string[] { "appRoleAssignments" };
requestConfiguration.QueryParameters.Top = 1;
});
var userList = new List<User>();
var pageIterator = PageIterator<User,UserCollectionResponse>.CreatePageIterator(graphServiceClient,usersResponse, (user) => { userList.Add(user); return true; });
await pageIterator.IterateAsync();
// (omitted the later filtering code for brevity)
Это запустилось, но я ждал более 90 секунд, а он все еще выполнял вызовы API и ответы со стороны await
; Я не уверен, застрял ли он каким-то образом в бесконечном цикле или просто длился вечно, но в любом случае такая задержка не является приемлемым решением.
@TinyWang ок, добавление Directory.Read.All
к запрошенным областям делает GSC.Users.Request().GetAsync()
успешным, теперь я думаю, мой вопрос переходит к тому, какой правильный фильтр и т. д., чтобы получить информацию, которую я ищу
Спасибо за Ваше подтверждение! А если не получится, возможно, нам еще понадобится User.Read.All
.
@TinyWang Я обновил вопрос сейчас, чтобы сосредоточиться на исходном вопросе, преодолев проблему с разрешениями.
Привет! Судя по документу API, appRoleId не поддерживает фильтр. Фильтр поддержки свойств будет иметь соответствующее описание. Но результат вашего теста уже доказал, что это свойство не поддерживается.
Тогда у меня уже был образец, и вы можете использовать его напрямую: stackoverflow.com/a/78079972/14574199
Затем о requestConfiguration.QueryParameters.Top = 1;
--> Я тестирую Top1, так как в моем тестовом клиенте всего 2 пользователя.... Вы можете использовать Top 999... аналогично этому случаю stackoverflow.com/a/78720008/14574199
appRoleId
не поддерживает фильтр из документа API. Как видите, principalDisplayName
поддерживает фильтр. Итак, я согласен с вами, что нам нужно получить всех пользователей, а затем самим назначить им определенные роли.
Что касается пагинации Graph API, мы могли бы установить размер страницы равным 999, чтобы минимизировать количество запросов, и я боюсь, что топ-1 — это причина, по которой вы так долго ждали.
Тогда наши коды будут похожи на
var usersResponse = await graphServiceClient.Users.GetAsync(requestConfiguration => {
requestConfiguration.QueryParameters.Expand = new string[] { "appRoleAssignments" };
requestConfiguration.QueryParameters.Top = 999;
});
var userList = new List<User>();
var pageIterator = PageIterator<User,UserCollectionResponse>.CreatePageIterator(graphServiceClient,usersResponse, (user) => { userList.Add(user); return true; });
await pageIterator.IterateAsync();
List<User> result = new List<User>();
foreach (var tempUser in userList) {
var roles = tempUser.AppRoleAssignments;
foreach(var role in roles) {
if (role.AppRoleId.ToString() == "role_id_here")
{
result.Add(tempUser);
break;
}
}
}
Я заметил, что при запросе «топ-999» он возвращает только топ-100?
спасибо за ваш ответ, разные API могут по-разному вести себя для максимального количества Максимина. Основываясь на вашем описании, я искал API и нашел In server-side paging, the Microsoft Graph service returns a default number of results in a single page without the client specifying the number of results to return using $top. For example, the GET /users endpoint returns a default of 100 results in a single page.
по этой ссылке. Боюсь, API пользователя списка изменил максимальное верхнее значение. Я вспомнил, что это
раньше было 999. А может я просто ошибся в памяти. В любом случае, не могли бы вы сообщить мне, сработало ли это для вас или нет? Сталкивались ли вы с проблемой столь долгого ожидания?
На самом деле в арендаторе больше пользователей, чем я думал (более 20 000), поэтому даже страницы в 999 — это слишком медленно; Можете ли вы придумать какой-нибудь фильтр, который я мог бы использовать, чтобы немного сузить список? Фильтр endsWith(mail, 'mydomain.com')
работает сам по себе, но API, похоже, не поддерживает его использование одновременно с параметром Expand
.
Боюсь, вам придется попробовать другие фильтры, я тестирую на своей стороне запрос https://graph.microsoft.com/v1.0/users?$expand=appRoleAssignments&$filter=userPrincipalName eq '[email protected]'
, который работает хорошо, но если я использовал $expand=appRoleAssignments&$filter=endswith(userPrincipalName,'[email protected]oft.com')
, это дало мне неподдерживаемую ошибку запроса, даже если это свойство поддерживает фильтрацию с помощью endswith
. Это странно.
Боюсь, у нас может быть обходной путь для создания группы, добавляющей в нее всех пользователей, имеющих определенную роль приложения. Я нашел этот случай, в котором использовался скрипт PowerShell. Мы также могли бы создать сценарий PowerShell, чтобы найти всех пользователей и добавить пользователей в эту группу.
если я правильно понимаю, сценарий powershell применяет фильтр, а затем один за другим выполняет дальнейшие вызовы, чтобы проверить appRoleAssignment для каждого пользователя. Не идеально
Привет, прошу прощения, что не четко выразил свою мысль. Я имею в виду, что мы можем попробовать PowerShell выполнить такую задачу, как фоновая служба, которая поможет нам найти всех пользователей, а затем добавить их в группу, чтобы нас не заботила производительность. Ссылка, которой я поделился, хм, я просто хочу, чтобы вы увидели пример PowerShell для вызова API MS Graph, некоторые сценарии могут быть похожими, но это не одно и то же.
ХОРОШО. чтобы уточнить, вы говорите, что вместо использования ролей для этой задачи при регистрации приложения мне лучше использовать группы?
да, поскольку у нас нет хорошего API для выполнения этой задачи и получения всех пользователей с определенной ролью для обслуживания в будущем, возможно, другим вариантом может быть создание группы для этих пользователей. Кстати, мы могли бы даже дать роль группе. Доступен API для участников группы списков и группы listmemberOf.
Этот ответ суммирует обсуждение комментариев
В настоящее время (июль 2024 г.) нет способа эффективно составить список всех пользователей с определенной ролью приложения; единственный способ — перечислить всех пользователей и проверить их роли на уровне приложения, что неприемлемо медленно, если, например, арендатор содержит более 20 000 пользователей.
Вместо этого систему можно было бы спроектировать для использования групп вместо ролей приложений. API поддерживает список членов группы.
Привет, я просто хочу дважды подтвердить, выдаст ли
var users = await GSC.Users.Request().GetAsync();
ошибку или нет. Насколько я понимаю, если мы хотим перечислить всех пользователей в клиенте, которые соответствуют какому-либо правилу, необходимо, по крайней мере, иметь разрешение на чтение каталога, например Directory.Read.All. Если у вас нет этого разрешения, боюсь, реализовать ваше требование невозможно.