Skip to content
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

AzureIdentityAccessTokenProvider.GetAuthorizationTokenAsync #93

Closed
schuettecarsten opened this issue Jun 22, 2023 · 4 comments · Fixed by #95
Closed

AzureIdentityAccessTokenProvider.GetAuthorizationTokenAsync #93

schuettecarsten opened this issue Jun 22, 2023 · 4 comments · Fixed by #95

Comments

@schuettecarsten
Copy link
Contributor

schuettecarsten commented Jun 22, 2023

Method AzureIdentityAccessTokenProvider.GetAuthorizationTokenAsync will add the current uri to the instance variable _scopes. This is not thread-safe and might cause InvalidOperationExceptions because of collections that are modified concurrently. It also uses the current method parameter uri to initialize the instance variable. Next time, when a different uri is used, scopes might be wrong.

My exception:

System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
at bool System.Collections.Generic.HashSet.AddIfNotPresent(T value, out int location)
at async Task Microsoft.Kiota.Authentication.Azure.AzureIdentityAccessTokenProvider.GetAuthorizationTokenAsync(Uri uri, Dictionary additionalAuthenticationContext, CancellationToken cancellationToken)
at async Task Microsoft.Kiota.Abstractions.Authentication.BaseBearerTokenAuthenticationProvider.AuthenticateRequestAsync(RequestInformation request, Dictionary additionalAuthenticationContext, CancellationToken cancellationToken)
at async Task Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.GetHttpResponseMessage(RequestInformation requestInfo, CancellationToken cancellationToken, Activity activityForAttributes, string claims)
at async Task Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.SendAsync(RequestInformation requestInfo, ParsableFactory factory, Dictionary> errorMapping, CancellationToken cancellationToken)
at async Task Microsoft.Graph.Users.UsersRequestBuilder.GetAsync(Action requestConfiguration, CancellationToken cancellationToken)
[...]

Current implementation:

        if(!_scopes.Any())
            _scopes.Add($"{uri.Scheme}://{uri.Host}/.default");
        span?.SetTag("com.microsoft.kiota.authentication.scopes", string.Join(",", _scopes));
        var result = await this._credential.GetTokenAsync(new TokenRequestContext(_scopes.ToArray(), claims: decodedClaim), cancellationToken);

Idea to fix this is to not modify _scopes instance variable but use a local array:

        string[] scopes = _scopes.ToArray();
        if (scopes.Length == 0)
            sopes = new string[] { $"{uri.Scheme}://{uri.Host}/.default" };
        span?.SetTag("com.microsoft.kiota.authentication.scopes", string.Join(",", scopes));
        var result = await this._credential.GetTokenAsync(new TokenRequestContext(scopes, claims: decodedClaim), cancellationToken);
@baywet
Copy link
Member

baywet commented Jun 22, 2023

Thanks for reporting this @schuettecarsten
Use a local variable array means we'd be making a copy on every call, which would add to the memory pressure and GC workload.
What do you think about using a concurrent dictionary (string, bool) for the field instead?
Would you be willing to submit a pull request to correct this issue?
Thanks!

@schuettecarsten
Copy link
Contributor Author

Use a local variable array means we'd be making a copy on every call

That's correct, but the ToArray() call is there in the old code also. Its directly inside the this._credential.GetTokenAsync(new TokenRequestContext(_scopes.ToArray().... So there should be no additional array. Maybe lifetime is a few ticks longer for the length check, but that seems to be acceptable. A concurrent dictionary seems to have a mich higher overhead for locking stuff?

@schuettecarsten
Copy link
Contributor Author

schuettecarsten commented Jun 22, 2023

Optimized version with less allocations, what do you think @baywet?

        string[] scopes;
        if (_scopes.Any())
            scopes = _scopes.ToArray()
        else
            sopes = new string[] { $"{uri.Scheme}://{uri.Host}/.default" };
        span?.SetTag("com.microsoft.kiota.authentication.scopes", string.Join(",", scopes));

Yes, I can create a pull request on this.

@schuettecarsten
Copy link
Contributor Author

The PR is ready, please review carefully, I've added two more minor changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants