Search in sources :

Example 1 with UnauthorizedException

use of oidc.exceptions.UnauthorizedException in project OpenConext-oidcng by OpenConext.

the class IntrospectEndpoint method introspect.

@PostMapping(value = { "oidc/introspect" }, consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ResponseEntity<Map<String, Object>> introspect(HttpServletRequest request) throws ParseException, IOException, java.text.ParseException {
    HTTPRequest httpRequest = ServletUtils.createHTTPRequest(request);
    TokenIntrospectionRequest tokenIntrospectionRequest = TokenIntrospectionRequest.parse(httpRequest);
    ClientAuthentication clientAuthentication = tokenIntrospectionRequest.getClientAuthentication();
    String accessTokenValue = tokenIntrospectionRequest.getToken().getValue();
    // https://tools.ietf.org/html/rfc7662 is vague about the authorization requirements, but we enforce basic auth
    if (!(clientAuthentication instanceof PlainClientSecret)) {
        LOG.warn("No authentication present");
        throw new UnauthorizedException("Invalid user / secret");
    }
    String clientId = clientAuthentication.getClientID().getValue();
    OpenIDClient resourceServer = openIDClientRepository.findOptionalByClientId(clientId).orElseThrow(() -> new UnknownClientException(clientId));
    MDCContext.mdcContext("action", "Introspect", "rp", resourceServer.getClientId(), "accessTokenValue", accessTokenValue);
    if (!secretsMatch((PlainClientSecret) clientAuthentication, resourceServer)) {
        LOG.warn("Secret does not match for RS " + resourceServer.getClientId());
        throw new UnauthorizedException("Invalid user / secret");
    }
    if (!resourceServer.isResourceServer()) {
        LOG.warn("RS required for not configured for RP " + resourceServer.getClientId());
        throw new UnauthorizedException("Requires ResourceServer");
    }
    Optional<SignedJWT> optionalSignedJWT = tokenGenerator.parseAndValidateSignedJWT(accessTokenValue);
    if (!optionalSignedJWT.isPresent()) {
        LOG.warn("Invalid access_token " + accessTokenValue);
        return ResponseEntity.ok(Collections.singletonMap("active", false));
    }
    SignedJWT signedJWT = optionalSignedJWT.get();
    String jwtId = signedJWT.getJWTClaimsSet().getJWTID();
    Optional<AccessToken> optionalAccessToken = accessTokenRepository.findByJwtId(jwtId);
    if (!optionalAccessToken.isPresent()) {
        LOG.warn("No access_token found " + accessTokenValue);
        return ResponseEntity.ok(Collections.singletonMap("active", false));
    }
    AccessToken accessToken = optionalAccessToken.get();
    if (accessToken.isExpired(Clock.systemDefaultZone())) {
        LOG.warn("Access token is expired " + accessTokenValue);
        return ResponseEntity.ok(Collections.singletonMap("active", false));
    }
    List<String> scopes = accessToken.getScopes();
    Map<String, Object> result = new TreeMap<>();
    boolean isUserAccessToken = !accessToken.isClientCredentials();
    if (isUserAccessToken) {
        OpenIDClient openIDClient = openIDClientRepository.findOptionalByClientId(accessToken.getClientId()).orElseThrow(() -> new UnknownClientException(accessToken.getClientId()));
        if (!openIDClient.getClientId().equals(resourceServer.getClientId()) && !openIDClient.getAllowedResourceServers().contains(resourceServer.getClientId())) {
            throw new UnauthorizedException(String.format("RP %s is not allowed to use the API of resource server %s. Allowed resource servers are %s", accessToken.getClientId(), resourceServer.getClientId(), openIDClient.getAllowedResourceServers()));
        }
        User user = tokenGenerator.decryptAccessTokenWithEmbeddedUserInfo(signedJWT);
        result.put("updated_at", user.getUpdatedAt());
        if (resourceServer.isIncludeUnspecifiedNameID()) {
            result.put("unspecified_id", user.getUnspecifiedNameId());
        }
        result.put("authenticating_authority", user.getAuthenticatingAuthority());
        result.put("sub", user.getSub());
        result.putAll(user.getAttributes());
        List<String> acrClaims = user.getAcrClaims();
        if (!CollectionUtils.isEmpty(acrClaims)) {
            result.put("acr", String.join(" ", acrClaims));
        }
        boolean validPseudonymisation = validPseudonymisation(result, resourceServer, openIDClient);
        if (!validPseudonymisation && enforceEduidResourceServerLinkedAccount) {
            LOG.warn(String.format("Pseudonymisation failed. No eduperson_principal_name for RS %s", resourceServer.getClientId()));
            return ResponseEntity.ok(Collections.singletonMap("active", false));
        }
    }
    // The following claims can not be overridden by the
    result.put("active", true);
    result.put("scope", String.join(" ", scopes));
    result.put("client_id", accessToken.getClientId());
    result.put("exp", accessToken.getExpiresIn().getTime() / 1000L);
    result.put("sub", accessToken.getSub());
    result.put("iss", issuer);
    result.put("token_type", "Bearer");
    LOG.debug(String.format("Returning introspect active %s for RS %s", true, resourceServer.getClientId()));
    return ResponseEntity.ok(result);
}
Also used : HTTPRequest(com.nimbusds.oauth2.sdk.http.HTTPRequest) User(oidc.model.User) UnknownClientException(oidc.exceptions.UnknownClientException) OpenIDClient(oidc.model.OpenIDClient) TokenIntrospectionRequest(com.nimbusds.oauth2.sdk.TokenIntrospectionRequest) SignedJWT(com.nimbusds.jwt.SignedJWT) TreeMap(java.util.TreeMap) PlainClientSecret(com.nimbusds.oauth2.sdk.auth.PlainClientSecret) AccessToken(oidc.model.AccessToken) UnauthorizedException(oidc.exceptions.UnauthorizedException) ClientAuthentication(com.nimbusds.oauth2.sdk.auth.ClientAuthentication) PostMapping(org.springframework.web.bind.annotation.PostMapping)

Example 2 with UnauthorizedException

use of oidc.exceptions.UnauthorizedException in project OpenConext-oidcng by OpenConext.

the class TokenEndpoint method handleAuthorizationCodeGrant.

private ResponseEntity handleAuthorizationCodeGrant(AuthorizationCodeGrant authorizationCodeGrant, OpenIDClient client) {
    String code = authorizationCodeGrant.getAuthorizationCode().getValue();
    MDCContext.mdcContext("code", "code");
    AuthorizationCode authorizationCode = concurrentAuthorizationCodeRepository.findByCodeNotAlreadyUsedAndMarkAsUsed(code);
    if (authorizationCode == null) {
        /*
             * Now it become's tricky. Did we get an 'null' because the code was bogus or because it was already
             * used? To both satisfy the - highly theoretical - risk of the audit race condition and the OIDC certification
             * demand of deleting access_token issued with the re-used authorization code we need to query again.
             *
             * If they code was bogus this will result in a 404 exception by the authorizationCodeRepository#findByCode
             * and if we find something then we know there was a re-use issue.
             */
        AuthorizationCode byCode = authorizationCodeRepository.findByCode(code);
        accessTokenRepository.deleteByAuthorizationCodeId(byCode.getId());
        throw new TokenAlreadyUsedException("Authorization code already used");
    }
    if (!authorizationCode.getClientId().equals(client.getClientId())) {
        throw new UnauthorizedException("Client is not authorized for the authorization code");
    }
    if (authorizationCodeGrant.getRedirectionURI() != null && !authorizationCodeGrant.getRedirectionURI().toString().equals(authorizationCode.getRedirectUri())) {
        throw new RedirectMismatchException("Redirects do not match");
    }
    if (authorizationCode.isRedirectURIProvided() && authorizationCodeGrant.getRedirectionURI() == null) {
        throw new RedirectMismatchException("Redirect URI is mandatory if specified in code request");
    }
    if (authorizationCode.isExpired(Clock.systemDefaultZone())) {
        throw new UnauthorizedException("Authorization code expired");
    }
    CodeVerifier codeVerifier = authorizationCodeGrant.getCodeVerifier();
    String codeChallenge = authorizationCode.getCodeChallenge();
    if (codeVerifier != null) {
        if (codeChallenge == null) {
            throw new CodeVerifierMissingException("code_verifier present, but no code_challenge in the authorization_code");
        }
        CodeChallengeMethod codeChallengeMethod = CodeChallengeMethod.parse(authorizationCode.getCodeChallengeMethod());
        CodeChallenge computed = CodeChallenge.compute(codeChallengeMethod, codeVerifier);
        // Constant time comparison
        if (!MessageDigest.isEqual(codeChallenge.getBytes(), computed.getValue().getBytes())) {
            LOG.error(String.format("CodeVerifier %s with method %s does not match codeChallenge %s. Expected codeChallenge is %s", codeVerifier.getValue(), codeChallengeMethod, codeChallenge, computed.getValue()));
            throw new CodeVerifierMissingException("code_verifier does not match code_challenge");
        }
    }
    User user = userRepository.findUserBySub(authorizationCode.getSub());
    MDCContext.mdcContext(user);
    // User information is encrypted in access token
    LOG.debug("Deleting user " + user.getSub());
    userRepository.delete(user);
    Map<String, Object> body = tokenEndpointResponse(Optional.of(user), client, authorizationCode.getScopes(), authorizationCode.getIdTokenClaims(), false, authorizationCode.getNonce(), Optional.of(authorizationCode.getAuthTime()), Optional.of(authorizationCode.getId()));
    return new ResponseEntity<>(body, responseHttpHeaders, HttpStatus.OK);
}
Also used : AuthorizationCode(oidc.model.AuthorizationCode) User(oidc.model.User) CodeChallengeMethod(com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod) CodeVerifierMissingException(oidc.exceptions.CodeVerifierMissingException) TokenAlreadyUsedException(oidc.exceptions.TokenAlreadyUsedException) CodeVerifier(com.nimbusds.oauth2.sdk.pkce.CodeVerifier) ResponseEntity(org.springframework.http.ResponseEntity) UnauthorizedException(oidc.exceptions.UnauthorizedException) RedirectMismatchException(oidc.exceptions.RedirectMismatchException) CodeChallenge(com.nimbusds.oauth2.sdk.pkce.CodeChallenge)

Example 3 with UnauthorizedException

use of oidc.exceptions.UnauthorizedException in project OpenConext-oidcng by OpenConext.

the class TokenEndpoint method token.

@PostMapping(value = "oidc/token", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE })
public ResponseEntity token(HttpServletRequest request) throws IOException, ParseException, JOSEException, java.text.ParseException, CertificateException, BadJOSEException {
    HTTPRequest httpRequest = ServletUtils.createHTTPRequest(request);
    TokenRequest tokenRequest = TokenRequest.parse(httpRequest);
    ClientAuthentication clientAuthentication = tokenRequest.getClientAuthentication();
    if (clientAuthentication != null && !(clientAuthentication instanceof PlainClientSecret || clientAuthentication instanceof JWTAuthentication)) {
        throw new IllegalArgumentException(String.format("Unsupported '%s' findByClientId authentication in token endpoint", clientAuthentication.getClass()));
    }
    AuthorizationGrant authorizationGrant = tokenRequest.getAuthorizationGrant();
    if (clientAuthentication == null && authorizationGrant instanceof AuthorizationCodeGrant && ((AuthorizationCodeGrant) authorizationGrant).getCodeVerifier() == null) {
        throw new CodeVerifierMissingException("code_verifier required without client authentication");
    }
    String clientId = clientAuthentication != null ? clientAuthentication.getClientID().getValue() : tokenRequest.getClientID().getValue();
    OpenIDClient client = openIDClientRepository.findOptionalByClientId(clientId).orElseThrow(() -> new UnknownClientException(clientId));
    if (clientAuthentication == null && !client.isPublicClient()) {
        throw new UnauthorizedException("Non-public client requires authentication");
    }
    if (clientAuthentication != null) {
        if (clientAuthentication instanceof PlainClientSecret && !secretsMatch((PlainClientSecret) clientAuthentication, client)) {
            throw new UnauthorizedException("Invalid user / secret");
        } else if (clientAuthentication instanceof JWTAuthentication && !verifySignature((JWTAuthentication) clientAuthentication, client, this.tokenEndpoint)) {
            throw new UnauthorizedException("Invalid user / signature");
        }
    }
    MDCContext.mdcContext("action", "Token", "rp", clientId, "grant", authorizationGrant.getType().getValue());
    if (!client.getGrants().contains(authorizationGrant.getType().getValue())) {
        throw new InvalidGrantException("Invalid grant: " + authorizationGrant.getType().getValue());
    }
    if (authorizationGrant instanceof AuthorizationCodeGrant) {
        return handleAuthorizationCodeGrant((AuthorizationCodeGrant) authorizationGrant, client);
    } else if (authorizationGrant instanceof ClientCredentialsGrant) {
        return handleClientCredentialsGrant(client, tokenRequest);
    } else if (authorizationGrant instanceof RefreshTokenGrant) {
        return handleRefreshCodeGrant((RefreshTokenGrant) authorizationGrant, client);
    }
    throw new IllegalArgumentException("Not supported - yet - authorizationGrant " + authorizationGrant.getType().getValue());
}
Also used : HTTPRequest(com.nimbusds.oauth2.sdk.http.HTTPRequest) JWTAuthentication(com.nimbusds.oauth2.sdk.auth.JWTAuthentication) UnknownClientException(oidc.exceptions.UnknownClientException) OpenIDClient(oidc.model.OpenIDClient) RefreshTokenGrant(com.nimbusds.oauth2.sdk.RefreshTokenGrant) CodeVerifierMissingException(oidc.exceptions.CodeVerifierMissingException) InvalidGrantException(oidc.exceptions.InvalidGrantException) AuthorizationCodeGrant(com.nimbusds.oauth2.sdk.AuthorizationCodeGrant) PlainClientSecret(com.nimbusds.oauth2.sdk.auth.PlainClientSecret) ClientCredentialsGrant(com.nimbusds.oauth2.sdk.ClientCredentialsGrant) TokenRequest(com.nimbusds.oauth2.sdk.TokenRequest) UnauthorizedException(oidc.exceptions.UnauthorizedException) ClientAuthentication(com.nimbusds.oauth2.sdk.auth.ClientAuthentication) AuthorizationGrant(com.nimbusds.oauth2.sdk.AuthorizationGrant) PostMapping(org.springframework.web.bind.annotation.PostMapping)

