OfficeDev / TeamsFx

Developer tools for building Teams apps
Other
427 stars 164 forks source link

Custom Graph API permission is ignored #1380

Closed grumpykiwi closed 2 years ago

grumpykiwi commented 2 years ago

Describe the bug Added code to return a list of users in a specific AD group. Added Groups.Read.All to scopes inside index,.js GetToken() method. On execute receive Insufficient privileges to complete the operation.

To Reproduce Steps to reproduce the behavior:

  1. Create new app with Teams Toolkit
  2. Add the following line to tab.razor code section in the GetUserProfilePhoto() method after the call to initialize the graph client: var emps = await graphClient.Groups[GroupId].Members.Request().GetAsync();

Set a breakpoint on this line

  1. Run the App using F5
  2. Click on Authorize. You will hit the breakpoint set above
  3. Hit F10 to step over. Code will jump to the finally section and throw the following error:

`blazor.server.js:21 [2021-06-19T01:16:06.352Z] Error: Status Code: Forbidden Microsoft.Graph.ServiceException: Code: Authorization_RequestDenied Message: Insufficient privileges to complete the operation. Inner error: AdditionalData: date: 2021-06-19T01:16:06 request-id: 6fbf179b-be6c-4523-830d-a6629a7ee248 client-request-id: 6fbf179b-be6c-4523-830d-a6629a7ee248 ClientRequestId: 6fbf179b-be6c-4523-830d-a6629a7ee248

at Microsoft.Graph.HttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) at Microsoft.Graph.BaseRequest.SendRequestAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption) at Microsoft.Graph.BaseRequest.SendAsync[T](Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption) at Microsoft.Graph.GroupMembersCollectionWithReferencesRequest.GetAsync(CancellationToken cancellationToken) at TeamsStatus.Core.Pages.Tab.GetUserProfilePhoto() in D:\projects\TeamsStatus.Core\TeamsStatus.Core\Pages\Tab.razor:line 142 at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)`

Expected behavior The emps var to be populated with a list of users in the specified AD group

VS Code Extension Information (please complete the following information):

CLI Information (please complete the following information):

Additional context Add any other context about the problem here.

JerryYangKai commented 2 years ago

Hi @grumpykiwi , since we only got JavaScript version of microsoft/teamsfx sdk, so we including webpack to wrapper it and using JSRuntime to trigger it in Blazor. The webpack source code is in JS/src/index.js, you could add your code such as 'added Groups.Read.All to scopes inside index,.js GetToken() method'. Then under JS folder , run 'npm install' and 'npm run build' to webpack the code and refresh the webpack file (wwwroot/teamsfx.js). Finally you could using JSRuntime trigger the func you just updated in TeamsFx.cs. await jsRuntime.InvokeAsync("TeamsFx.getToken"); We've tried to remove webpack and provide a more convenient way to using sdk in the future

grumpykiwi commented 2 years ago

Hi

I modified index.js as follows:

var scope = ["User.Read, Presence.Read.All, Group.Read.All, Directory.Read.All, Domain.Read.All, email, GroupMember.Read.All, Mail.Send, offline_access, openid, People.Read.All, profile, TeamMember.Read.All, User.ReadWrite.All"];

Changed in both popupLoginPage() and getToken(). Did the npm stuff, re-ran and get an error on this line of code inside GetUserProfilePhoto()

var photoStream = await graphClient.Me.Photo.Content.Request().GetAsync();

Error is:

{"Consent failed for the scope User.Read, Presence.Read.All, Group.Read.All, Directory.Read.All, Domain.Read.All, email, GroupMember.Read.All, Mail.Send, offline_access, openid, People.Read.All, profile, TeamMember.Read.All, User.ReadWrite.All with error: FailedToOpenWindow\nErrorWithCode.ConsentFailed: Consent failed for the scope User.Read, Presence.Read.All, Group.Read.All, Directory.Read.All, Domain.Read.All, email, GroupMember.Read.All, Mail.Send, offline_access, openid, People.Read.All, profile, TeamMember.Read.All, User.ReadWrite.All with error: FailedToOpenWindow\n at Object.failureCallback (webpack://TeamsFx/./node_modules/@microsoft/teamsfx/dist-esm/src/credential/teamsUserCredential.browser.js?:125:36)\n at m (webpack://TeamsFx/./node_modules/@microsoft/teams-js/dist/MicrosoftTeams.min.js?:1:16239)\n at u (webpack://TeamsFx/./node_modules/@microsoft/teams-js/dist/MicrosoftTeams.min.js?:1:15495)\n at Object.e.authenticate (webpack://TeamsFx/./node_modules/@microsoft/teams-js/dist/MicrosoftTeams.min.js?:1:17717)\n at eval (webpack://TeamsFx/./node_modules/@microsoft/teamsfx/dist-esm/src/credential/teamsUserCredential.browser.js?:102:85)\n at Object.g [as initialize] (webpack://TeamsFx/./node_modules/@microsoft/teams-js/dist/MicrosoftTeams.min.js?:1:36899)\n at eval (webpack://TeamsFx/./node_modules/@microsoft/teamsfx/dist-esm/src/credential/teamsUserCredential.browser.js?:101:66)\n at new Promise ()\n at TeamsUserCredential.eval (webpack://TeamsFx/./node_modules/@microsoft/teamsfx/dist-esm/src/credential/teamsUserCredential.browser.js?:100:20)\n at Generator.next ()"}

I tried defining the scopes differently but that didnt work either.

var scope = [ "User.Read", "Presence.Read.All", "Group.Read.All", "Directory.Read.All", "Domain.Read.All", "email", "GroupMember.Read.All", "Mail.Send", "offline_access", "openid", "People.Read.All", "profile", "TeamMember.Read.All", "User.ReadWrite.All"

I have attached a PDF that shows permissions granted to the app. I dont see anything amiss in the list.

Ideas?

Thanks for your help. Cheers

Mark PC Teams Status.Core - Microsoft Azure.pdf

JerryYangKai commented 2 years ago

@SLdragon Could you please help to look at this error? Thanks.

SLdragon commented 2 years ago

@grumpykiwi , from your error message, it said "FailedToOpenWindow", did your browser block the popup window? You can take a look in the in the address bar in the browser

grumpykiwi commented 2 years ago

This is the method that is called when clicking on the Authenticate button. There are a couple of lines that I added for testing, but most of it was generated by the Teams Toolkit Visual Studio template.

` private async Task GetUserProfilePhoto() { try { IsLoading = true; IsEmployeesLoaded = false;

        var graphClient = teamsfx.GetGraphServiceClient();
        var photoStream = await graphClient.Me.Photo.Content.Request().GetAsync();
        var presence = await graphClient.Me.Presence.Request().GetAsync();
        var profile = await graphClient.Me.Request().GetAsync();

        var helper = new Data.GraphHelper(graphClient);
        //          Employees = await helper.GetEmployeeData();

        var emps = await graphClient.Groups[GroupId].Members
            .Request()
            .GetAsync();

        if (photoStream != null)
        {
            // Copy the photo stream to a memory stream
            // to get the bytes out of it
            var memoryStream = new MemoryStream();
            photoStream.CopyTo(memoryStream);
            var photoBytes = memoryStream.ToArray();

            // Generate a data URI for the photo
            UserPhotoUri = $"data:image/png;base64,{Convert.ToBase64String(photoBytes)}";
        }

        Title = profile.JobTitle;
        Status = string.Format("{0} - {1}", presence.Activity, presence.Availability);
        Email = profile.Mail;
    }
    catch (Exception ex)
    {
        e = ex;
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        IsLoading = false;
        // Uncomment this when get group permission issue sorted
        // IsEmployeesLoaded = true;
    }
}

`

The line that fails is var photoStream = await graphClient.Me.Photo.Content.Request().GetAsync(); which was generated from the toolkit. Error was captured by inserting the catch

This happens inside Team desktop client. I hit F5 from VS to start and it automatically runs in Teams. I dont see any attempts from Teams to open a window and no messages about a blocked popup.

Sorry about the code formatting. Thing has a mind of its own

SLdragon commented 2 years ago

Hi, @grumpykiwi , could you share a minimal sample of this issue, so we can take a look and investigate the root cause, thanks!

grumpykiwi commented 2 years ago

Sample attached. I have removed App Id, Tenant ID and Client Id for security purposes.

Line 140 of tab.razor is where the exception happens. Code execution goes straight to the catch at line 167 where you can see the failed to open window, which is being generated by the toolkit code as far as I can tell.

The customized scopes are in index.js. As you can see I tried 2 different ways to define them. Maybe it is just looking for a different format? I have not found any documentation or hints on a Google search about it, so am guessing.

TeamsStatus.Core.MSFT.zip

I had to remove all of the node modules folders in order to make the zip file small enough to attach.

Let me know what else you need,

Thanks

SLdragon commented 2 years ago

Thanks, we will have a try

SLdragon commented 2 years ago

Hi, @grumpykiwi , I have one quick question:

When you click the Auth button, did you see the login page in the browser like this? image

If you do not see this login window, and did you see this block icon in your browser?

image

grumpykiwi commented 2 years ago

I went into the site settings and allowed popups and notifications for teams.microsoft.com.

The popup appears very briefly but then disappears and I end up with a bunch of 400 errors in the console like this:

Microsoft.TeamsFx.SimpleAuth.Components.Auth.Exceptions.AadUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID '4d902334-ba4f-4830-8525-c884b050ccf2' named ' PC Teams Status.Core'. Send an interactive authorization request for this user and resource. Trace ID: 4e46b3c1-dcc5-47bf-a819-ad32de230a01 Correlation ID: d0f7d60e-bb59-46d9-98bf-94ad82e11c78 Timestamp: 2021-06-23 13:03:28Z ---> MSAL.NetCore.4.30.0.0.MsalUiRequiredException: ErrorCode: invalid_grant Microsoft.Identity.Client.MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID '4d902334-ba4f-4830-8525-c884b050ccf2' named ' PC Teams Status.Core'. Send an interactive authorization request for this user and resource. Trace ID: 4e46b3c1-dcc5-47bf-a819-ad32de230a01 Correlation ID: d0f7d60e-bb59-46d9-98bf-94ad82e11c78 Timestamp: 2021-06-23 13:03:28Z at Microsoft.Identity.Client.Internal.Requests.RequestBase.HandleTokenRefreshError(MsalServiceException e, MsalAccessTokenCacheItem cachedAccessTokenItem) at Microsoft.Identity.Client.Internal.Requests.OnBehalfOfRequest.ExecuteAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.ApiConfig.Executors.ConfidentialClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenOnBehalfOfParameters onBehalfOfParameters, CancellationToken cancellationToken) at Microsoft.TeamsFx.SimpleAuth.Components.Auth.AuthHandler.AcquireTokenBySsoTokenOnBehalfOf(String ssoToken, String[] scopes) StatusCode: 400 ResponseBody: {"error":"invalid_grant","error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID '4d902334-ba4f-4830-8525-c884b050ccf2' named ' PC Teams Status.Core'. Send an interactive authorization request for this user and resource.\r\nTrace ID: 4e46b3c1-dcc5-47bf-a819-ad32de230a01\r\nCorrelation ID: d0f7d60e-bb59-46d9-98bf-94ad82e11c78\r\nTimestamp: 2021-06-23 13:03:28Z","error_codes":[65001],"timestamp":"2021-06-23 13:03:28Z","trace_id":"4e46b3c1-dcc5-47bf-a819-ad32de230a01","correlation_id":"d0f7d60e-bb59-46d9-98bf-94ad82e11c78","suberror":"consent_required"} Headers: Cache-Control: no-store, no-cache Pragma: no-cache Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff P3P: CP="DSP CUR OTPi IND OTRi ONL FIN" client-request-id: d0f7d60e-bb59-46d9-98bf-94ad82e11c78 x-ms-request-id: 4e46b3c1-dcc5-47bf-a819-ad32de230a01 x-ms-ests-server: 2.1.11829.4 - WUS2 ProdSlices x-ms-clitelem: 1,65001,0,, Set-Cookie: fpc=Al1H_D3TVA5JlKKm0o8rjXyL3WreAgAAAPonZdgOAAAAb3TSjhMAAAD7J2XYDgAAAA; expires=Fri, 23-Jul-2021 13:03:28 GMT; path=/; secure; HttpOnly; SameSite=None, x-ms-gateway-slice=estsfd; path=/; secure; httponly, stsservicecookie=estsfd; path=/; secure; samesite=none; httponly Date: Wed, 23 Jun 2021 13:03:28 GMT

