예제 #1
0
        public async Task <ConsentAuthorizeResponseContainer <T> > PostAuthorizationRequestAsync <T>(
            ConsentDiscoveryDocumentResponse discovery,
            ConsentAuthorizeRequest requestObject,
            T context) where T : class
        {
            var response = new ConsentAuthorizeResponseContainer <T> {
                Context = context
            };

            try
            {
                var s_cts = new CancellationTokenSource();
                s_cts.CancelAfter(_options.RequestTimeout);
                var httpClient = GetHttpClient();
                using var httpResponse = await PostJsonContentAsync(discovery.AuthorizeEndpoint, httpClient, requestObject, s_cts.Token);

                if (!httpResponse.IsSuccessStatusCode)
                {
                    response.Response = new ConsentAuthorizeResponse()
                    {
                        Subject    = requestObject.Subject,
                        Scopes     = requestObject.Scopes,
                        Authorized = false,
                        Error      = new ConsentBaseResponse.ConsentError
                        {
                            Message    = $"StatusCode={httpResponse.StatusCode}",
                            StatusCode = (int)httpResponse.StatusCode
                        }
                    };
                    if (httpResponse.Content is object)
                    {
                        var contentText = await httpResponse.Content.ReadAsStringAsync();

                        response.Response.Error.Message = contentText;
                    }
                    _logger.LogError($"authorizationEndPoint={discovery.AuthorizeEndpoint},statusCode={httpResponse.StatusCode},content=\'{response.Response.Error.Message}\'");
                    return(response);
                }


                if (httpResponse.Content is object && httpResponse.Content.Headers.ContentType.MediaType == "application/json")
                {
                    var contentStream = await httpResponse.Content.ReadAsStreamAsync();

                    var consentAuthorizeResponse = await System.Text.Json.JsonSerializer.DeserializeAsync <ConsentAuthorizeResponse>(contentStream, new System.Text.Json.JsonSerializerOptions {
                        IgnoreNullValues = true, PropertyNameCaseInsensitive = true
                    });

                    response.Response = consentAuthorizeResponse;
                    return(response);
                }
                throw new Exception("HTTP Response was invalid and cannot be deserialized.");
            }
            catch (Exception ex)
            {
                response.Response = new ConsentAuthorizeResponse()
                {
                    Subject    = requestObject.Subject,
                    Scopes     = requestObject.Scopes,
                    Authorized = false,
                    Error      = new ConsentBaseResponse.ConsentError
                    {
                        Message    = ex.Message,
                        StatusCode = (int)HttpStatusCode.BadRequest
                    }
                };
                return(response);
            }
        }
예제 #2
0
        public async Task OnGetAsync()
        {
            NameIdentifier = User.Claims.GetClaimsByType(".externalNamedIdentitier").FirstOrDefault().Value;

            var key = _oidcPipelineKey.GetOIDCPipeLineKey();

            OriginalAuthorizationRequest = await _oidcPipelineStore.GetOriginalIdTokenRequestAsync(key);

            var queryScopes = (from item in OriginalAuthorizationRequest.Raw
                               where item.Key == "scope"
                               let scopes = item.Value.Split(" ")
                                            from cItem in scopes
                                            where cItem.StartsWith(ScopeBaseUrl)
                                            select cItem).ToList();


            ConsentResponseContainers = new List <ConsentResponseContainer>();
            ExternalServiceEntities   = await _oidcConsentOrchestratorAdmin.GetAllExternalServiceEntitiesAsync();

            foreach (var es in ExternalServiceEntities)
            {
                var queryScopesService = (from item in queryScopes
                                          where item.StartsWith($"{ScopeBaseUrl}{es.Name}")
                                          select item).ToList();
                if (queryScopesService.Any())
                {
                    var discoCache = _consentDiscoveryCacheAccessor.GetConsentDiscoveryCache(es);
                    var doco       = await discoCache.GetAsync();

                    List <string> scopes = null;
                    switch (doco.AuthorizationType)
                    {
                    case Constants.AuthorizationTypes.Implicit:
                        scopes = null;
                        break;

                    case Constants.AuthorizationTypes.Subject:
                        scopes = null;
                        break;

                    case Constants.AuthorizationTypes.SubjectAndScopes:
                        scopes = queryScopes;
                        break;
                    }
                    if (doco.AuthorizationType != Constants.AuthorizationTypes.Implicit)
                    {
                        var request = new ConsentAuthorizeRequest
                        {
                            AuthorizeType = doco.AuthorizationType,
                            Scopes        = scopes,
                            Subject       = NameIdentifier
                        };
                        var response = await _consentExternalService.PostAuthorizationRequestAsync(doco, request);

                        var consentResponseContainer = new ConsentResponseContainer()
                        {
                            ExternalServiceEntity = es,
                            DiscoveryDocument     = doco,
                            Request  = request,
                            Response = response
                        };
                        ConsentResponseContainers.Add(consentResponseContainer);
                    }
                    else
                    {
                        ConsentResponseContainers.Add(new ConsentResponseContainer {
                            ExternalServiceEntity = es,
                            DiscoveryDocument     = doco,
                            Request  = null,
                            Response = null
                        });
                    }
                }
            }
            var finalScopes = (from item in ConsentResponseContainers
                               where item.Response.Authorized == true
                               from scope in item.Response.Scopes
                               select scope).ToList();

            var claims = (from item in ConsentResponseContainers
                          where item.Response.Authorized == true && item.Response.Claims != null
                          from claim in item.Response.Claims
                          let c = new ConsentAuthorizeClaim
            {
                Type = $"{item.ExternalServiceEntity.Name}.{claim.Type}",
                Value = claim.Value
            }
                          select c)
                         .DistinctBy(p => new { p.Type, p.Value })
                         .ToList();


            var customs = (from item in ConsentResponseContainers
                           where item.Response.Authorized == true && item.Response.CustomPayload != null
                           let c = new CustomPayloadContainer
            {
                Name = $"{item.ExternalServiceEntity.Name}",
                CustomPayload = item.Response.CustomPayload
            }
                           select c).ToList();

            var docoTokenService = await _tokenServiceDiscoveryCache.GetAsync();

            ArbitraryTokenTokenRequestV2 = new ArbitraryTokenTokenRequestV2()
            {
                Address            = docoTokenService.TokenEndpoint,
                ClientId           = _FluffyBunny4TokenServiceConfiguration.ClientId,
                ClientSecret       = _FluffyBunny4TokenServiceConfiguration.ClientSecret,
                Subject            = NameIdentifier,
                Scope              = new HashSet <string>(),
                ArbitraryClaims    = new Dictionary <string, List <string> >(),
                ArbitraryAmrs      = new List <string>(),
                ArbitraryAudiences = new List <string>(),
                CustomPayload      = null
            };

            if (customs.Any())
            {
                Dictionary <string, object> customMap = new Dictionary <string, object>();
                foreach (var custom in customs)
                {
                    customMap[custom.Name] = custom.CustomPayload;
                }
                ArbitraryTokenTokenRequestV2.CustomPayload = customMap;
            }
            foreach (var item in finalScopes)
            {
                ArbitraryTokenTokenRequestV2.Scope.Add(item);
            }

            foreach (var claim in claims)
            {
                if (!ArbitraryTokenTokenRequestV2.ArbitraryClaims.ContainsKey(claim.Type))
                {
                    ArbitraryTokenTokenRequestV2.ArbitraryClaims[claim.Type] = new List <string>();
                }
                ArbitraryTokenTokenRequestV2.ArbitraryClaims[claim.Type].Add(claim.Value);
            }
            JsonArbitraryTokenTokenRequestV2 = _serializer.Serialize(ArbitraryTokenTokenRequestV2);

            var httpClient   = new HttpClient();
            var tokenPayload = await _fluffyBunnyTokenService.RequestArbitraryTokenAsync(httpClient, ArbitraryTokenTokenRequestV2);
        }
        public async Task <IActionResult> PostAuthorizeAsync([FromBody] ConsentAuthorizeRequest authorizeRequest)
        {
            var authorizeResponse = new ConsentAuthorizeResponse
            {
                Authorized = false,
                Subject    = authorizeRequest.Subject
            };

            if (string.IsNullOrWhiteSpace(authorizeRequest.Subject))
            {
                authorizeResponse.Error = new ConsentBaseResponse.ConsentError
                {
                    StatusCode = (int)HttpStatusCode.BadRequest,
                    Message    = "bad subject"
                };
                return(Unauthorized(authorizeResponse));
            }

            // we are a SubjectAndScopes controller so scopes have to be present;
            if (authorizeRequest.Scopes == null || !authorizeRequest.Scopes.Any())
            {
                authorizeResponse.Error = new ConsentBaseResponse.ConsentError
                {
                    StatusCode = (int)HttpStatusCode.BadRequest,
                    Message    = "No scopes where requested!"
                };
                return(Unauthorized(authorizeResponse));
            }

            // check if user is in our database.
            //authorizeResponse.Authorized = authorizeRequest.Subject == "good" || authorizeRequest.Subject == "104758924428036663951" ;
            authorizeResponse.Authorized = true;  // accept all.
            if (authorizeResponse.Authorized)
            {
                authorizeResponse.Scopes = authorizeRequest.Scopes;
                authorizeResponse.Claims = new List <ConsentAuthorizeResponse.ConsentAuthorizeClaim>
                {
                    new ConsentAuthorizeResponse.ConsentAuthorizeClaim
                    {
                        Type  = "geo_location",
                        Value = "Canada"
                    }
                };
                authorizeResponse.CustomPayload = new MyCustom
                {
                    ConsentAuthorizeRequest = authorizeRequest,
                    Name       = nameof(MyCustom), Value = 1234,
                    Properties = new List <MyCustom.Inner>()
                    {
                        new MyCustom.Inner()
                        {
                            Name  = GuidS,
                            Value = 1
                        },
                        new MyCustom.Inner()
                        {
                            Name  = GuidS,
                            Value = 2
                        }
                    }
                };
            }
            else
            {
                authorizeResponse.Error = new ConsentBaseResponse.ConsentError
                {
                    StatusCode = (int)HttpStatusCode.BadRequest,
                    Message    = "User is bad!"
                };
            }

            if (authorizeResponse.Authorized)
            {
                return(Ok(authorizeResponse));
            }
            return(Unauthorized(authorizeResponse));
        }