Example 4 with UnauthorizedException

use of oidc.exceptions.UnauthorizedException in project OpenConext-oidcng by OpenConext.

the class TokenGenerator method decryptAccessTokenWithEmbeddedUserInfo.

public User decryptAccessTokenWithEmbeddedUserInfo(String accessToken) {
    Optional<SignedJWT> optionalSignedJWT = parseAndValidateSignedJWT(accessToken);
    SignedJWT signedJWT = optionalSignedJWT.orElseThrow(() -> new UnauthorizedException("Invalid refresh_token value"));
    return this.decryptAccessTokenWithEmbeddedUserInfo(signedJWT);
}
Also used : UnauthorizedException(oidc.exceptions.UnauthorizedException) SignedJWT(com.nimbusds.jwt.SignedJWT)

Example 5 with UnauthorizedException

use of oidc.exceptions.UnauthorizedException in project OpenConext-oidcng by OpenConext.

the class TokenEndpoint method handleRefreshCodeGrant.

private ResponseEntity handleRefreshCodeGrant(RefreshTokenGrant refreshTokenGrant, OpenIDClient client) throws java.text.ParseException {
    String refreshTokenValue = refreshTokenGrant.getRefreshToken().getValue();
    RefreshToken refreshToken;
    SignedJWT signedJWT = null;
    boolean oldFormat = uuidPattern.matcher(refreshTokenValue).matches();
    if (oldFormat) {
        // Old refreshToken
        refreshToken = refreshTokenRepository.findByInnerValue(refreshTokenValue);
    } else {
        Optional<SignedJWT> optionalSignedJWT = tokenGenerator.parseAndValidateSignedJWT(refreshTokenValue);
        signedJWT = optionalSignedJWT.orElseThrow(() -> new UnauthorizedException("Invalid refresh_token value"));
        String jwtId = signedJWT.getJWTClaimsSet().getJWTID();
        refreshToken = refreshTokenRepository.findByJwtId(jwtId).orElseThrow(() -> new IllegalArgumentException("RefreshToken not found"));
    }
    if (!refreshToken.getClientId().equals(client.getClientId())) {
        throw new InvalidClientException("Client is not authorized for the refresh token");
    }
    if (refreshToken.isExpired(Clock.systemDefaultZone())) {
        throw new UnauthorizedException("Refresh token expired");
    }
    // New tokens will be issued
    refreshTokenRepository.delete(refreshToken);
    // It is possible that the access token is already removed by cron cleanup actions
    Optional<AccessToken> accessToken;
    if (oldFormat) {
        // It is possible that the access token is already removed by cron cleanup actions
        accessToken = accessTokenRepository.findOptionalAccessTokenByValue(refreshToken.getAccessTokenValue());
    } else {
        accessToken = accessTokenRepository.findById(refreshToken.getAccessTokenId());
    }
    accessToken.ifPresent(accessTokenRepository::delete);
    Optional<User> optionalUser;
    if (refreshToken.isClientCredentials()) {
        optionalUser = Optional.empty();
    } else if (oldFormat) {
        optionalUser = Optional.of(tokenGenerator.decryptAccessTokenWithEmbeddedUserInfo(refreshToken.getAccessTokenValue()));
    } else {
        optionalUser = Optional.of(tokenGenerator.decryptAccessTokenWithEmbeddedUserInfo(signedJWT));
    }
    Map<String, Object> body = tokenEndpointResponse(optionalUser, client, refreshToken.getScopes(), Collections.emptyList(), false, null, optionalUser.map(User::getUpdatedAt), Optional.empty());
    return new ResponseEntity<>(body, responseHttpHeaders, HttpStatus.OK);
}
Also used : User(oidc.model.User) SignedJWT(com.nimbusds.jwt.SignedJWT) ResponseEntity(org.springframework.http.ResponseEntity) RefreshToken(oidc.model.RefreshToken) AccessToken(oidc.model.AccessToken) UnauthorizedException(oidc.exceptions.UnauthorizedException) InvalidClientException(oidc.exceptions.InvalidClientException)