--- End of inner exception stack trace --- at Microsoft.TeamsFx.SimpleAuth.Components.Auth.AuthHandler.AcquireTokenBySsoTokenOnBehalfOf(String ssoToken, String[] scopes) at Microsoft.TeamsFx.SimpleAuth.Controllers.AuthController.AcquireAccessTokenBySsoToken(PostTokenRequestBody body) at Microsoft.TeamsFx.SimpleAuth.Controllers.AuthController.PostToken(PostTokenRequestBody body) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

Capture

The capture from AD App Registration clearly shows (to me) that the app has all the permissions it needs.

Am I missing something here ? Since the popup appears so briefly, I never get the chance to approve additional permissions.

Also, is there a trick to get this app to run in the desktop client instead of the browser?

Thanks

grumpykiwi commented 2 years ago

I captured what happens. Hope it helps

https://user-images.githubusercontent.com/43825887/123111133-4b1ac200-d402-11eb-919f-d4b3f1e479df.mp4

grumpykiwi commented 2 years ago

Update. I created 2 brand new Visual Studio solutions from the Teams FX templates.

  1. Template 1. Completed SSO setup and added needed permissions 1 at a time, All added and still working fine
  2. Template 2. Copied over App & Client ID from existing project. Added 1st permission. Failed from that point.

@microsoft/teamsfx@0.1.2 : Error - Failed to get access token from authentication server: The AAD configuration in server is invalid.

This leads me to speculate there is something awry with the app registration for 4d902334-ba4f-4830-8525-c884b050ccf2.

In the meantime I will keep plugging away with template 1, adding 1 UI piece at a time, test and repeat. That way if and when it breaks I will know what broke it.. Not necessarily why of course. I shall report back my findings.

