public async Task <IEndpointResult> ProcessAsync(HttpContext context)
        {
            _logger.LogTrace("Processing discovery request.");

            // validate HTTP
            if (!HttpMethods.IsGet(context.Request.Method))
            {
                _logger.LogWarning("Discovery endpoint only supports GET requests");
                return(new StatusCodeResult(HttpStatusCode.MethodNotAllowed));
            }

            _logger.LogDebug("Start discovery request");

            if (!_options.Endpoints.EnableDiscoveryEndpoint)
            {
                _logger.LogInformation("Discovery endpoint disabled. 404.");
                return(new StatusCodeResult(HttpStatusCode.NotFound));
            }

            var baseUrl   = context.GetIdentityServerBaseUrl().EnsureTrailingSlash();
            var issuerUri = await _issuerNameService.GetCurrentAsync();

            // generate response
            _logger.LogTrace("Calling into discovery response generator: {type}", _responseGenerator.GetType().FullName);
            var response = await _responseGenerator.CreateDiscoveryDocumentAsync(baseUrl, issuerUri);

            return(new DiscoveryDocumentResult(response, _options.Discovery.ResponseCacheInterval));
        }
        /// <summary>
        /// Invokes the middleware.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="router">The router.</param>
        /// <param name="session">The user session.</param>
        /// <param name="events">The event service.</param>
        /// <param name="issuerNameService">The issuer name service</param>
        /// <param name="backChannelLogoutService"></param>
        /// <returns></returns>
        public async Task Invoke(
            HttpContext context,
            IEndpointRouter router,
            IUserSession session,
            IEventService events,
            IIssuerNameService issuerNameService,
            IBackChannelLogoutService backChannelLogoutService)
        {
            // this will check the authentication session and from it emit the check session
            // cookie needed from JS-based signout clients.
            await session.EnsureSessionIdCookieAsync();

            context.Response.OnStarting(async() =>
            {
                if (context.GetSignOutCalled())
                {
                    _logger.LogDebug("SignOutCalled set; processing post-signout session cleanup.");

                    // this clears our session id cookie so JS clients can detect the user has signed out
                    await session.RemoveSessionIdCookieAsync();

                    // back channel logout
                    var logoutContext = await session.GetLogoutNotificationContext();
                    if (logoutContext != null)
                    {
                        await backChannelLogoutService.SendLogoutNotificationsAsync(logoutContext);
                    }
                }
            });

            try
            {
                var endpoint = router.Find(context);
                if (endpoint != null)
                {
                    LicenseValidator.ValidateIssuer(await issuerNameService.GetCurrentAsync());

                    _logger.LogInformation("Invoking IdentityServer endpoint: {endpointType} for {url}", endpoint.GetType().FullName, context.Request.Path.ToString());

                    var result = await endpoint.ProcessAsync(context);

                    if (result != null)
                    {
                        _logger.LogTrace("Invoking result: {type}", result.GetType().FullName);
                        await result.ExecuteAsync(context);
                    }

                    return;
                }
            }
            catch (Exception ex)
            {
                await events.RaiseAsync(new UnhandledExceptionEvent(ex));

                _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message);
                throw;
            }

            await _next(context);
        }
    /// <inheritdoc/>
    public async Task SendLoginRequestAsync(BackchannelUserLoginRequest request)
    {
        var url = await _issuerNameService.GetCurrentAsync();

        url += "/ciba?id=" + request.InternalId;
        _logger.LogWarning("IBackchannelAuthenticationUserNotificationService not implemented. But for testing, visit {url} to simulate what a user might need to do to complete the request.", url);
    }
Пример #4
0
    /// <inheritdoc />
    public async Task <string> StoreAsync(AuthenticationTicket ticket)
    {
        ArgumentNullException.ThrowIfNull(ticket);

        ticket.SetIssuer(await _issuerNameService.GetCurrentAsync());

        var key = CryptoRandom.CreateUniqueId(format: CryptoRandom.OutputFormat.Hex);

        _logger.LogDebug("Creating entry in store for AuthenticationTicket, key {key}, with expiration: {expiration}", key, ticket.GetExpiration());

        var session = new ServerSideSession
        {
            Key         = key,
            Scheme      = ticket.AuthenticationScheme,
            Created     = ticket.GetIssued(),
            Renewed     = ticket.GetIssued(),
            Expires     = ticket.GetExpiration(),
            SubjectId   = ticket.GetSubjectId(),
            SessionId   = ticket.GetSessionId(),
            DisplayName = ticket.GetDisplayName(_options.ServerSideSessions.UserDisplayNameClaimType),
            Ticket      = ticket.Serialize(_protector)
        };

        await _store.CreateSessionAsync(session);

        return(key);
    }
