Skip to content

Escape # characters in UPN for graph user requests #1523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Fixed `Get-PnPRecycleBinListItem` not retrieving second stage items if only `RowLimit` is specified.
- Fixed `Add-PnPDataRowsToSiteTemplate` issue with PnP templates when it contained multilingual references.
- Fixed `Copy-PnPItemProxy` is not recognized as the name of a cmdlet, function, script file, or operable program error with the cmdlet.- Fixed `Copy-PnPItemProxy` is not recognized as the name of a cmdlet, function, script file, or operable program error with the cmdlet.
- Fixed `Add-PnPMicrosoft365GroupMember`, `Remove-PnPMicrosoft365GroupMember`, `Add-PnPTeamsUser` and `Remove-PnPTeamsUser` not being able to handle guest accounts containing # characters [#1523](https://github.com/pnp/powershell/pull/1523)

### Removed

Expand All @@ -45,6 +46,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Arleta [PowershellScripts]
- Brendon Lee [brenle]
- Guillaume Bordier [gbordier]
- [reusto]

## [1.9.0]

Expand Down
16 changes: 13 additions & 3 deletions src/Commands/Utilities/Microsoft365GroupsUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,21 @@ internal static async Task AddMembersAsync(HttpClient httpClient, Guid groupId,
await AddUsersToGroupAsync("members", httpClient, groupId, users, accessToken, removeExisting);
}

internal static string GetUserGraphUrlForUPN(string upn)
{

var escapedUpn = upn.Replace("#", "%23");

if (escapedUpn.StartsWith("$")) return $"users('{escapedUpn}')";

return $"users/{escapedUpn}";
}

private static async Task AddUsersToGroupAsync(string groupName, HttpClient httpClient, Guid groupId, string[] users, string accessToken, bool removeExisting)
{
foreach (var user in users)
{
var userIdResult = await GraphHelper.GetAsync(httpClient, $"v1.0/users/{user}?$select=Id", accessToken);
var userIdResult = await GraphHelper.GetAsync(httpClient, $"v1.0/{GetUserGraphUrlForUPN(user)}?$select=Id", accessToken);
var resultElement = JsonSerializer.Deserialize<JsonElement>(userIdResult);
if (resultElement.TryGetProperty("id", out JsonElement idProperty))
{
Expand Down Expand Up @@ -196,7 +206,7 @@ private static async Task RemoveUserFromGroupAsync(string groupName, HttpClient
{
foreach (var user in users)
{
var resultString = await GraphHelper.GetAsync(httpClient, $"v1.0/users/{user}?$select=Id", accessToken);
var resultString = await GraphHelper.GetAsync(httpClient, $"v1.0/{GetUserGraphUrlForUPN(user)}?$select=Id", accessToken);
var resultElement = JsonSerializer.Deserialize<JsonElement>(resultString);
if (resultElement.TryGetProperty("id", out JsonElement idElement))
{
Expand Down Expand Up @@ -325,7 +335,7 @@ internal static async Task<Dictionary<string, string>> GetUserIdsBatched(HttpCli
foreach (var upn in userPrincipalNames)
{
id++;
batch.Requests.Add(new GraphBatchRequest() { Id = id.ToString(), Method = "GET", Url = $"/users/{upn}?$select=Id" });
batch.Requests.Add(new GraphBatchRequest() { Id = id.ToString(), Method = "GET", Url = $"/{GetUserGraphUrlForUPN(upn)}?$select=Id" });
requests.Add(id.ToString(), upn);
}
var stringContent = new StringContent(JsonSerializer.Serialize(batch));
Expand Down
23 changes: 17 additions & 6 deletions src/Commands/Utilities/TeamsUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,25 @@ public static async Task<Team> NewTeamAsync(string accessToken, HttpClient httpC
return returnTeam;
}

internal static string GetUserGraphUrlForUPN(string upn)
{

var escapedUpn = upn.Replace("#", "%23");

if (escapedUpn.StartsWith("$")) return $"users('{escapedUpn}')";

return $"users/{escapedUpn}";
}

private static async Task<Group> CreateGroupAsync(string accessToken, HttpClient httpClient, string displayName, string description, string classification, string mailNickname, GroupVisibility visibility, string[] owners, TeamsTemplateType templateType = TeamsTemplateType.None, TeamResourceBehaviorOptions?[] resourceBehaviorOptions = null)
{
// When creating a group, we always need an owner, thus we'll try to define it from the passed in owners array
string ownerId = null;
if (owners != null && owners.Length > 0)
{
// Owner(s) have been provided, use the first owner as the initial owner. The other owners will be added later.
var user = await GraphHelper.GetAsync<User>(httpClient, $"v1.0/users/{owners[0]}?$select=Id", accessToken);
var user = await GraphHelper.GetAsync<User>(httpClient, $"v1.0/users/{GetUserGraphUrlForUPN(owners[0])}?$select=Id", accessToken);

if (user != null)
{
// User Id of the first owner has been found
Expand Down Expand Up @@ -376,7 +387,7 @@ public static async Task<HttpResponseMessage> SetTeamArchivedStateAsync(HttpClie
#region Users
public static async Task AddUserAsync(HttpClient httpClient, string accessToken, string groupId, string upn, string role)
{
var userIdResult = await GraphHelper.GetAsync(httpClient, $"v1.0/users/{upn}?$select=Id", accessToken);
var userIdResult = await GraphHelper.GetAsync(httpClient, $"v1.0/{GetUserGraphUrlForUPN(upn)}?$select=Id", accessToken);
var resultElement = JsonSerializer.Deserialize<JsonElement>(userIdResult);
if (resultElement.TryGetProperty("id", out JsonElement idProperty))
{
Expand All @@ -394,10 +405,10 @@ public static async Task AddUserAsync(HttpClient httpClient, string accessToken,

public static async Task AddUsersAsync(HttpClient httpClient, string accessToken, string groupId, string[] upn, string role)
{
var chunks = BatchUtility.Chunk(upn, 20);
var chunks = BatchUtility.Chunk(upn.Select(u => GetUserGraphUrlForUPN(u)), 20);
foreach (var chunk in chunks)
{
var results = await BatchUtility.GetPropertyBatchedAsync(httpClient, accessToken, chunk.ToArray(), "/users/{0}", "id");
var results = await BatchUtility.GetPropertyBatchedAsync(httpClient, accessToken, chunk.ToArray(), "/{0}", "id");
var teamChannelMember = new List<TeamChannelMember>();
foreach (var userid in results.Select(r => r.Value))
{
Expand Down Expand Up @@ -487,7 +498,7 @@ public static async Task<IEnumerable<User>> GetUsersAsync(HttpClient httpClient,

public static async Task DeleteUserAsync(HttpClient httpClient, string accessToken, string groupId, string upn, string role)
{
var user = await GraphHelper.GetAsync<User>(httpClient, $"v1.0/users/{upn}?$select=Id", accessToken);
var user = await GraphHelper.GetAsync<User>(httpClient, $"v1.0/{GetUserGraphUrlForUPN(upn)}?$select=Id", accessToken);
if (user != null)
{
// check if the user is an owner
Expand Down Expand Up @@ -566,7 +577,7 @@ public static async Task<TeamChannel> AddChannelAsync(string accessToken, HttpCl
if (isPrivate)
{
channel.Type = "#Microsoft.Graph.channel";
var user = await GraphHelper.GetAsync<User>(httpClient, $"v1.0/users/{ownerUPN}", accessToken);
var user = await GraphHelper.GetAsync<User>(httpClient, $"v1.0/{GetUserGraphUrlForUPN(ownerUPN)}", accessToken);
channel.Members = new List<TeamChannelMember>();
channel.Members.Add(new TeamChannelMember() { Roles = new List<string> { "owner" }, UserIdentifier = $"https://{PnPConnection.Current.GraphEndPoint}/v1.0/users('{user.Id}')" });
return await GraphHelper.PostAsync<TeamChannel>(httpClient, $"v1.0/teams/{groupId}/channels", channel, accessToken);
Expand Down