Thanks for all your help

SLdragon commented 2 years ago

Maybe there is a cache issue when you try to consent the permission,

You can follow the steps to see whether it can solve your problem:

image

image

The operation may take some time to take effect. You can restart your application after 1~2 hours, if the login page show the button which you can approve the permission you've requested, then it works.

grumpykiwi commented 2 years ago

Thanks. Did that. Will check in the morning. Almost midnight here. Cheers

Mark

grumpykiwi commented 2 years ago

Ran the app this AM. Prompted me 2x for the additional permissions then failed. Log from dev tools is attached.

Beginning to wonder if there is a configuration problem in Azure with that app registration. Probably something quite dumb on my part teams.microsoft.com-1624543244493.log

SLdragon commented 2 years ago

@grumpykiwi , from your error message "The AAD configuration in server is invalid" means that the client id or client secret is not valid.

If you're copy configuration from other project, this may introduce problem. AFIK client secret is stored in other place, maybe your client secret is not valid

You can refer this for more information about where is the secret: https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-5.0&tabs=windows#secret-manager

grumpykiwi commented 2 years ago

I deleted all previous secrets from the app registration in Azure and created a new one. Copied the value using the button provided into the appsettings.json file and re-ran it. Same error.

I then commented out the client secret line from the JSON file. No change

I just created a brand new Teams Project in VS2019. Did the SSO option under project and ran it. No issue.

I think there is something wrong in the app config somewhere. A difference between the project and Azure App registration. I will try and figure it out over the weekend. I have a working solution I can work with, but would like to sort this out so I can document and hopefully help someone else out that faces the same error.

grumpykiwi commented 2 years ago

I cloned from github a version of a teams app I had created. At the time of commit, I had added some custom scopes:

User.Read Presence.Read.All Group.Read.All GroupMember.Read.All

The app ran fine on 6/23 when committing. Now, it fails with:

Failed to get access token from authentication server: The AAD configuration in server is invalid.

This is from dev tools

I have not knowingly changed anything in the app config in any portal, yet it has stopped working. Is there some kind of behind the scenes daemon working on the app registrations in Azure that is corrupting the app entry?

I am baffled.

SLdragon commented 2 years ago

You can check whether your app secret is correct.

grumpykiwi commented 2 years ago

I think I figured it out. I have 2 apps created with the toolkit. MyTeamsApp3 and MyTeamsApp5.

Both have this entry in the csproj file:

`

net5.0
<UserSecretsId>ac2ac231-0188-4f79-bd70-ebcc17700631</UserSecretsId>

`

I opened the secrets.json file in VS Code and see this:

{ "CLIENT_SECRET": "4f1~93u6-XXXXXXXXXXXX" }

I poked around the Azure App registration portal and found this:

Teams App 5 Secret

What this shows explains what I am seeing.

When I create a 2nd project, the value points to the same file location as the 1st project and is therefore over-writing the secret value with the secret for the latest project. Leaving the previous project dead in the water.

This to me is a bug. The toolkit should generate a random GUID for the folder name on project creation, and place the secrets.json file in there. This would avoid the previous project being over-written.

In summary: The latest toolkit generated project is the only one that is going to work. All previous projects will break with an invalid client secret due to the over-writing.

grumpykiwi commented 2 years ago

I was able to work around the issue by doing the following:

  1. Created a new folder under the appdata path using the GUID from the ID field in manifest.json
  2. Created a new secret in the app portal. Copied and pasted ID and value into a text file for safe keeping
  3. Created a new secrets,json file in the new folder created in step 1. Using an old secrets.json as guide, created the JSON structure in the file.
  4. Copied in the new secret value prevously saved to the text file in step 2
  5. Updated the csproj file to use the new folder name from step 1
  6. Reran the project with F5. Success.

Hope this helps someone else. This was quite a rabbit hole of a problem. Lots of time spent in Dev Tools and Fiddler to capture what was being sent back and forth so I could nail down the source of the issue.

SLdragon commented 2 years ago

@grumpykiwi , thank you very much, seems this is a bug for our VS extension, I will inform our team members who working on the extension of this issue.

JerryYangKai commented 2 years ago

@grumpykiwi Thanks a lot about report this bug, We have fixed it and it will take affect in next release.

grumpykiwi commented 2 years ago

Glad to contribute. You might want to document the work around in the release for others that may have experienced the same problem but didn’t see this bug report.

Thanks

Mark

JerryYangKai commented 2 years ago

The new version of VS Teams Toolkit is availbale now in marketplace, we have fixed the bug in this release, please upgrade and try it.

grumpykiwi commented 2 years ago

Thanks Jerry. Will download in the morning

Mark

eriolchan commented 2 years ago

Close this bug since it has been fixed in the latest version. Feel free to reopen the issue if you find the issue again.