Пример #5
0
        private async Task <SecurityToken> CreateSecurityTokenAsync(SignInValidationResult result, ClaimsIdentity outgoingSubject)
        {
            var credentials = await _keys.GetSigningCredentialsAsync().ConfigureAwait(false);

            var x509Key = new X509SecurityKey(credentials.Key.GetX509Certificate(_keys));

            var relyingParty = result.RelyingParty;
            var descriptor   = new SecurityTokenDescriptor
            {
                Audience           = result.Client.ClientId,
                IssuedAt           = DateTime.UtcNow,
                NotBefore          = DateTime.UtcNow,
                Expires            = DateTime.UtcNow.AddSeconds(result.Client.IdentityTokenLifetime),
                SigningCredentials = new SigningCredentials(x509Key, relyingParty.SignatureAlgorithm, relyingParty.DigestAlgorithm),
                Subject            = outgoingSubject,
#if DUENDE
                Issuer = await _issuerNameService.GetCurrentAsync().ConfigureAwait(false),
#else
                Issuer = _contextAccessor.HttpContext.GetIdentityServerIssuerUri(),
#endif
            };

            if (result.RelyingParty.EncryptionCertificate != null)
            {
                descriptor.EncryptingCredentials = new X509EncryptingCredentials(result.RelyingParty.EncryptionCertificate);
            }

            var handler = CreateTokenHandler(result.RelyingParty.TokenType);

            return(handler.CreateToken(descriptor));
        }
Пример #6
0
        /// <summary>
        /// Create a personal access token for the current user and client
        /// </summary>
        /// <param name="context">The Http context</param>
        /// <param name="isRefenceToken">Creeate a reference token if true otherwise a JWT token</param>
        /// <param name="lifetimeDays">The token life time</param>
        /// <param name="apis">The list of audience</param>
        /// <param name="scopes">The list of scopes</param>
        /// <param name="claimTypes">The list of claims types</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">apis</exception>
        /// <exception cref="System.InvalidOperationException">Client id not found in user claims.
        /// or
        /// Client not found for client id '{clientId}'.
        /// or
        /// Api '{api}' not found in '{client.ClientName}' allowed scopes.
        /// or
        /// Scope '{scope}' not found in '{client.ClientName}' allowed scopes.</exception>
        public async Task <string> CreatePersonalAccessTokenAsync(HttpContext context,
                                                                  bool isRefenceToken,
                                                                  int lifetimeDays,
                                                                  IEnumerable <string> apis,
                                                                  IEnumerable <string> scopes,
                                                                  IEnumerable <string> claimTypes)
        {
            CheckParams(apis);

            scopes ??= Array.Empty <string>();
            claimTypes ??= Array.Empty <string>();

            claimTypes = claimTypes.Where(c => c != JwtClaimTypes.Name &&
                                          c != JwtClaimTypes.ClientId &&
                                          c != JwtClaimTypes.Subject);

            var user = new ClaimsPrincipal(new ClaimsIdentity(context.User.Claims.Select(c =>
            {
                if (JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.TryGetValue(c.Type, out string newType))
                {
                    return(new Claim(newType, c.Value));
                }
                return(c);
            }), "Bearer", JwtClaimTypes.Name, JwtClaimTypes.Role));

            var clientId = user.Claims.First(c => c.Type == JwtClaimTypes.ClientId).Value;

            await ValidateRequestAsync(apis, scopes, user, clientId).ConfigureAwait(false);

#if DUENDE
            var issuer = await _issuerNameService.GetCurrentAsync().ConfigureAwait(false);
#else
            var issuer = context.GetIdentityServerIssuerUri();
#endif
            var sub      = user.FindFirstValue(JwtClaimTypes.Subject) ?? user.FindFirstValue("nameid");
            var userName = user.Identity.Name;

            return(await _tokenService.CreateSecurityTokenAsync(new Token(IdentityServerConstants.TokenTypes.AccessToken)
            {
                AccessTokenType = isRefenceToken ? AccessTokenType.Reference : AccessTokenType.Jwt,
                Audiences = apis.ToArray(),
                ClientId = clientId,
                Claims = user.Claims.Where(c => claimTypes.Any(t => c.Type == t))
                         .Concat(new[]
                {
                    new Claim(JwtClaimTypes.Name, userName),
                    new Claim(JwtClaimTypes.ClientId, clientId),
                    new Claim(JwtClaimTypes.Subject, sub)
                })
                         .Concat(scopes.Select(s => new Claim("scope", s)))
                         .ToArray(),
                CreationTime = DateTime.UtcNow,
                Lifetime = Convert.ToInt32(TimeSpan.FromDays(lifetimeDays).TotalSeconds),
                Issuer = issuer
            }));
        }
        private async Task <string> CreateMessage(BackchannelUserLoginRequest request)
        {
            var client  = request.Client;
            var builder = new StringBuilder();

            if (client.LogoUri is not null)
            {
                builder.Append("<div><img src=\"");
                builder.Append(client.LogoUri);
                builder.Append("\"></div>");
            }

            builder.Append(_localizer["{0} is requesting your permission", client.ClientName]);

            if (!string.IsNullOrEmpty(request.BindingMessage))
            {
                builder.Append("<div>");
                builder.Append(_localizer["Verify that this identifier matches what the client is displaying: {0}", request.BindingMessage]);
                builder.Append("</div>");
            }

            builder.Append("<div>");
            builder.Append(_localizer[" Do you wish to continue?", client.ClientName]);
            builder.Append("</div>");

            var issuer = await _nameService.GetCurrentAsync().ConfigureAwait(false);

            if (!issuer.EndsWith('/'))
            {
                issuer += "/";
            }

            builder.Append("<div>");
            builder.Append("<a href =\"");
            builder.Append(issuer);
            builder.Append("ciba/consent?id=");
            builder.Append(request.InternalId);
            builder.Append("\">");
            builder.Append(_localizer["Yes, Continue"]);
            builder.Append("</a>");
            builder.Append("</div>");

            return(builder.ToString());
        }
