Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a9bed11
Implement automatic client registration
rmiccoli Oct 14, 2025
2f85ebe
Add verification of the request object signature
rmiccoli Oct 23, 2025
b3bbbac
Fix after rebase from develop
rmiccoli Oct 24, 2025
8bba0ea
Add missing license
rmiccoli Oct 24, 2025
d3ca114
Fix some code smells
rmiccoli Oct 24, 2025
a6e922e
Fix other code smells
rmiccoli Oct 24, 2025
290b240
Validate URL before constructing it
rmiccoli Oct 27, 2025
71ee991
Refactor methods to reduce complexity
rmiccoli Oct 28, 2025
1b57615
Combine common logic into an abstract class
rmiccoli Oct 31, 2025
7db6416
Fix code smell and move handler
rmiccoli Oct 31, 2025
921d25a
Add info in OP Entity Configuration
rmiccoli Oct 31, 2025
f028231
Add test
rmiccoli Oct 31, 2025
c1f4cb3
Add other tests
rmiccoli Nov 3, 2025
7e783a0
Refactor error responses
rmiccoli Nov 10, 2025
d4d1306
Replace mitreID client service
rmiccoli Nov 10, 2025
0b5a516
Fix client initialization
rmiccoli Nov 10, 2025
31eced6
Simplify fetchEntityConfiguration method
rmiccoli Nov 10, 2025
ac2155a
Delete the extra try
rmiccoli Nov 10, 2025
a48c2ce
Refactor code
rmiccoli Nov 11, 2025
a217194
Simplify doFilter "brain" method
rmiccoli Nov 11, 2025
1d5c272
Add tests
rmiccoli Nov 11, 2025
e096dc8
Refactor code and add tests
rmiccoli Nov 12, 2025
f9b590b
Increase coverage
rmiccoli Nov 12, 2025
8309548
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Nov 12, 2025
3b035af
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Nov 19, 2025
907f414
Run expired client task when oidfed profile is active
rmiccoli Nov 19, 2025
d3c6d1d
Fix sonar issues
rmiccoli Nov 19, 2025
eee86b6
Increase jwk size
rmiccoli Nov 24, 2025
53b0b72
Add a log during token request
rmiccoli Nov 24, 2025
c77e92d
Merge tag 'v1.13.1' into issue-1059
rmiccoli Nov 24, 2025
5c7c3ee
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Nov 24, 2025
d7ad42c
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Nov 24, 2025
13aac71
Avoid string concatenation
rmiccoli Nov 25, 2025
075042c
Increase jwk size
rmiccoli Nov 25, 2025
72fad1d
Add request object signing alg in clientDTO
rmiccoli Nov 25, 2025
81438ae
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Nov 26, 2025
c86fb05
Add filter to log basic auth header
rmiccoli Nov 27, 2025
96cd87c
Move the doFilter outside the debug block
rmiccoli Nov 27, 2025
9ddf01a
Extend test to check authz code flow works
rmiccoli Nov 28, 2025
0831138
Dump all headers
rmiccoli Nov 28, 2025
3b3388b
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Dec 18, 2025
7030d4e
Fix code smell
rmiccoli Dec 18, 2025
846db27
Fix request object signing algorithm
rmiccoli Jan 8, 2026
2be156e
Replace string with boolean
rmiccoli Jan 15, 2026
39b6a3e
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Jan 16, 2026
49515aa
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Jan 16, 2026
0e5cb5c
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Jan 23, 2026
60d764d
Update client when expired
rmiccoli Jan 23, 2026
7b237db
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Jan 23, 2026
059d41e
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Jan 29, 2026
503e080
Merge remote-tracking branch 'origin/develop' into issue-1059
rmiccoli Feb 13, 2026
eeb5b98
Enable basic auth filter only for oidfed
rmiccoli Feb 13, 2026
3b2da29
Disable Docker BuildKit
rmiccoli Feb 13, 2026
2e59c7d
Improve workflow
rmiccoli Feb 13, 2026
2698f27
Revert "Enable basic auth filter only for oidfed"
rmiccoli Feb 13, 2026
d8b3bde
Resolve conflict
rmiccoli Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.mitre.oauth2.web.IntrospectionEndpoint;
import org.mitre.oauth2.web.OAuthConfirmationController;
import org.mitre.oauth2.web.RevocationEndpoint;
import org.mitre.openid.connect.filter.AuthorizationRequestFilter;
import org.mitre.openid.connect.token.TofuUserApprovalHandler;
import org.mitre.openid.connect.view.UserInfoView;
import org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint;
Expand All @@ -44,7 +45,6 @@

