Skip to content

Commit

Permalink
Merge branch 'main' into feature/color-customization
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielaIn2 authored Feb 7, 2025
2 parents 6d5bfcf + b5aa3fa commit 9497f69
Show file tree
Hide file tree
Showing 23 changed files with 702 additions and 167 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v1.1.0](https://github.com/in2workspace/in2-verifier-api/releases/tag/v1.1.0)
### Added
- Add refresh token support for the OpenID Connect flow
- Add nonce support for the OpenID Connect authorization code flow

## [v1.0.17](https://github.com/in2workspace/in2-verifier-api/releases/tag/v1.0.17)
### Added
- Add documentation for OIDC client registration and interaction with the verifier.
Expand Down
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
<span>by </span><a href="https://in2.es">in2.es</a>
<p><p>

[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)

[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=bugs)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=security_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=ncloc)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=coverage)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)

[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=bugs)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=security_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=ncloc)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)

[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=coverage)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
</div>

# Introduction
Expand All @@ -38,7 +38,7 @@ With the Verifier, organizations can leverage:
The verifier interacts with clients in two ways:

1. **Using OpenID Connect Core**
OpenID Connect Core serves as the primary standard for authentication and Authentication between clients and the verifier.
OpenID Connect Core serves as the primary standard for authentication between clients and the verifier.
[Learn more about OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)

2. **Using OpenID for Verifiable Presentations (OpenID4VP)**
Expand All @@ -55,6 +55,7 @@ The OpenID Connect integration for clients consists of two main steps:

1. **Authentication Request**
2. **Token Request**
3. **Refresh Token Request**

### Step 1: Client Registration

Expand Down Expand Up @@ -94,7 +95,7 @@ The trusted services list contains all the verified and authorized services with
| **redirectUris** | Must include all the URLs where you expect to receive authentication responses. These should be HTTPS URLs to ensure secure communication. |
| **scopes** | Currently, only `openid_learcredential` is accepted. This scope allows your service to request the necessary credentials. |
| **clientAuthenticationMethods** | Must be set to `["client_secret_jwt"]`, as this is the only supported authentication method. |
| **authorizationGrantTypes** | Must be set to `["authorization_code"]`, as this is the only supported grant type. |
| **authorizationGrantTypes** | Must be set to `["authorization_code"]` and `["refresh_token"]` if needed, as this is the only supported grant type. |
| **postLogoutRedirectUris** | Include URLs where users should be redirected after they log out from your service. |
| **requireAuthorizationConsent** | Set to `false` because explicit user consent is not required in this flow. |
| **requireProofKey** | Set to `false` as PKCE is not utilized. |
Expand Down Expand Up @@ -127,7 +128,7 @@ clients:
redirectUris: ["https://dome-marketplace-sbx.org/auth/vc/callback"]
scopes: ["openid_learcredential"]
clientAuthenticationMethods: ["client_secret_jwt"]
authorizationGrantTypes: ["authorization_code"]
authorizationGrantTypes: ["authorization_code", "refresh_token"]
postLogoutRedirectUris: ["https://dome-marketplace-sbx.org/"]
requireAuthorizationConsent: false
requireProofKey: false
Expand Down Expand Up @@ -338,6 +339,15 @@ The following restrictions and configurations are applied in this flow:
- The verifier uses `direct_post` as the response mode.
- This indicates that the authentication response must be sent directly to the verifier's endpoint specified in the `response_uri`.

# Configuring the Verifier as an External Identity Provider on Keycloak

This guide will teach you how to configure an external Identity Provider using **OpenID Connect v1.0**. The guide is divided into two main sections:

1. **Registering the Client with the Identity Provider (IDP)**
2. **Configuring the External Identity Provider in Keycloak**

Images and links to documentation are included to simplify the process.

---

# Configuring the Verifier as an External Identity Provider on Keycloak
Expand Down
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ sonar {
"src/main/java/es/in2/vcverifier/service/impl/ClientAssertionValidationServiceImpl.java, " +
"src/main/java/es/in2/vcverifier/service/impl/TrustFrameworkServiceImpl.java, " +
"src/main/java/es/in2/vcverifier/service/impl/CertificateValidationServiceImpl.java, " +
"src/main/java/es/in2/verifier/security/filters/CustomTokenRequestConverter.java, " +
"src/main/java/es/in2/verifier/security/AuthorizationServerConfig.java, " +
"src/main/java/es/in2/vcverifier/exception/MismatchOrganizationIdentifierException.java"
}
}
Expand Down Expand Up @@ -122,10 +124,11 @@ tasks.jacocoTestReport {
}
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
"**/AuthorizationResponseProcessorServiceImpl.class",
"**/TrustFrameworkServiceImpl.class",
"**/CertificateValidationServiceImpl.class",
"**/MismatchOrganizationIdentifierException.class",
"**/CustomTokenRequestConverter.class",
"**/AuthorizationServerConfig.class",
])
}))
}
8 changes: 8 additions & 0 deletions src/main/java/es/in2/verifier/config/CacheStoreConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import es.in2.verifier.config.properties.SecurityProperties;
import es.in2.verifier.model.AuthorizationCodeData;
import es.in2.verifier.model.AuthorizationRequestJWT;
import es.in2.verifier.model.RefreshTokenDataCache;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -27,6 +28,13 @@ public CacheStore<AuthorizationRequestJWT> cacheStoreForAuthorizationRequestJWT(
TimeUnit.of(ChronoUnit.valueOf(securityProperties.token().accessToken().cronUnit())));
}