Пример #8
0
        /// <summary>
        /// Issues a JWT.
        /// </summary>
        /// <param name="lifetime">The lifetime.</param>
        /// <param name="claims">The claims.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">claims</exception>
        public virtual async Task <string> IssueJwtAsync(int lifetime, IEnumerable <Claim> claims)
        {
            if (claims == null)
            {
                throw new ArgumentNullException(nameof(claims));
            }

            var issuer = await IssuerNameService.GetCurrentAsync();

            var token = new Token
            {
                CreationTime = _clock.UtcNow.UtcDateTime,
                Issuer       = issuer,
                Lifetime     = lifetime,

                Claims = new HashSet <Claim>(claims, new ClaimComparer())
            };

            return(await _tokenCreation.CreateTokenAsync(token));
        }
        /// <summary>
        /// Generates the asynchronous.
        /// </summary>
        /// <param name="wsfedEndpoint">The wsfed endpoint.</param>
        /// <returns></returns>
        public async Task <WsFederationConfiguration> GenerateAsync(string wsfedEndpoint)
        {
            var credentials = await _keys.GetSigningCredentialsAsync().ConfigureAwait(false);

            var key     = credentials.Key;
            var keyInfo = new KeyInfo(key.GetX509Certificate(_keys));

#if DUENDE
            var issuer = await _issuerNameService.GetCurrentAsync().ConfigureAwait(false);
#else
            var issuer = _contextAccessor.HttpContext.GetIdentityServerIssuerUri();
#endif
            var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);
            var config             = new WsFederationConfiguration()
            {
                Issuer             = issuer,
                TokenEndpoint      = wsfedEndpoint,
                SigningCredentials = signingCredentials,
            };
            config.SigningKeys.Add(key);
            config.KeyInfos.Add(keyInfo);

            return(config);
        }
