Beispiel #1
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 <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));
            }
        }