예제 #4
0
        public async Task ValidateAsync(ExtensionGrantValidationContext context)
        {
            var client = context.Request.Client as ClientExtra;

            _scopedTenantRequestContext.Context.Client = client;

            var form  = context.Request.Raw;
            var error = false;
            // make sure nothing is malformed
            bool err = false;
            var  los = new List <string>();

            // optional stuff;
            var accessTokenLifetimeOverride = form.Get(Constants.AccessTokenLifetime);

            if (!string.IsNullOrWhiteSpace(accessTokenLifetimeOverride))
            {
                int accessTokenLifetime = 0;
                if (int.TryParse(accessTokenLifetimeOverride, out accessTokenLifetime))
                {
                    if (accessTokenLifetime > 0 && accessTokenLifetime <= client.AccessTokenLifetime)
                    {
                        context.Request.AccessTokenLifetime = accessTokenLifetime;
                    }
                    else
                    {
                        los.Add($"{Constants.AccessTokenLifetime}:{accessTokenLifetimeOverride} is out of range.");
                        err = true;
                    }
                }
            }
            error = error || err;
            err   = false;

            /*
             * bool allowOfflineAccess = client.AccessTokenType == AccessTokenType.Jwt;
             * var accessTokenType = form.Get(Constants.AccessTokenType);
             * if (!string.IsNullOrWhiteSpace(accessTokenType))
             * {
             *  if (string.Compare(accessTokenType, "Reference", true) == 0)
             *  {
             *      allowOfflineAccess = false;
             *  }
             *  else if (string.Compare(accessTokenType, "Jwt", true) == 0)
             *  {
             *      allowOfflineAccess = true;
             *  }
             * }
             *
             * if (!allowOfflineAccess)
             * {
             *
             *  context.Request.ValidatedResources.ParsedScopes = context.Request.ValidatedResources.ParsedScopes.Where(item =>
             *      item.ParsedName != "offline_access").ToList();
             *  context.Request.RequestedScopes = context.Request.RequestedScopes.Where(item =>
             *      item != "offline_access");
             *
             * }
             */
            // VALIDATE if issuer must be allowed
            // -------------------------------------------------------------------
            var issuer = form.Get("issuer");

            if (!string.IsNullOrEmpty(issuer))
            {
                issuer = issuer.ToLower();
                var foundIssuer = client.AllowedArbitraryIssuers.FirstOrDefault(x => x == issuer);
                if (string.IsNullOrWhiteSpace(foundIssuer))
                {
                    error = true;
                    los.Add($"issuer:{issuer} is NOT in the AllowedArbitraryIssuers collection.");
                }
            }
            _scopedTenantRequestContext.Context.Issuer = issuer;
            error = error || err;
            err   = false;

            // MUST have subject
            var subjectToken = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken);

            if (string.IsNullOrWhiteSpace(subjectToken))
            {
                err = true;
                los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken} is required");
            }
            error = error || err;
            err   = false;

            var subjectTokenType = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType);

            if (string.IsNullOrWhiteSpace(subjectTokenType))
            {
                err = true;
                los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType} is required");
            }
            else
            {
                // check to see if the subjectTokenType is allowed
                var allowedSubjectTokenType = client.AllowedTokenExchangeSubjectTokenTypes.FirstOrDefault(x => x == subjectTokenType);
                if (string.IsNullOrWhiteSpace(allowedSubjectTokenType))
                {
                    // not here
                    err = true;
                    los.Add($"{subjectTokenType} is NOT allowed for this client");
                }
            }
            error = error || err;
            err   = false;

            var requestedScopesRaw     = form[Constants.Scope].Split(' ').Distinct().ToList();
            var requestedServiceScopes = GetServiceToScopesFromRequest(requestedScopesRaw);

            _scopedOverrideRawScopeValues.IsOverride = true;

            var externalServices = new Dictionary <string, ExternalService>();

            // validate that this client is allowed to have access to external services
            foreach (var serviceScopeSet in requestedServiceScopes)
            {
                var externalService = await _externalServicesStore.GetExternalServiceByNameAsync(serviceScopeSet.Key);

                if (externalService == null)
                {
                    _logger.LogError($"external service: {serviceScopeSet.Key} does not exist");
                    continue;
                }

                if (client.AllowedTokenExchangeExternalServices.Contains(externalService.Name))
                {
                    externalServices.Add(externalService.Name, externalService);
                }
                else
                {
                    err = true;
                    var message = $"external_service:{externalService.Name} is not allowed for this client";
                    los.Add(message);
                }
            }
            error = error || err;
            err   = false;

            var subject = "";

            if (!error)
            {
                switch (subjectTokenType)
                {
                case FluffyBunny4.Constants.TokenExchangeTypes.IdToken:

                    var validatedResult = await _identityTokenValidator.ValidateIdTokenAsync(subjectToken, _tokenExchangeOptions.AuthorityKey);

                    if (validatedResult.IsError)
                    {
                        err = true;
                        los.Add($"failed to validate id_token");
                        _logger.LogError($"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}\nError={validatedResult.Error}");
                    }

                    subject = SubjectFromClaimsPrincipal(validatedResult.User);
                    if (string.IsNullOrWhiteSpace(subject))
                    {
                        err = true;
                        los.Add($"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}");
                    }


                    break;

                case FluffyBunny4.Constants.TokenExchangeTypes.AccessToken:
                    var validatedResultAccessToken = await _tokenValidator.ValidateAccessTokenAsync(subjectToken);

                    if (validatedResultAccessToken.IsError)
                    {
                        err = true;
                        los.Add($"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}");
                    }

                    subject = validatedResultAccessToken.Claims
                              .Where(item => item.Type == JwtClaimTypes.Subject)
                              .Select(item => item.Value)
                              .FirstOrDefault();
                    if (string.IsNullOrWhiteSpace(subject))
                    {
                        err = true;
                        los.Add($"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}");
                    }

                    break;

                default:
                    err = true;
                    los.Add($"not supported: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}");
                    break;
                }
                error = error || err;
                err   = false;
            }

            if (error)
            {
                context.Result.IsError = true;
                context.Result.Error   = string.Join <string>(" | ", los);
                _logger.LogError($"context.Result.Error");
                return;
            }

            var consentAuthorizeResponseTasks = new List <Task <ConsentAuthorizeResponseContainer <PostContext> > >();
            var finalCustomPayload            = new Dictionary <string, object>();

            foreach (var serviceScopeSet in requestedServiceScopes)
            {
                ExternalService externalService = null;
                externalServices.TryGetValue(serviceScopeSet.Key, out externalService);

                if (externalService == null)
                {
                    _logger.LogError($"external service: {serviceScopeSet.Key} does not exist");
                    continue;
                }

                var discoCache =
                    await _consentDiscoveryCacheAccessor.GetConsentDiscoveryCacheAsync(serviceScopeSet.Key);

                var doco = await discoCache.GetAsync();

                if (doco.IsError)
                {
                    // OPINION: If I have a lot of external services it it probably better to let this continue even it if
                    //          results in an access_token that is missing this bad service's scopes.

                    _logger.LogError(doco.Error);
                    continue;
                }

                List <string> scopes = null;
                switch (doco.AuthorizationType)
                {
                case Constants.AuthorizationTypes.Implicit:
                    scopes = null;
                    break;

                case Constants.AuthorizationTypes.SubjectAndScopes:
                    scopes = serviceScopeSet.Value;
                    break;
                }

                if (doco.AuthorizationType == Constants.AuthorizationTypes.Implicit)
                {
                    _scopedOverrideRawScopeValues.Scopes.AddRange(serviceScopeSet.Value);
                }
                else
                {
                    var request = new ConsentAuthorizeRequest
                    {
                        AuthorizeType = doco.AuthorizationType,
                        Scopes        = scopes,
                        Subject       = subject,
                        Requester     = new ConsentAuthorizeRequest.ClientRequester()
                        {
                            ClientDescription = client.Description,
                            ClientId          = client.ClientId,
                            ClientName        = client.ClientName,
                            Namespace         = client.Namespace,
                            Tenant            = client.TenantName
                        }
                    };

                    // How to send many requests in parallel in ASP.Net Core
                    // https://www.michalbialecki.com/2018/04/19/how-to-send-many-requests-in-parallel-in-asp-net-core/
                    var task = _consentExternalService.PostAuthorizationRequestAsync(doco, request, new PostContext()
                    {
                        ServiceScopeSet   = serviceScopeSet,
                        DiscoveryDocument = doco
                    });
                    consentAuthorizeResponseTasks.Add(task);
                }
            }
            var consentAuthorizeResponses = await Task.WhenAll(consentAuthorizeResponseTasks);

            foreach (var consentAuthorizeResponse in consentAuthorizeResponses)
            {
                var response        = consentAuthorizeResponse.Response;
                var serviceScopeSet = consentAuthorizeResponse.Context.ServiceScopeSet;
                var doco            = consentAuthorizeResponse.Context.DiscoveryDocument;
                if (response.Error != null)
                {
                    _logger.LogError($"ExternalService:{serviceScopeSet.Key},Error:{response.Error.Message}");
                }
                else if (response.Authorized)
                {
                    switch (doco.AuthorizationType)
                    {
                    case Constants.AuthorizationTypes.SubjectAndScopes:
                        // make sure no funny business is coming in from the auth call.
                        var serviceRoot = $"{_tokenExchangeOptions.BaseScope}{serviceScopeSet.Key}";
                        var query       = (from item in response.Scopes
                                           where item.StartsWith(serviceRoot)
                                           select item);
                        _scopedOverrideRawScopeValues.Scopes.AddRange(query);
                        if (response.Claims != null && response.Claims.Any())
                        {
                            foreach (var cac in response.Claims)
                            {
                                // namespace the claims.
                                _scopedOptionalClaims.Claims.Add(new Claim($"{serviceScopeSet.Key}.{cac.Type}",
                                                                           cac.Value));
                            }
                        }

                        if (response.CustomPayload != null)
                        {
                            finalCustomPayload.Add(serviceScopeSet.Key, response.CustomPayload);
                        }

                        break;
                    }
                }
            }

            if (finalCustomPayload.Any())
            {
                _scopedOptionalClaims.Claims.Add(new Claim(
                                                     Constants.CustomPayload,
                                                     _serializer.Serialize(finalCustomPayload),
                                                     IdentityServerConstants.ClaimValueTypes.Json));
            }

            _scopedOptionalClaims.Claims.Add(new Claim(JwtClaimTypes.AuthenticationMethod, GrantType));
            var claims = new List <Claim>
            {
                new Claim(JwtClaimTypes.AuthenticationMethod, GrantType)
            };

            context.Result = new GrantValidationResult(subject, GrantType, claims);
            _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.ExtensionGrantValidationContext, context);
            return;
        }
        public async Task <ConsentAuthorizeResponse> PostAuthorizationRequestAsync(
            ConsentDiscoveryDocumentResponse discovery,
            ConsentAuthorizeRequest requestObject)
        {
            try
            {
                var httpClient = new HttpClient();
                using var httpResponse = await PostJsonContentAsync(discovery.AuthorizeEndpoint, httpClient, requestObject);

                if (!httpResponse.IsSuccessStatusCode)
                {
                    var result = new ConsentAuthorizeResponse()
                    {
                        Subject    = requestObject.Subject,
                        Scopes     = requestObject.Scopes,
                        Authorized = false,
                        Error      = new Error
                        {
                            Message    = $"StatusCode={httpResponse.StatusCode}",
                            StatusCode = (int)httpResponse.StatusCode
                        }
                    };
                    if (httpResponse.Content is object)
                    {
                        var contentText = await httpResponse.Content.ReadAsStringAsync();

                        result.Error.Message = contentText;
                    }
                    _logger.LogError($"statusCode={httpResponse.StatusCode},content=\'{result.Error.Message}\'");
                    return(result);
                }


                if (httpResponse.Content is object && httpResponse.Content.Headers.ContentType.MediaType == "application/json")
                {
                    var contentStream = await httpResponse.Content.ReadAsStreamAsync();

                    var consentAuthorizeResponse = await System.Text.Json.JsonSerializer.DeserializeAsync <ConsentAuthorizeResponse>(contentStream, new System.Text.Json.JsonSerializerOptions {
                        IgnoreNullValues = true, PropertyNameCaseInsensitive = true
                    });

                    return(consentAuthorizeResponse);
                }
                throw new Exception("HTTP Response was invalid and cannot be deserialised.");
            }
            catch (Exception ex)
            {
                var result = new ConsentAuthorizeResponse()
                {
                    Subject    = requestObject.Subject,
                    Scopes     = requestObject.Scopes,
                    Authorized = false,
                    Error      = new Error
                    {
                        Message    = ex.Message,
                        StatusCode = (int)HttpStatusCode.BadRequest
                    }
                };
                return(result);
            }
        }
        public async Task ValidateAsync(ExtensionGrantValidationContext context)
        {
            var client = context.Request.Client as ClientExtra;

            _scopedTenantRequestContext.Context.Client = client;
            _scopedOverrideRawScopeValues.IsOverride   = true;
            var externalServices = await _externalServicesStore.GetExternalServicesAsync();

            var form  = context.Request.Raw;
            var error = false;
            var los   = new List <string>();

            // make sure nothing is malformed
            bool err = false;


            // optional stuff;
            var accessTokenLifetimeOverride = form.Get(Constants.AccessTokenLifetime);

            if (!string.IsNullOrWhiteSpace(accessTokenLifetimeOverride))
            {
                int accessTokenLifetime = 0;
                if (int.TryParse(accessTokenLifetimeOverride, out accessTokenLifetime))
                {
                    if (accessTokenLifetime > 0 && accessTokenLifetime <= client.AccessTokenLifetime)
                    {
                        context.Request.AccessTokenLifetime = accessTokenLifetime;
                    }
                    else
                    {
                        los.Add($"{Constants.AccessTokenLifetime}:{accessTokenLifetimeOverride} is out of range.");
                        err = true;
                    }
                }
            }
            error = error || err;
            err   = false;

            // MUST have subject
            // -------------------------------------------------------------------
            var subjectToken = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken);

            if (string.IsNullOrWhiteSpace(subjectToken))
            {
                err = true;
                los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken} is required");
            }
            error = error || err;
            err   = false;

            // MUST have SubjectTokenType
            // -------------------------------------------------------------------
            var subjectTokenType = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType);

            if (string.IsNullOrWhiteSpace(subjectTokenType))
            {
                err = true;
                los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType} is required");
            }
            error = error || err;
            err   = false;

            if (error)
            {
                context.Result.IsError = true;
                context.Result.Error   = string.Join <string>(" | ", los);
                _logger.LogError($"context.Result.Error");
                return;
            }

            DateTime     tokenIssuedAtTime;
            List <Claim> claims;
            var          subject = "";

            switch (subjectTokenType)
            {
            case FluffyBunny4.Constants.TokenExchangeTypes.AccessToken:
                if (subjectToken.Contains('.'))
                {
                    err = true;
                    los.Add($"failed to validate, not a reference_token: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}");
                }
                var validatedResultAccessToken = await _tokenValidator.ValidateAccessTokenAsync(subjectToken);

                if (validatedResultAccessToken.IsError)
                {
                    err = true;
                    los.Add($"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}");
                }
                subject = validatedResultAccessToken.Claims
                          .Where(item => item.Type == JwtClaimTypes.Subject)
                          .Select(item => item.Value)
                          .FirstOrDefault();
                if (string.IsNullOrWhiteSpace(subject))
                {
                    err = true;
                    los.Add($"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}");
                }


                claims = validatedResultAccessToken.Claims.ToList();
                var amr = claims.FirstOrDefault(claim => claim.Type == JwtClaimTypes.AuthenticationMethod &&
                                                claim.Value == Constants.GrantType.TokenExchange);

                if (amr == null)
                {
                    err = true;
                    los.Add($"failed to validate, missing amr={Constants.GrantType.TokenExchange}: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}");
                }

                var issuedAt = claims.FirstOrDefault(claim => claim.Type == JwtClaimTypes.IssuedAt);
                if (issuedAt == null)
                {
                    err = true;
                    los.Add(
                        $"failed to validate, {JwtClaimTypes.IssuedAt} is missing: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}");
                }

                var            unixSeconds    = Convert.ToInt64(issuedAt.Value);
                DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixSeconds);

                tokenIssuedAtTime = dateTimeOffset.UtcDateTime;

                var referenceTokenStoreGrantStoreHashAccessor = _referenceTokenStore as IGrantStoreHashAccessor;
                var fixedSubjectToken        = subjectToken.Substring(2);
                var hashKey                  = referenceTokenStoreGrantStoreHashAccessor.GetHashedKey(fixedSubjectToken);
                var accessTokenPersitedGrant = await _persistedGrantStore.GetAsync(hashKey);

                if (accessTokenPersitedGrant == null)
                {
                    err = true;
                    los.Add($"failed to validate, accessTokenPersitedGrant is missing: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}");
                }

                var accessTokenPersitedGrantExtra = accessTokenPersitedGrant as PersistedGrantExtra;
                _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.SubjectToken, subjectToken);
                _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.PersistedGrantExtra, accessTokenPersitedGrantExtra);

                var requestedScopes = context.Request.RequestedScopes.ToList();
                if (!string.IsNullOrWhiteSpace(accessTokenPersitedGrantExtra.RefreshTokenKey))
                {
                    _scopedOverrideRawScopeValues.Scopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
                    _scopedOptionalClaims.Claims.Add(new Claim(JwtClaimTypes.Scope, IdentityServerConstants.StandardScopes.OfflineAccess));
                    if (!requestedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess))
                    {
                        requestedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
                    }
                }
                else
                {
                    if (requestedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess))
                    {
                        requestedScopes.Remove(IdentityServerConstants.StandardScopes.OfflineAccess);
                    }
                }

                context.Request.RequestedScopes = requestedScopes;
                break;


            default:
                throw new Exception($"not supported: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}");
                break;
            }
            error = error || err;
            err   = false;
            if (error)
            {
                context.Result.IsError = true;
                context.Result.Error   = string.Join <string>(" | ", los);
                _logger.LogError($"context.Result.Error");
                return;
            }


            var finalCustomPayload = new Dictionary <string, object>();

            var requestedScopesRaw = form[Constants.Scope].Split(' ').ToList();

            var consentAuthorizeResponseTasks = new List <Task <ConsentAuthorizeResponseContainer <PostContext> > >();
            var requestedServiceScopes        = GetServiceToScopesFromRequest(requestedScopesRaw);

            foreach (var serviceScopeSet in requestedServiceScopes)
            {
                var externalService = await _externalServicesStore.GetExternalServiceByNameAsync(serviceScopeSet.Key);

                if (externalService == null)
                {
                    _logger.LogError($"external service: {serviceScopeSet.Key} does not exist");
                    continue;
                }

                var discoCache =
                    await _consentDiscoveryCacheAccessor.GetConsentDiscoveryCacheAsync(serviceScopeSet.Key);

                var doco = await discoCache.GetAsync();

                if (doco.IsError)
                {
                    // OPINION: If I have a lot of external services it it probably better to let this continue even it if
                    //          results in an access_token that is missing this bad service's scopes.

                    _logger.LogError(doco.Error);
                    continue;
                }

                List <string> scopes = null;
                switch (doco.AuthorizationType)
                {
                case Constants.AuthorizationTypes.Implicit:
                    scopes = null;
                    break;

                case Constants.AuthorizationTypes.SubjectAndScopes:
                    scopes = serviceScopeSet.Value;
                    break;
                }

                if (doco.AuthorizationType == Constants.AuthorizationTypes.Implicit)
                {
                    _scopedOverrideRawScopeValues.Scopes.AddRange(serviceScopeSet.Value);
                }
                else
                {
                    var request = new ConsentAuthorizeRequest
                    {
                        AuthorizeType = doco.AuthorizationType,
                        Scopes        = scopes,
                        Subject       = subject,
                        Requester     = new ConsentAuthorizeRequest.ClientRequester()
                        {
                            ClientDescription = client.Description,
                            ClientId          = client.ClientId,
                            ClientName        = client.ClientName,
                            Namespace         = client.Namespace,
                            Tenant            = client.TenantName
                        }
                    };


                    // How to send many requests in parallel in ASP.Net Core
                    // https://www.michalbialecki.com/2018/04/19/how-to-send-many-requests-in-parallel-in-asp-net-core/
                    var task = _consentExternalService.PostAuthorizationRequestAsync(doco, request, new PostContext()
                    {
                        ServiceScopeSet   = serviceScopeSet,
                        DiscoveryDocument = doco
                    });
                    consentAuthorizeResponseTasks.Add(task);
                }
            }
            var consentAuthorizeResponses = await Task.WhenAll(consentAuthorizeResponseTasks);

            foreach (var consentAuthorizeResponse in consentAuthorizeResponses)
            {
                var response        = consentAuthorizeResponse.Response;
                var serviceScopeSet = consentAuthorizeResponse.Context.ServiceScopeSet;
                var doco            = consentAuthorizeResponse.Context.DiscoveryDocument;

                if (response.Error != null)
                {
                    _logger.LogError($"ExternalService:{serviceScopeSet.Key},Error:{response.Error.Message}");
                }
                else if (response.Authorized)
                {
                    switch (doco.AuthorizationType)
                    {
                    case Constants.AuthorizationTypes.SubjectAndScopes:
                        // make sure no funny business is coming in from the auth call.
                        var serviceRoot = $"{_tokenExchangeOptions.BaseScope}{serviceScopeSet.Key}";
                        var query       = (from item in response.Scopes
                                           where item.StartsWith(serviceRoot)
                                           select item);
                        _scopedOverrideRawScopeValues.Scopes.AddRange(query);
                        if (response.Claims != null && response.Claims.Any())
                        {
                            foreach (var cac in response.Claims)
                            {
                                // namespace the claims.
                                _scopedOptionalClaims.Claims.Add(new Claim($"{serviceScopeSet.Key}.{cac.Type}",
                                                                           cac.Value));
                            }
                        }

                        if (response.CustomPayload != null)
                        {
                            finalCustomPayload.Add(serviceScopeSet.Key, response.CustomPayload);
                        }

                        break;
                    }
                }
                _logger.LogInformation($"ExternalService:{serviceScopeSet.Key},Authorized:{response.Authorized}");
            }

            if (finalCustomPayload.Any())
            {
                _scopedOptionalClaims.Claims.Add(new Claim(
                                                     Constants.CustomPayload,
                                                     _serializer.Serialize(finalCustomPayload),
                                                     IdentityServerConstants.ClaimValueTypes.Json));
            }
            claims = new List <Claim>
            {
                // in this case we want to preserve that the original came from Constants.GrantType.TokenExchange
                new Claim(JwtClaimTypes.AuthenticationMethod, Constants.GrantType.TokenExchange)
            };

            context.Result = new GrantValidationResult(subject, GrantType, tokenIssuedAtTime, claims);
            _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.ExtensionGrantValidationContext, context);
            _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.OverrideTokenIssuedAtTime, tokenIssuedAtTime);
            return;
        }
        public async Task <IActionResult> AuthorizeDeviceAsync([FromBody] AuthorizeDeviceRequest data)
        {
            // validate client
            var clientResult = await _clientValidator.ValidateAsync(this.HttpContext);

            if (clientResult.Client == null)
            {
                return(Unauthorized(OidcConstants.TokenErrors.InvalidClient));
            }
            var client = clientResult.Client as ClientExtra;

            var grantType =
                clientResult.Client.AllowedGrantTypes.FirstOrDefault(agt => agt == OidcConstants.GrantTypes.DeviceCode);

            if (grantType == null)
            {
                return(Unauthorized(OidcConstants.TokenErrors.InvalidGrant));
            }

            var deviceAuth = await _deviceFlowStore.FindByUserCodeAsync(data.UserCode.Sha256());

            if (deviceAuth == null)
            {
                return(NotFound($"Invalid user code, Device authorization failure - user code is invalid"));
            }


            // VALIDATE if issuer must exist and must be allowed
            // -------------------------------------------------------------------
            var issuer = data.Issuer;

            if (!string.IsNullOrEmpty(data.Issuer))
            {
                issuer = issuer.ToLower();
                var foundIssuer = client.AllowedArbitraryIssuers.FirstOrDefault(x => x == issuer);
                if (string.IsNullOrWhiteSpace(foundIssuer))
                {
                    return(NotFound($"issuer:{issuer} is NOT in the AllowedArbitraryIssuers collection."));
                }
            }

            // VALIDATE if AccessTokenLifetime must exist and must be allowed
            // -------------------------------------------------------------------
            int lifetime = client.AccessTokenLifetime;

            if (data.AccessTokenLifetime != null)
            {
                var requestedAccessTokenLifetime = (int)data.AccessTokenLifetime;
                if (requestedAccessTokenLifetime > 0 && requestedAccessTokenLifetime <= client.AccessTokenLifetime)
                {
                    lifetime = requestedAccessTokenLifetime;
                }
                else
                {
                    return(NotFound($"AccessTokenLifetime:{requestedAccessTokenLifetime} is NOT in range 0-{client.AccessTokenLifetime}."));
                }
            }

            if (data.AccessTokenLifetime != null)
            {
                deviceAuth.Lifetime = (int)data.AccessTokenLifetime;
            }

            string subject = "";

            try
            {
                var validatedResult =
                    await _identityTokenValidator.ValidateIdTokenAsync(data.IdToken,
                                                                       _tokenExchangeOptions.AuthorityKey);

                if (validatedResult.IsError)
                {
                    throw new Exception(
                              $"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={data.IdToken}",
                              new Exception(validatedResult.Error));
                }

                subject = SubjectFromClaimsPrincipal(validatedResult.User);
                if (string.IsNullOrWhiteSpace(subject))
                {
                    throw new Exception(
                              $"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={data.IdToken}");
                }

                var claims = validatedResult.User.Claims.ToList();
                claims.Add(new Claim(JwtClaimTypes.AuthenticationTime,
                                     claims.FirstOrDefault(c => c.Type == JwtClaimTypes.IssuedAt).Value));
                claims.Add(new Claim(JwtClaimTypes.IdentityProvider, _tokenExchangeOptions.AuthorityKey));

                var newClaimsIdentity = new ClaimsIdentity(claims);
                var subjectPrincipal  = new ClaimsPrincipal(newClaimsIdentity);

                deviceAuth.Subject = subjectPrincipal;

                var requestedScopes = deviceAuth.RequestedScopes.ToList();
                var offlineAccess   =
                    requestedScopes.FirstOrDefault(x => x == IdentityServerConstants.StandardScopes.OfflineAccess);

                var allowedScopes      = new List <string>();
                var allowedClaims      = new List <Claim>();
                var finalCustomPayload = new Dictionary <string, object>();

                var consentAuthorizeResponseTasks = new List <Task <ConsentAuthorizeResponseContainer <PostContext> > >();
                var requestedServiceScopes        = GetServiceToScopesFromRequest(deviceAuth.RequestedScopes.ToList());
                foreach (var serviceScopeSet in requestedServiceScopes)
                {
                    var externalService =
                        await _externalServicesStore.GetExternalServiceByNameAsync(serviceScopeSet.Key);

                    if (externalService == null)
                    {
                        continue;
                    }

                    var discoCache =
                        await _consentDiscoveryCacheAccessor.GetConsentDiscoveryCacheAsync(serviceScopeSet.Key);

                    var doco = await discoCache.GetAsync();

                    if (doco.IsError)
                    {
                        _logger.LogError(doco.Error);
                        continue;
                    }
                    List <string> scopes = null;
                    switch (doco.AuthorizationType)
                    {
                    case Constants.AuthorizationTypes.Implicit:
                        scopes = null;
                        break;

                    case Constants.AuthorizationTypes.SubjectAndScopes:
                        scopes = serviceScopeSet.Value;
                        break;
                    }

                    if (doco.AuthorizationType == Constants.AuthorizationTypes.Implicit)
                    {
                        allowedScopes.AddRange(serviceScopeSet.Value);
                    }
                    else
                    {
                        var request = new ConsentAuthorizeRequest
                        {
                            AuthorizeType = doco.AuthorizationType,
                            Scopes        = scopes,
                            Subject       = subject,
                            Requester     = new ConsentAuthorizeRequest.ClientRequester()
                            {
                                ClientDescription = client.Description,
                                ClientId          = client.ClientId,
                                ClientName        = client.ClientName,
                                Namespace         = client.Namespace,
                                Tenant            = client.TenantName
                            }
                        };
                        // How to send many requests in parallel in ASP.Net Core
                        // https://www.michalbialecki.com/2018/04/19/how-to-send-many-requests-in-parallel-in-asp-net-core/
                        var task = _consentExternalService.PostAuthorizationRequestAsync(doco, request, new PostContext()
                        {
                            ServiceScopeSet   = serviceScopeSet,
                            DiscoveryDocument = doco
                        });
                        consentAuthorizeResponseTasks.Add(task);
                    }
                }
                var consentAuthorizeResponses = await Task.WhenAll(consentAuthorizeResponseTasks);

                foreach (var consentAuthorizeResponse in consentAuthorizeResponses)
                {
                    var response        = consentAuthorizeResponse.Response;
                    var serviceScopeSet = consentAuthorizeResponse.Context.ServiceScopeSet;
                    var doco            = consentAuthorizeResponse.Context.DiscoveryDocument;

                    if (response.Error != null)
                    {
                        _logger.LogError(response.Error.Message);
                        continue;
                    }
                    if (response.Authorized)
                    {
                        switch (doco.AuthorizationType)
                        {
                        case Constants.AuthorizationTypes.SubjectAndScopes:
                            // make sure no funny business is coming in from the auth call.
                            var serviceRoot = $"{_tokenExchangeOptions.BaseScope}{serviceScopeSet.Key}";
                            var query       = (from item in response.Scopes
                                               where item.StartsWith(serviceRoot)
                                               select item);
                            allowedScopes.AddRange(query);
                            if (response.Claims != null && response.Claims.Any())
                            {
                                foreach (var cac in response.Claims)
                                {
                                    // namespace the claims.
                                    allowedClaims.Add(new Claim($"{serviceScopeSet.Key}.{cac.Type}",
                                                                cac.Value));
                                }
                            }

                            if (response.CustomPayload != null)
                            {
                                finalCustomPayload.Add(serviceScopeSet.Key, response.CustomPayload);
                            }

                            break;
                        }
                    }
                }

                if (finalCustomPayload.Any())
                {
                    allowedClaims.Add(new Claim(
                                          Constants.CustomPayload,
                                          _serializer.Serialize(finalCustomPayload),
                                          IdentityServerConstants.ClaimValueTypes.Json));
                }

                deviceAuth.IsAuthorized = true;
                //  deviceAuth.SessionId = sid;
                if (!string.IsNullOrWhiteSpace(offlineAccess))
                {
                    allowedScopes.Add(offlineAccess);
                }

                deviceAuth.AuthorizedScopes = allowedScopes;

                var deviceExtra = _coreMapperAccessor.Mapper.Map <DeviceCodeExtra>(deviceAuth);
                deviceExtra.AuthorizedClaims    = allowedClaims;
                deviceExtra.Issuer              = issuer;
                deviceExtra.AccessTokenLifetime = lifetime;

                await _deviceFlowStore.UpdateByUserCodeAsync(data.UserCode.Sha256(), deviceExtra);

                return(Ok());
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
                return(StatusCode(500));
            }
        }