Пример #10
0
        internal async Task <KeyContainer> CreateAndStoreNewKeyAsync(SigningAlgorithmOptions alg)
        {
            _logger.LogDebug("Creating new key.");

            var now = _clock.UtcNow.UtcDateTime;
            var iss = await _issuerNameService.GetCurrentAsync();

            KeyContainer container = null;

            if (alg.IsRsaKey)
            {
                var rsa = CryptoHelper.CreateRsaSecurityKey(_options.RsaKeySize);

                container = alg.UseX509Certificate ?
                            new X509KeyContainer(rsa, alg.Name, now, _options.KeyRetirementAge, iss) :
                            (KeyContainer) new RsaKeyContainer(rsa, alg.Name, now);
            }
            else if (alg.IsEcKey)
            {
                var ec = CryptoHelper.CreateECDsaSecurityKey(CryptoHelper.GetCurveNameFromSigningAlgorithm(alg.Name));
                // X509 certs don't currently work with EC keys.
                container = //_options.WrapKeysInX509Certificate ? //new X509KeyContainer(ec, alg, now, _options.KeyRetirementAge, iss) :
                            (KeyContainer) new EcKeyContainer(ec, alg.Name, now);
            }
            else
            {
                throw new Exception($"Invalid alg '{alg}'");
            }

            var key = _protector.Protect(container);
            await _store.StoreKeyAsync(key);

            _logger.LogInformation("Created and stored new key with kid {kid}.", container.Id);

            return(container);
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentException">ParsedSecret.Credential is not a JWT token</exception>
        public async Task <SecretValidationResult> ValidateAsync(IEnumerable <Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = new SecretValidationResult {
                Success = false
            };
            var success = new SecretValidationResult {
                Success = true
            };

            if (parsedSecret.Type != IdentityServerConstants.ParsedSecretTypes.JwtBearer)
            {
                return(fail);
            }

            if (!(parsedSecret.Credential is string jwtTokenString))
            {
                _logger.LogError("ParsedSecret.Credential is not a string.");
                return(fail);
            }

            List <SecurityKey> trustedKeys;

            try
            {
                trustedKeys = await secrets.GetKeysAsync();
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Could not parse secrets");
                return(fail);
            }

            if (!trustedKeys.Any())
            {
                _logger.LogError("There are no keys available to validate client assertion.");
                return(fail);
            }

            var validAudiences = new[]
            {
                // token endpoint URL
                string.Concat((await _issuerNameService.GetCurrentAsync()).EnsureTrailingSlash(),
                              Constants.ProtocolRoutePaths.Token)
            };

            var tokenValidationParameters = new TokenValidationParameters
            {
                IssuerSigningKeys        = trustedKeys,
                ValidateIssuerSigningKey = true,

                ValidIssuer    = parsedSecret.Id,
                ValidateIssuer = true,

                ValidAudiences   = validAudiences,
                ValidateAudience = true,

                RequireSignedTokens   = true,
                RequireExpirationTime = true,

                ClockSkew = TimeSpan.FromMinutes(5)
            };

            try
            {
                var handler = new JwtSecurityTokenHandler();
                handler.ValidateToken(jwtTokenString, tokenValidationParameters, out var token);

                var jwtToken = (JwtSecurityToken)token;
                if (jwtToken.Subject != jwtToken.Issuer)
                {
                    _logger.LogError("Both 'sub' and 'iss' in the client assertion token must have a value of client_id.");
                    return(fail);
                }

                var exp = jwtToken.Payload.Exp;
                if (!exp.HasValue)
                {
                    _logger.LogError("exp is missing.");
                    return(fail);
                }

                var jti = jwtToken.Payload.Jti;
                if (jti.IsMissing())
                {
                    _logger.LogError("jti is missing.");
                    return(fail);
                }

                if (await _replayCache.ExistsAsync(Purpose, jti))
                {
                    _logger.LogError("jti is found in replay cache. Possible replay attack.");
                    return(fail);
                }
                else
                {
                    await _replayCache.AddAsync(Purpose, jti, DateTimeOffset.FromUnixTimeSeconds(exp.Value).AddMinutes(5));
                }

                return(success);
            }
            catch (Exception e)
            {
                _logger.LogError(e, "JWT token validation error");
                return(fail);
            }
        }
Пример #12
0
    /// <summary>
    /// Validates a secret
    /// </summary>
    /// <param name="secrets">The stored secrets.</param>
    /// <param name="parsedSecret">The received secret.</param>
    /// <returns>
    /// A validation result
    /// </returns>
    /// <exception cref="System.ArgumentException">ParsedSecret.Credential is not a JWT token</exception>
    public async Task <SecretValidationResult> ValidateAsync(IEnumerable <Secret> secrets, ParsedSecret parsedSecret)
    {
        var fail = new SecretValidationResult {
            Success = false
        };
        var success = new SecretValidationResult {
            Success = true
        };

        if (parsedSecret.Type != IdentityServerConstants.ParsedSecretTypes.JwtBearer)
        {
            return(fail);
        }

        if (!(parsedSecret.Credential is string jwtTokenString))
        {
            _logger.LogError("ParsedSecret.Credential is not a string.");
            return(fail);
        }

        List <SecurityKey> trustedKeys;

        try
        {
            trustedKeys = await secrets.GetKeysAsync();
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Could not parse secrets");
            return(fail);
        }

        if (!trustedKeys.Any())
        {
            _logger.LogError("There are no keys available to validate client assertion.");
            return(fail);
        }

        var validAudiences = new[]
        {
            // token endpoint URL
            string.Concat(_urls.BaseUrl.EnsureTrailingSlash(), Constants.ProtocolRoutePaths.Token),
            // TODO: remove the issuer URL in a future major release?
            // issuer URL
            string.Concat((await _issuerNameService.GetCurrentAsync()).EnsureTrailingSlash(), Constants.ProtocolRoutePaths.Token)
        }.Distinct();

        var tokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKeys        = trustedKeys,
            ValidateIssuerSigningKey = true,

            ValidIssuer    = parsedSecret.Id,
            ValidateIssuer = true,

            ValidAudiences   = validAudiences,
            ValidateAudience = true,

            RequireSignedTokens   = true,
            RequireExpirationTime = true,

            ClockSkew = TimeSpan.FromMinutes(5)
        };

        var handler = new JsonWebTokenHandler()
        {
            MaximumTokenSizeInBytes = _options.InputLengthRestrictions.Jwt
        };
        var result = handler.ValidateToken(jwtTokenString, tokenValidationParameters);

        if (!result.IsValid)
        {
            _logger.LogError(result.Exception, "JWT token validation error");
            return(fail);
        }

        var jwtToken = (JsonWebToken)result.SecurityToken;

        if (jwtToken.Subject != jwtToken.Issuer)
        {
            _logger.LogError("Both 'sub' and 'iss' in the client assertion token must have a value of client_id.");
            return(fail);
        }

        var exp = jwtToken.ValidTo;

        if (exp == DateTime.MinValue)
        {
            _logger.LogError("exp is missing.");
            return(fail);
        }

        var jti = jwtToken.Id;

        if (jti.IsMissing())
        {
            _logger.LogError("jti is missing.");
            return(fail);
        }

        if (await _replayCache.ExistsAsync(Purpose, jti))
        {
            _logger.LogError("jti is found in replay cache. Possible replay attack.");
            return(fail);
        }
        else
        {
            await _replayCache.AddAsync(Purpose, jti, exp.AddMinutes(5));
        }

        return(success);
    }
Пример #13
0
        public async Task <AuthorizeRequestValidationResult> ValidateAsync(NameValueCollection parameters, ClaimsPrincipal subject = null)
        {
            _logger.LogDebug("Start authorize request protocol validation");

            var request = new ValidatedAuthorizeRequest
            {
                Options    = _options,
                IssuerName = await _issuerNameService.GetCurrentAsync(),
                Subject    = subject ?? Principal.Anonymous,
                Raw        = parameters ?? throw new ArgumentNullException(nameof(parameters))
            };

            // load client_id
            // client_id must always be present on the request
            var loadClientResult = await LoadClientAsync(request);

            if (loadClientResult.IsError)
            {
                return(loadClientResult);
            }

            // load request object
            var roLoadResult = await LoadRequestObjectAsync(request);

            if (roLoadResult.IsError)
            {
                return(roLoadResult);
            }

            // validate request object
            var roValidationResult = await ValidateRequestObjectAsync(request);

            if (roValidationResult.IsError)
            {
                return(roValidationResult);
            }

            // validate client_id and redirect_uri
            var clientResult = await ValidateClientAsync(request);

            if (clientResult.IsError)
            {
                return(clientResult);
            }

            // state, response_type, response_mode
            var mandatoryResult = ValidateCoreParameters(request);

            if (mandatoryResult.IsError)
            {
                return(mandatoryResult);
            }

            // scope, scope restrictions and plausability, and resource indicators
            var scopeResult = await ValidateScopeAndResourceAsync(request);

            if (scopeResult.IsError)
            {
                return(scopeResult);
            }

            // nonce, prompt, acr_values, login_hint etc.
            var optionalResult = await ValidateOptionalParametersAsync(request);

            if (optionalResult.IsError)
            {
                return(optionalResult);
            }

            // custom validator
            _logger.LogDebug("Calling into custom validator: {type}", _customValidator.GetType().FullName);
            var context = new CustomAuthorizeRequestValidationContext
            {
                Result = new AuthorizeRequestValidationResult(request)
            };
            await _customValidator.ValidateAsync(context);

            var customResult = context.Result;

            if (customResult.IsError)
            {
                LogError("Error in custom validation", customResult.Error, request);
                return(Invalid(request, customResult.Error, customResult.ErrorDescription));
            }

            _logger.LogTrace("Authorize request protocol validation successful");

            LicenseValidator.ValidateClient(request.ClientId);

            return(Valid(request));
        }