@Bean
public CacheStore<RefreshTokenDataCache> cacheStoreForRefreshTokenData() {
return new CacheStore<>(
Long.parseLong(securityProperties.token().accessToken().expiration()),
TimeUnit.of(ChronoUnit.valueOf(securityProperties.token().accessToken().cronUnit())));
}

@Bean
public CacheStore<OAuth2AuthorizationRequest> cacheStoreForOAuth2AuthorizationRequest() {
return new CacheStore<>(10, TimeUnit.MINUTES);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package es.in2.verifier.config.properties;

import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "flags")
public record FlagsProperties(@NotNull boolean isNonceRequiredOnFapiProfile) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public record AuthorizationCodeData(
String state,
JsonNode verifiableCredential,
Set<String> requestedScopes,
OAuth2Authorization oAuth2Authorization
OAuth2Authorization oAuth2Authorization,
String clientNonce

) {
}
14 changes: 14 additions & 0 deletions src/main/java/es/in2/verifier/model/AuthorizationContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package es.in2.verifier.model;

import lombok.Builder;

@Builder
public record AuthorizationContext(
String state,
String scope,
String redirectUri,
String clientNonce,
String originalRequestURL,
String requestUri
) {
}
14 changes: 14 additions & 0 deletions src/main/java/es/in2/verifier/model/RefreshTokenDataCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package es.in2.verifier.model;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.Builder;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;

@Builder
public record RefreshTokenDataCache(
OAuth2RefreshToken refreshToken,
String clientId,
JsonNode verifiableCredential

) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import es.in2.verifier.component.CryptoComponent;
import es.in2.verifier.config.CacheStore;
import es.in2.verifier.config.properties.FlagsProperties;
import es.in2.verifier.config.properties.SecurityProperties;
import es.in2.verifier.component.CryptoComponent;
import es.in2.verifier.model.AuthorizationCodeData;
import es.in2.verifier.model.AuthorizationRequestJWT;
import es.in2.verifier.model.RefreshTokenDataCache;
import es.in2.verifier.security.filters.CustomAuthenticationProvider;
import es.in2.verifier.security.filters.CustomAuthorizationRequestConverter;
import es.in2.verifier.security.filters.CustomErrorResponseHandler;
Expand All @@ -27,16 +29,20 @@
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
Expand All @@ -55,6 +61,8 @@ public class AuthorizationServerConfig {
private final CacheStore<AuthorizationCodeData> cacheStoreForAuthorizationCodeData;
private final ObjectMapper objectMapper;
private final RegisteredClientsCorsConfig registeredClientsCorsConfig;
private final CacheStore<RefreshTokenDataCache> refreshTokenDataCacheCacheStore;
private final FlagsProperties flagsProperties;

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
Expand All @@ -69,13 +77,13 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
// Adds an AuthenticationConverter (pre-processor) used when attempting to extract
// an OAuth2 authorization request (or consent) from HttpServletRequest to an instance
// of OAuth2AuthorizationCodeRequestAuthenticationToken or OAuth2AuthorizationConsentAuthenticationToken.
.authorizationRequestConverter(new CustomAuthorizationRequestConverter(didService,jwtService,cryptoComponent,cacheStoreForAuthorizationRequestJWT,cacheStoreForOAuth2AuthorizationRequest,securityProperties,registeredClientRepository))
.authorizationRequestConverter(new CustomAuthorizationRequestConverter(didService,jwtService,cryptoComponent,cacheStoreForAuthorizationRequestJWT,cacheStoreForOAuth2AuthorizationRequest,securityProperties,registeredClientRepository, flagsProperties))
.errorResponseHandler(new CustomErrorResponseHandler())
)
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.accessTokenRequestConverter(new CustomTokenRequestConverter(jwtService, clientAssertionValidationService, vpService, cacheStoreForAuthorizationCodeData,oAuth2AuthorizationService(),objectMapper))
.authenticationProvider(new CustomAuthenticationProvider(jwtService,registeredClientRepository,securityProperties,objectMapper))
.accessTokenRequestConverter(new CustomTokenRequestConverter(jwtService, clientAssertionValidationService, vpService, cacheStoreForAuthorizationCodeData,oAuth2AuthorizationService(),objectMapper, refreshTokenDataCacheCacheStore))
.authenticationProvider(new CustomAuthenticationProvider(jwtService,registeredClientRepository,securityProperties,objectMapper, refreshTokenDataCacheCacheStore, oAuth2AuthorizationService()))
)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
return http.build();
Expand All @@ -95,6 +103,16 @@ public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getJwsHeader().algorithm(SignatureAlgorithm.ES256);
}
};
}

@Bean
public OAuth2AuthorizationService oAuth2AuthorizationService() {
return new InMemoryOAuth2AuthorizationService();
Expand Down
Loading

0 comments on commit 9497f69

Please sign in to comment.