public async Task Token()

in src/Microsoft.Health.Fhir.Api/Controllers/AadSmartOnFhirProxyController.cs [287:447]


        public async Task<ActionResult> Token(
            [FromForm(Name = "grant_type")] string grantType,
            [FromForm(Name = "code")] string compoundCode,
            [FromForm(Name = "redirect_uri")] Uri redirectUri,
            [FromForm(Name = "client_id")] string clientId,
            [FromForm(Name = "client_secret")] string clientSecret)
        {
            try
            {
                EnsureArg.IsNotNull(grantType, nameof(grantType));
            }
            catch (ArgumentNullException ex)
            {
                throw new AadSmartOnFhirProxyBadRequestException(string.Format(Resources.ValueCannotBeNull, ex.ParamName), ex);
            }
#pragma warning disable CA2000 //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#httpclient-and-lifetime-management
            var client = _httpClientFactory.CreateClient();
#pragma warning restore CA2000

            if (Request != null &&
                string.IsNullOrEmpty(clientId) &&
                Request.Headers.TryGetValue("Authorization", out var authHeaderValues))
            {
                var authHeader = authHeaderValues.ToString();
                if (authHeader.Contains("Basic ", StringComparison.OrdinalIgnoreCase))
                {
                    var basicTokenEncoded = authHeader.Remove(0, "Basic ".Length);
                    var basicToken = Base64UrlEncoder.Decode(basicTokenEncoded);
                    var basicTokenSplit = basicToken.Split(":");
                    if (basicTokenSplit.Length == 2)
                    {
                        clientId = basicTokenSplit[0];
                        clientSecret = basicTokenSplit[1];
                    }
                }
            }

            // Azure AD supports client_credentials, etc.
            // These are used in tests and may have value even when SMART proxy is used.
            // This prevents disabling those options.
            // TODO: This should probably removed.
            //       If somebody is accessing the IDP through this endpoint, all things not SMART on FHIR should be errors.
            //       For now, we will keep it to keep the E2E tests from failing.
            // TODO: Add handling of 'aud' -> 'resource', should that be an error or should translation be done?
            if (grantType != "authorization_code")
            {
                var fields = new List<KeyValuePair<string, string>>();
                foreach (var f in Request.Form)
                {
                    fields.Add(new KeyValuePair<string, string>(f.Key, f.Value));
                }

                using var passThroughContent = new FormUrlEncodedContent(fields);

                var passThroughResponse = await client.PostAsync(new Uri(_aadTokenEndpoint), passThroughContent);

                return new ContentResult()
                {
                    Content = await passThroughResponse.Content.ReadAsStringAsync(),
                    StatusCode = (int)passThroughResponse.StatusCode,
                    ContentType = "application/json",
                };
            }

            try
            {
                EnsureArg.IsNotNull(compoundCode, nameof(compoundCode));
                EnsureArg.IsNotNull(redirectUri, nameof(redirectUri));
                EnsureArg.IsNotNull(clientId, nameof(clientId));
            }
            catch (ArgumentNullException ex)
            {
                throw new AadSmartOnFhirProxyBadRequestException(string.Format(Resources.ValueCannotBeNull, ex.ParamName), ex);
            }

            JObject decodedCompoundCode;
            string code;
            try
            {
                decodedCompoundCode = JObject.Parse(Base64UrlEncoder.Decode(compoundCode));
                code = decodedCompoundCode["code"].ToString();
            }
            catch (Exception ex)
            {
                _logger.LogError("Error decoding compound code: {message}", ex.Message);
                throw new AadSmartOnFhirProxyBadRequestException(Resources.InvalidCompoundCode, ex);
            }

            Uri callbackUrl = _urlResolver.ResolveRouteNameUrl(RouteNames.AadSmartOnFhirProxyCallback, new RouteValueDictionary { { "encodedRedirect", Base64UrlEncoder.Encode(redirectUri.ToString()) } });

            var formValues = new List<KeyValuePair<string, string>>(
                new[]
                {
                    new KeyValuePair<string, string>("grant_type", grantType),
                    new KeyValuePair<string, string>("code", code),
                    new KeyValuePair<string, string>("redirect_uri", callbackUrl.AbsoluteUri),
                    new KeyValuePair<string, string>("client_id", clientId),
                });

            if (!string.IsNullOrEmpty(clientSecret))
            {
                formValues.Add(new KeyValuePair<string, string>("client_secret", clientSecret));
            }

            using var content = new FormUrlEncodedContent(formValues);

            HttpResponseMessage response = await client.PostAsync(new Uri(_aadTokenEndpoint), content);

            if (!response.IsSuccessStatusCode)
            {
                return new ContentResult()
                {
                    Content = await response.Content.ReadAsStringAsync(),
                    StatusCode = (int)response.StatusCode,
                    ContentType = "application/json",
                };
            }

            var tokenResponse = JObject.Parse(await response.Content.ReadAsStringAsync());

            // Handle fields passed through launch context
            foreach (var launchField in _launchContextFields)
            {
                if (decodedCompoundCode[launchField] != null)
                {
                    tokenResponse[launchField] = decodedCompoundCode[launchField];
                }
            }

            tokenResponse["client_id"] = clientId;

            // Replace fully qualifies scopes with short scopes and replace $
            string[] scopes = tokenResponse["scope"]?.ToString().Split(' ');

            if (scopes != null)
            {
                var scopesBuilder = new StringBuilder();

                foreach (var s in scopes)
                {
                    if (IsAbsoluteUrl(s))
                    {
                        var scopeUri = new Uri(s);
                        scopesBuilder.Append($"{scopeUri.Segments.Last().Replace('$', '/')} ");
                    }
                    else
                    {
                        scopesBuilder.Append($"{s.Replace('$', '/')} ");
                    }
                }

                tokenResponse["scope"] = scopesBuilder.ToString().TrimEnd(' ');
            }

            return new ContentResult()
            {
                Content = tokenResponse.ToString(Newtonsoft.Json.Formatting.None),
                StatusCode = (int)response.StatusCode,
                ContentType = "application/json",
            };
        }