Пример #14
0
    private async Task <TokenValidationResult> ValidateJwtAsync(string jwtString,
                                                                IEnumerable <SecurityKeyInfo> validationKeys, bool validateLifetime = true, string audience = null)
    {
        using var activity = Tracing.BasicActivitySource.StartActivity("TokenValidator.ValidateJwt");

        var handler = new JsonWebTokenHandler();

        var parameters = new TokenValidationParameters
        {
            ValidIssuer       = await _issuerNameService.GetCurrentAsync(),
            IssuerSigningKeys = validationKeys.Select(k => k.Key),
            ValidateLifetime  = validateLifetime
        };

        if (audience.IsPresent())
        {
            parameters.ValidAudience = audience;
        }
        else
        {
            parameters.ValidateAudience = false;

            // if no audience is specified, we make at least sure that it is an access token
            if (_options.AccessTokenJwtType.IsPresent())
            {
                parameters.ValidTypes = new[] { _options.AccessTokenJwtType };
            }
        }

        var result = handler.ValidateToken(jwtString, parameters);

        if (!result.IsValid)
        {
            if (result.Exception is SecurityTokenExpiredException expiredException)
            {
                _logger.LogInformation(expiredException, "JWT token validation error: {exception}",
                                       expiredException.Message);
                return(Invalid(OidcConstants.ProtectedResourceErrors.ExpiredToken));
            }
            else
            {
                _logger.LogError(result.Exception, "JWT token validation error: {exception}",
                                 result.Exception.Message);
                return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken));
            }
        }

        var id = result.ClaimsIdentity;

        // if access token contains an ID, log it
        var jwtId = id.FindFirst(JwtClaimTypes.JwtId);

        if (jwtId != null)
        {
            _log.JwtId = jwtId.Value;
        }

        // load the client that belongs to the client_id claim
        Client client   = null;
        var    clientId = id.FindFirst(JwtClaimTypes.ClientId);

        if (clientId != null)
        {
            client = await _clients.FindEnabledClientByIdAsync(clientId.Value);

            if (client == null)
            {
                LogError($"Client deleted or disabled: {clientId}");
                return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken));
            }
        }

        var claims = id.Claims.ToList();

        // check the scope format (array vs space delimited string)
        var scopes = claims.Where(c => c.Type == JwtClaimTypes.Scope).ToArray();

        if (scopes.Any())
        {
            foreach (var scope in scopes)
            {
                if (scope.Value.Contains(" "))
                {
                    claims.Remove(scope);

                    var values = scope.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                    foreach (var value in values)
                    {
                        claims.Add(new Claim(JwtClaimTypes.Scope, value));
                    }
                }
            }
        }

        return(new TokenValidationResult
        {
            IsError = false,

            Claims = claims,
            Client = client,
            Jwt = jwtString
        });
    }
