microsoftgraph / msgraph-beta-sdk-dotnet

The Microsoft Graph Client Beta Library for .NET supports the Microsoft Graph /beta endpoint. (preview)
Other
97 stars 32 forks source link

Invitation resource type won't send invite (convert) internally user to external #798

Closed markes20754 closed 9 months ago

markes20754 commented 9 months ago

I'm using the invitation resource to try to send B2B invitations to "convert" locally synced AAD accounts to external accounts as documented here: https://learn.microsoft.com/en-us/entra/external-id/invite-internal-users . This works as documented at the end of the article when using methods such as powershell New-MGInviation, New-MGBetaInvitation. However, I'm running into an issue when posting the .NET Invitation Resource type from a .NET application, It appears that the library may want to create a new user rather than converting the existing user. Specifically, the Graph returns "The invited user already exists in the directory as objectID: {AAD objID}. They can use that account to sign in to shared apps and resources."

I'm using Microsoft.Graph.Beta

Am I using this incorrectly or is there something that is always trying to create a new guest user rather than trying to convert an existing internal user to a guest? Thanks!

Here's the sample code snip: and screenshot of result:

string[] scopes = new string[1] { "https://graph.microsoft.com/.default"};
var tenantId = "TENANT_ID";
var clientId = "APPLICATION_ID";
var clientSecret = "YOUR_SECRET";

var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,   
};

var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options);
var _graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);

var invitation = new Invitation()
{
    InvitedUserEmailAddress= "user@contoso.com",
    InviteRedirectUrl ="https://myapplications.azure.us/contoso.us",
    SendInvitationMessage = true,
};

var result = _graphServiceClient.Invitations.PostAsync(invitation); 

image

andrueastman commented 9 months ago

Thanks for raising this @markes20754

The linked article shows that the request is made to https://graph.microsoft.com/v1.0/invitations while this package will make requests to https://graph.microsoft.com/beta/invitations which may behave differently. Any chance this is resolved if you switch to the v1.0 package or change the baseUrl value in the GraphServiceClient constructor?

markes20754 commented 9 months ago

Hi @andrueastman I tried setting v1.0 as the endpoint and beta endpoints. Both result in the same error. NOTE: the New-MGBetaInvitation (which uses the /beta endpoint) works properly so I don't think this is an issue between the v1.0 and beta endpoints. Thanks!

andrueastman commented 9 months ago

@markes20754 Thanks for the extra information. Are you by any chance able to capture a trace of the payload sent to the API when using Powershell so that we can compare with the one generated by the SDK to identify the discrepancy?

markes20754 commented 9 months ago

@andrueastman I was able to capture both in a fiddler trace. It appears that the PowerShell module is actually passing the Azure AD InvitedUser object information in the request body where the .NET invitation is not.

High level logic that works in Powershell:

  1. Call to Graph to get the AAD user object based on the UPN (get-mgBetaUser)
  2. Call to Graph to invite the AAD user (new-mgBetaInvitation)

Similar logic is done in .NET -- Here's the code snip which includes the Azure AD user object as the InvitedUser property but it's not passed in the request. First screen shot shows the PS call to get the user and then invite that user. Second screen shot is .net but it's not including the invited user information. From looking at the code, it appears that .InvitedUser is read only and is only creating a new guest in the tenant and can't convert an existing user to external.

var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, options); var _graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes); var usrAzureUser = new User = _graphServiceClient.Users("gemma.perry@UPNSUFFIXREMOVED").GetAsync.Result;

var invitation = new Invitation() { InvitedUser = usrAzureUser, InvitedUserEmailAddress= "gemma.perry@UPNSUFFIXREMOVED", InviteRedirectUrl ="https://myapplications.azure.us/contoso.us", SendInvitationMessage = true, };

POWERSHELL FIDDLER TRACE -- NOTE invitedUser is passed image

.NET FIDDLER TRACE -- NOTE invitedUser property data is missing image

andrueastman commented 9 months ago

Thanks for the information here @markes20754

The missing property is due to the backingstore which tries to send only changed values if the data is a response from the API.

You can resolve this by adding this line so that all the data is sent back.

var usrAzureUser = await graphClient.Users["user-id"].GetAsync();
usrAzureUser.BackingStore.InitializationCompleted = false; // add this line so that all properties are serialized. 
var invitation = new Invitation()
{
    InvitedUser = usrAzureUser,
    InvitedUserEmailAddress = "user@contoso.com",
    InviteRedirectUrl = "https://myapplications.azure.us/contoso.us",
    SendInvitationMessage = true,
};

var jsonString = KiotaJsonSerializer.SerializeAsString(invitation);
markes20754 commented 9 months ago

@andrueastman - Many thanks. That resolved the issue. I'll submit some updated documentation notes to the invitation manager page: https://learn.microsoft.com/en-us/graph/api/resources/invitation?view=graph-rest-1.0

microsoft-github-policy-service[bot] commented 9 months ago
andrueastman commented 9 months ago

Thanks for confirming @markes20754