@SpringBootApplication
@EnableTransactionManagement

// @formatter:off
@ComponentScan(basePackages = {
"it.infn.mw.iam.config",
Expand Down Expand Up @@ -89,8 +89,9 @@
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,
value=RevocationEndpoint.class),
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,
value=UserInfoView.class)

value=UserInfoView.class),
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,
value=AuthorizationRequestFilter.class)
})
@EnableCaching
@EnableAutoConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import it.infn.mw.iam.api.scim.model.ScimUser;
import it.infn.mw.iam.persistence.model.IamAccount;

@SuppressWarnings("deprecation")
@RestController
@RequestMapping(ClientManagementAPIController.ENDPOINT)
public class ClientManagementAPIController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javax.validation.constraints.NotBlank;

import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientRelyingPartyEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.openid.connect.service.OIDCTokenService;
import org.springframework.context.ApplicationEventPublisher;
Expand All @@ -39,6 +40,8 @@
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import com.nimbusds.jose.JWSAlgorithm;

import it.infn.mw.iam.api.client.management.validation.OnClientCreation;
import it.infn.mw.iam.api.client.management.validation.OnClientUpdate;
import it.infn.mw.iam.api.client.service.ClientConverter;
Expand All @@ -63,6 +66,7 @@
import it.infn.mw.iam.persistence.model.IamAccountClient;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;

@SuppressWarnings("deprecation")
@Service
@Validated
public class DefaultClientManagementService implements ClientManagementService {
Expand Down Expand Up @@ -126,12 +130,23 @@ public RegisteredClientDTO saveNewClient(RegisteredClientDTO client) throws Pars
entity.setCreatedAt(Date.from(clock.instant()));
entity.setActive(true);

if (hasRelyingParty(client)) {
ClientRelyingPartyEntity clientRelyingParty =
new ClientRelyingPartyEntity(entity, client.getExpiration(), client.getEntityId());
entity.setClientRelyingParty(clientRelyingParty);
entity.setRequestObjectSigningAlg(JWSAlgorithm.RS256);
}

defaultsService.setupClientDefaults(entity);
entity = clientService.saveNewClient(entity);

return converter.registeredClientDtoFromEntity(entity);
}

private boolean hasRelyingParty(RegisteredClientDTO request) {
return request.getEntityId() != null;
}