Пример #15
0
    /// <summary>
    /// Invokes the middleware.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="router">The router.</param>
    /// <param name="userSession">The user session.</param>
    /// <param name="events">The event service.</param>
    /// <param name="issuerNameService">The issuer name service</param>
    /// <param name="sessionCoordinationService"></param>
    /// <returns></returns>
    public async Task Invoke(
        HttpContext context,
        IEndpointRouter router,
        IUserSession userSession,
        IEventService events,
        IIssuerNameService issuerNameService,
        ISessionCoordinationService sessionCoordinationService)
    {
        // this will check the authentication session and from it emit the check session
        // cookie needed from JS-based signout clients.
        await userSession.EnsureSessionIdCookieAsync();

        context.Response.OnStarting(async() =>
        {
            if (context.GetSignOutCalled())
            {
                _logger.LogDebug("SignOutCalled set; processing post-signout session cleanup.");

                // this clears our session id cookie so JS clients can detect the user has signed out
                await userSession.RemoveSessionIdCookieAsync();

                var user = await userSession.GetUserAsync();
                if (user != null)
                {
                    var session = new UserSession
                    {
                        SubjectId   = user.GetSubjectId(),
                        SessionId   = await userSession.GetSessionIdAsync(),
                        DisplayName = user.GetDisplayName(),
                        ClientIds   = (await userSession.GetClientListAsync()).ToList(),
                        Issuer      = await issuerNameService.GetCurrentAsync()
                    };
                    await sessionCoordinationService.ProcessLogoutAsync(session);
                }
            }
        });

        try
        {
            var endpoint = router.Find(context);
            if (endpoint != null)
            {
                var endpointType = endpoint.GetType().FullName;

                using var activity = Tracing.BasicActivitySource.StartActivity("IdentityServerProtocolRequest");
                activity?.SetTag(Tracing.Properties.EndpointType, endpointType);

                LicenseValidator.ValidateIssuer(await issuerNameService.GetCurrentAsync());

                _logger.LogInformation("Invoking IdentityServer endpoint: {endpointType} for {url}", endpointType, context.Request.Path.ToString());

                var result = await endpoint.ProcessAsync(context);

                if (result != null)
                {
                    _logger.LogTrace("Invoking result: {type}", result.GetType().FullName);
                    await result.ExecuteAsync(context);
                }

                return;
            }
        }
        catch (Exception ex)
        {
            await events.RaiseAsync(new UnhandledExceptionEvent(ex));

            _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message);
            throw;
        }

        await _next(context);
    }
        private async Task <TokenValidationResult> ValidateJwtAsync(string jwt, IEnumerable <SecurityKeyInfo> validationKeys, bool validateLifetime = true, string audience = null)
        {
            var handler = new JwtSecurityTokenHandler();

            handler.InboundClaimTypeMap.Clear();

            var parameters = new TokenValidationParameters
            {
                ValidIssuer       = await _issuerNameService.GetCurrentAsync(),
                IssuerSigningKeys = validationKeys.Select(k => k.Key),
                ValidateLifetime  = validateLifetime
            };

            if (audience.IsPresent())
            {
                parameters.ValidAudience = audience;
            }
            else
            {
                parameters.ValidateAudience = false;
            }

            try
            {
                var id = handler.ValidateToken(jwt, parameters, out var securityToken);
                var jwtSecurityToken = securityToken as JwtSecurityToken;

                // if no audience is specified, we make at least sure that it is an access token
                if (audience.IsMissing())
                {
                    if (_options.AccessTokenJwtType.IsPresent())
                    {
                        var type = jwtSecurityToken.Header.Typ;
                        if (!string.Equals(type, _options.AccessTokenJwtType))
                        {
                            return(new TokenValidationResult
                            {
                                IsError = true,
                                Error = "invalid JWT token type"
                            });
                        }
                    }
                }

                // if access token contains an ID, log it
                var jwtId = id.FindFirst(JwtClaimTypes.JwtId);
                if (jwtId != null)
                {
                    _log.JwtId = jwtId.Value;
                }

                // load the client that belongs to the client_id claim
                Client client   = null;
                var    clientId = id.FindFirst(JwtClaimTypes.ClientId);
                if (clientId != null)
                {
                    client = await _clients.FindEnabledClientByIdAsync(clientId.Value);

                    if (client == null)
                    {
                        throw new InvalidOperationException("Client does not exist anymore.");
                    }
                }

                var claims = id.Claims.ToList();

                // check the scope format (array vs space delimited string)
                var scopes = claims.Where(c => c.Type == JwtClaimTypes.Scope).ToArray();
                if (scopes.Any())
                {
                    foreach (var scope in scopes)
                    {
                        if (scope.Value.Contains(" "))
                        {
                            claims.Remove(scope);

                            var values = scope.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries);
                            foreach (var value in values)
                            {
                                claims.Add(new Claim(JwtClaimTypes.Scope, value));
                            }
                        }
                    }
                }

                return(new TokenValidationResult
                {
                    IsError = false,

                    Claims = claims,
                    Client = client,
                    Jwt = jwt
                });
            }
            catch (SecurityTokenExpiredException expiredException)
            {
                _logger.LogInformation(expiredException, "JWT token validation error: {exception}", expiredException.Message);
                return(Invalid(OidcConstants.ProtectedResourceErrors.ExpiredToken));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "JWT token validation error: {exception}", ex.Message);
                return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken));
            }
        }
    /// <summary>
    /// Validates the request.
    /// </summary>
    /// <param name="parameters">The parameters.</param>
    /// <param name="clientValidationResult">The client validation result.</param>
    /// <returns></returns>
    /// <exception cref="System.ArgumentNullException">
    /// parameters
    /// or
    /// client
    /// </exception>
    public async Task <TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult)
    {
        using var activity = Tracing.BasicActivitySource.StartActivity("TokenRequestValidator.ValidateRequest");

        _logger.LogDebug("Start token request validation");

        _validatedRequest = new ValidatedTokenRequest
        {
            IssuerName                          = await _issuerNameService.GetCurrentAsync(),
            Raw                                 = parameters ?? throw new ArgumentNullException(nameof(parameters)),
                                        Options = _options
        };

        if (clientValidationResult == null)
        {
            throw new ArgumentNullException(nameof(clientValidationResult));
        }

        _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation);

        /////////////////////////////////////////////
        // check client protocol type
        /////////////////////////////////////////////
        if (_validatedRequest.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect)
        {
            LogError("Invalid protocol type for client",
                     new
            {
                clientId             = _validatedRequest.Client.ClientId,
                expectedProtocolType = IdentityServerConstants.ProtocolTypes.OpenIdConnect,
                actualProtocolType   = _validatedRequest.Client.ProtocolType
            });

            return(Invalid(OidcConstants.TokenErrors.InvalidClient));
        }

        /////////////////////////////////////////////
        // check grant type
        /////////////////////////////////////////////
        var grantType = parameters.Get(OidcConstants.TokenRequest.GrantType);

        if (grantType.IsMissing())
        {
            LogError("Grant type is missing");
            return(Invalid(OidcConstants.TokenErrors.UnsupportedGrantType));
        }

        if (grantType.Length > _options.InputLengthRestrictions.GrantType)
        {
            LogError("Grant type is too long");
            return(Invalid(OidcConstants.TokenErrors.UnsupportedGrantType));
        }

        _validatedRequest.GrantType = grantType;

        //////////////////////////////////////////////////////////
        // check for resource indicator and basic formatting
        //////////////////////////////////////////////////////////
        var resourceIndicators = parameters.GetValues(OidcConstants.TokenRequest.Resource) ?? Enumerable.Empty <string>();

        if (resourceIndicators?.Any(x => x.Length > _options.InputLengthRestrictions.ResourceIndicatorMaxLength) == true)
        {
            return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator maximum length exceeded"));
        }

        if (!resourceIndicators.AreValidResourceIndicatorFormat(_logger))
        {
            return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator format"));
        }

        if (resourceIndicators.Count() > 1)
        {
            return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Multiple resource indicators not supported on token endpoint."));
        }

        _validatedRequest.RequestedResourceIndicator = resourceIndicators.SingleOrDefault();


        //////////////////////////////////////////////////////////
        // run specific logic for grants
        //////////////////////////////////////////////////////////

        switch (grantType)
        {
        case OidcConstants.GrantTypes.AuthorizationCode:
            return(await RunValidationAsync(ValidateAuthorizationCodeRequestAsync, parameters));

        case OidcConstants.GrantTypes.ClientCredentials:
            return(await RunValidationAsync(ValidateClientCredentialsRequestAsync, parameters));

        case OidcConstants.GrantTypes.Password:
            return(await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters));

        case OidcConstants.GrantTypes.RefreshToken:
            return(await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters));

        case OidcConstants.GrantTypes.DeviceCode:
            return(await RunValidationAsync(ValidateDeviceCodeRequestAsync, parameters));

        case OidcConstants.GrantTypes.Ciba:
            return(await RunValidationAsync(ValidateCibaRequestRequestAsync, parameters));

        default:
            return(await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters));
        }
    }
        /// <inheritdoc/>
        public async Task <IEnumerable <string> > GetFrontChannelLogoutNotificationsUrlsAsync(LogoutNotificationContext context)
        {
            var frontChannelUrls = new List <string>();

            foreach (var clientId in context.ClientIds)
            {
                var client = await _clientStore.FindEnabledClientByIdAsync(clientId);

                if (client != null)
                {
                    if (client.FrontChannelLogoutUri.IsPresent())
                    {
                        var url = client.FrontChannelLogoutUri;

                        // add session id if required
                        if (client.ProtocolType == IdentityServerConstants.ProtocolTypes.OpenIdConnect)
                        {
                            if (client.FrontChannelLogoutSessionRequired)
                            {
                                url = url.AddQueryString(OidcConstants.EndSessionRequest.Sid, context.SessionId);
                                url = url.AddQueryString(OidcConstants.EndSessionRequest.Issuer, await _issuerNameService.GetCurrentAsync());
                            }
                        }
                        else if (client.ProtocolType == IdentityServerConstants.ProtocolTypes.WsFederation)
                        {
                            url = url.AddQueryString(Constants.WsFedSignOut.LogoutUriParameterName, Constants.WsFedSignOut.LogoutUriParameterValue);
                        }

                        frontChannelUrls.Add(url);
                    }
                }
            }

            if (frontChannelUrls.Any())
            {
                var msg = frontChannelUrls.Aggregate((x, y) => x + ", " + y);
                _logger.LogDebug("Client front-channel logout URLs: {0}", msg);
            }
            else
            {
                _logger.LogDebug("No client front-channel logout URLs");
            }

            return(frontChannelUrls);
        }