Aggregations

UnauthorizedException (oidc.exceptions.UnauthorizedException)5 SignedJWT (com.nimbusds.jwt.SignedJWT)3 User (oidc.model.User)3 ClientAuthentication (com.nimbusds.oauth2.sdk.auth.ClientAuthentication)2 PlainClientSecret (com.nimbusds.oauth2.sdk.auth.PlainClientSecret)2 HTTPRequest (com.nimbusds.oauth2.sdk.http.HTTPRequest)2 CodeVerifierMissingException (oidc.exceptions.CodeVerifierMissingException)2 UnknownClientException (oidc.exceptions.UnknownClientException)2 AccessToken (oidc.model.AccessToken)2 OpenIDClient (oidc.model.OpenIDClient)2 ResponseEntity (org.springframework.http.ResponseEntity)2 PostMapping (org.springframework.web.bind.annotation.PostMapping)2 AuthorizationCodeGrant (com.nimbusds.oauth2.sdk.AuthorizationCodeGrant)1 AuthorizationGrant (com.nimbusds.oauth2.sdk.AuthorizationGrant)1 ClientCredentialsGrant (com.nimbusds.oauth2.sdk.ClientCredentialsGrant)1 RefreshTokenGrant (com.nimbusds.oauth2.sdk.RefreshTokenGrant)1 TokenIntrospectionRequest (com.nimbusds.oauth2.sdk.TokenIntrospectionRequest)1 TokenRequest (com.nimbusds.oauth2.sdk.TokenRequest)1 JWTAuthentication (com.nimbusds.oauth2.sdk.auth.JWTAuthentication)1 CodeChallenge (com.nimbusds.oauth2.sdk.pkce.CodeChallenge)1