@Override
public void deleteClientByClientId(String clientId) {

Expand Down Expand Up @@ -169,7 +184,7 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO cli
ClientDetailsEntity oldClient = clientService.findClientByClientId(clientId)
.orElseThrow(ClientSuppliers.clientNotFound(clientId));

if (oldClient.getClientRelyingParty() != null) {
if (oldClient.getClientRelyingParty() != null && !oldClient.getClientId().startsWith("https")) {
throw new InvalidRequestException("Federated clients cannot be updated");
}

Expand All @@ -188,6 +203,13 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO cli
client.setClientSecret(defaultsService.generateClientSecret());
}

if (hasRelyingParty(client)) {
ClientRelyingPartyEntity clientRelyingParty =
new ClientRelyingPartyEntity(newClient, client.getExpiration(), client.getEntityId());
newClient.setClientRelyingParty(clientRelyingParty);
newClient.setRequestObjectSigningAlg(JWSAlgorithm.RS256);
}

newClient = clientService.updateClient(newClient);
eventPublisher.publishEvent(new ClientUpdatedEvent(this, newClient));
return converter.registeredClientDtoFromEntity(newClient);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ public RegisteredClientDTO registeredClientDtoFromEntity(ClientDetailsEntity ent
clientDTO.setDeviceCodeValiditySeconds(entity.getDeviceCodeValiditySeconds());
clientDTO.setDynamicallyRegistered(entity.isDynamicallyRegistered());
clientDTO.setIdTokenValiditySeconds(entity.getIdTokenValiditySeconds());
clientDTO.setJwksUri(entity.getJwksUri());

Optional.ofNullable(entity.getJwks()).ifPresent(k -> clientDTO.setJwk(k.toString()));
clientDTO.setPolicyUri(entity.getPolicyUri());
Expand Down Expand Up @@ -173,6 +172,10 @@ public RegisteredClientDTO registeredClientDtoFromEntity(ClientDetailsEntity ent
clientDTO.setStatusChangedOn(entity.getStatusChangedOn());
clientDTO.setStatusChangedBy(entity.getStatusChangedBy());

if (entity.getClientRelyingParty() != null) {
clientDTO.setExpiration(entity.getClientRelyingParty().getExpiration());
clientDTO.setEntityId(entity.getClientRelyingParty().getEntityId());
}
return clientDTO;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@
*/
package it.infn.mw.iam.api.openid_federation;

import java.net.URI;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.mitre.oauth2.model.ClientDetailsEntity;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -35,19 +31,14 @@
import org.springframework.web.bind.annotation.RestController;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.oauth2.sdk.GrantType;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
import com.nimbusds.openid.connect.sdk.federation.trust.TrustChain;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;

import it.infn.mw.iam.api.client.registration.service.ClientRegistrationService;
import it.infn.mw.iam.api.client.service.ClientService;
import it.infn.mw.iam.api.common.ErrorDTO;
import it.infn.mw.iam.api.common.client.AuthorizationGrantType;
import it.infn.mw.iam.api.common.client.OAuthResponseType;
import it.infn.mw.iam.api.common.client.RegisteredClientDTO;
import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod;
import it.infn.mw.iam.core.oidc.ExplicitClientRegistrationMapper;
import it.infn.mw.iam.core.oidc.FederationError;
import it.infn.mw.iam.core.oidc.InvalidClientMetadataException;
import it.infn.mw.iam.core.oidc.InvalidTrustChainException;
Expand All @@ -66,107 +57,18 @@ public class FederationRegistrationController {
private final FederationResponseBuilder federationResponseBuilder;
private final IamClientRepository clientRepo;
private final ClientService clientService;
private final ExplicitClientRegistrationMapper clientMapper;

public FederationRegistrationController(TrustChainService trustChainService,
ClientRegistrationService clientRegistrationService,
FederationResponseBuilder federationResponseBuilder, IamClientRepository clientRepo,
ClientService clientService) {
ClientService clientService, ExplicitClientRegistrationMapper clientMapper) {
this.trustChainService = trustChainService;
this.clientRegistrationService = clientRegistrationService;
this.federationResponseBuilder = federationResponseBuilder;
this.clientRepo = clientRepo;
this.clientService = clientService;
}

private RegisteredClientDTO createClientDtoFromRpMetadata(EntityStatement rpRequest) {
RegisteredClientDTO dtoClient = new RegisteredClientDTO();
OIDCClientMetadata metadata = rpRequest.getClaimsSet().getRPMetadata();

setClientName(dtoClient, metadata);
setContacts(dtoClient, metadata);
setGrantTypes(dtoClient, metadata);
setRedirectUris(dtoClient, metadata);
setResponseTypes(dtoClient, metadata);
setTokenEndpointAuthMethod(dtoClient, metadata);
setScope(dtoClient, metadata);
setEntityId(dtoClient, rpRequest);

return dtoClient;
}

private void setClientName(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
dto.setClientName(metadata.getName() != null ? metadata.getName() : "OIDFed client");
}

private void setContacts(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
if (metadata.getEmailContacts() != null) {
dto.setContacts(new HashSet<>(metadata.getEmailContacts()));
}
}

private void setGrantTypes(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
if (metadata.getGrantTypes() != null) {
dto.setGrantTypes(metadata.getGrantTypes()
.stream()
.map(GrantType::getValue)
.map(AuthorizationGrantType::fromGrantType)
.collect(Collectors.toSet()));
} else {
dto.setGrantTypes(Set.of(AuthorizationGrantType.CODE));
}
}

private void setRedirectUris(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
if (metadata.getRedirectionURIs() == null) {
throw new InvalidClientMetadataException("invalid_redirect_uri",
"Missing redirect uris from RP Entity Statement");
}
dto.setRedirectUris(
metadata.getRedirectionURIs().stream().map(URI::toString).collect(Collectors.toSet()));
}

private void setResponseTypes(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
Set<String> supportedResponseTypes =
Set.of(ResponseType.CODE.toString(), ResponseType.TOKEN.toString());
if (metadata.getResponseTypes() != null) {
Set<OAuthResponseType> responseTypes = metadata.getResponseTypes()
.stream()
.map(ResponseType::toString)
.filter(supportedResponseTypes::contains)
.map(OAuthResponseType::fromResponseType)
.collect(Collectors.toSet());
if (responseTypes.isEmpty()) {
throw new InvalidClientMetadataException("invalid_client_metadata",
"Unsupported response type");
}
dto.setResponseTypes(responseTypes);
} else {
dto.setResponseTypes(Set.of(OAuthResponseType.CODE));
}
}

private void setTokenEndpointAuthMethod(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
if (metadata.getTokenEndpointAuthMethod() != null) {
dto.setTokenEndpointAuthMethod(TokenEndpointAuthenticationMethod
.valueOf(metadata.getTokenEndpointAuthMethod().getValue()));
} else {
dto.setTokenEndpointAuthMethod(TokenEndpointAuthenticationMethod.client_secret_basic);
}
}

private void setScope(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
if (metadata.getScope() != null) {
dto.setScope(metadata.getScope().toStringList().stream().collect(Collectors.toSet()));
} else {
dto.setScope(Set.of("openid"));
}
}

private void setEntityId(RegisteredClientDTO dto, EntityStatement rpRequest) {
if (rpRequest.getEntityID() == null) {
throw new InvalidClientMetadataException("invalid_client_metadata", "Missing RP Entity ID");
}
dto.setEntityId(rpRequest.getEntityID().getValue());
this.clientMapper = clientMapper;
}

@PostMapping(value = "/iam/api/oid-fed/client-registration",
Expand Down Expand Up @@ -195,7 +97,7 @@ public ResponseEntity<String> register(@RequestBody String requestJwt)
TrustChain trustChain = trustChainService.validateFromEntityConfiguration(rpRequest);

// 4. Create RegisteredClientDTO from RP metadata
RegisteredClientDTO dtoClient = createClientDtoFromRpMetadata(rpRequest);
RegisteredClientDTO dtoClient = clientMapper.createClientDtoFromRpMetadata(rpRequest);
dtoClient.setExpiration(trustChain.resolveExpirationTime());

// 5. Register the client by using the already existing service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.mitre.oauth2.service.impl.DefaultOAuth2ProviderTokenService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.config.UIConfiguration;
import org.mitre.openid.connect.filter.AuthorizationRequestFilter;
import org.mitre.openid.connect.service.ApprovedSiteService;
import org.mitre.openid.connect.service.BlacklistedSiteService;
import org.mitre.openid.connect.service.ClientLogoLoadingService;
Expand Down Expand Up @@ -65,7 +64,6 @@
import org.mitre.uma.service.ResourceSetService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
Expand Down Expand Up @@ -195,21 +193,6 @@ ServerConfigInterceptor serverConfigInterceptor() {
return new ServerConfigInterceptor();
}

@Bean
FilterRegistrationBean<AuthorizationRequestFilter> disabledMitreFilterRegistration(
AuthorizationRequestFilter f) {

FilterRegistrationBean<AuthorizationRequestFilter> b = new FilterRegistrationBean<>(f);
b.setEnabled(false);
return b;
}

@Bean(name = "mitreAuthzRequestFilter")
AuthorizationRequestFilter authorizationRequestFilter() {

return new AuthorizationRequestFilter();
}

@Bean
AuthenticationTimeStamper timestamper() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static class UserLoginConfig extends WebSecurityConfigurerAdapter {
private OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler;

@Autowired
@Qualifier("mitreAuthzRequestFilter")
@Qualifier("iamAuthzRequestFilter")
private GenericFilterBean authorizationRequestFilter;

@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.infn.mw.iam.core.oidc;

import java.util.Optional;

import org.springframework.stereotype.Component;

import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;

import it.infn.mw.iam.api.common.client.RegisteredClientDTO;
import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod;

@Component
public class AutomaticClientRegistrationMapper extends BaseFedClientRegistrationMapper {

@Override
protected void setTokenEndpointAuthMethod(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
dto.setTokenEndpointAuthMethod(Optional.ofNullable(metadata.getTokenEndpointAuthMethod())
.map(v -> TokenEndpointAuthenticationMethod.valueOf(v.getValue()))
.orElse(TokenEndpointAuthenticationMethod.private_key_jwt));
}

@Override
protected void setJwks(RegisteredClientDTO dto, OIDCClientMetadata metadata) {
if (metadata.getJWKSetURI() == null && metadata.getJWKSet() == null) {
throw new InvalidClientMetadataException("invalid_client_metadata", "Missing jwks/jwks_uri");
}

Optional.ofNullable(metadata.getJWKSetURI())
.ifPresent(uri -> dto.setJwksUri(uri.toASCIIString()));

Optional.ofNullable(metadata.getJWKSet()).ifPresent(jwk -> dto.setJwk(jwk.toString()));
}
}
Loading