diff --git a/iam-common/pom.xml b/iam-common/pom.xml index 43c2e12ff5..1e2c7689fc 100644 --- a/iam-common/pom.xml +++ b/iam-common/pom.xml @@ -25,6 +25,10 @@ iam-persistence ${project.version} + + com.nimbusds + nimbus-jose-jwt + diff --git a/iam-login-service/pom.xml b/iam-login-service/pom.xml index fe9c9d58c9..9bb6d1eda8 100644 --- a/iam-login-service/pom.xml +++ b/iam-login-service/pom.xml @@ -38,7 +38,6 @@ 1.0.2 2.7.9 2.2.1 - 9.37.4 @@ -201,11 +200,10 @@ javax.persistence ${javax.persistence.version} - + com.nimbusds nimbus-jose-jwt - ${nimbus-jose-jwt.version} @@ -238,6 +236,18 @@ jstl + + javax.servlet.jsp + javax.servlet.jsp-api + 2.3.3 + provided + + + + org.freemarker + freemarker + + org.springframework.security spring-security-taglibs @@ -273,21 +283,17 @@ runtime - + - org.mitre - openid-connect-server - - - org.eclipse.persistence - org.eclipse.persistence.core - - + com.google.guava + guava + 33.5.0-jre + - org.mitre - openid-connect-client + org.apache.httpcomponents + httpclient @@ -297,7 +303,6 @@ - org.bouncycastle bcpkix-jdk18on diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/IamLoginService.java b/iam-login-service/src/main/java/it/infn/mw/iam/IamLoginService.java index 2a3c3c4cde..51e7ff1649 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/IamLoginService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/IamLoginService.java @@ -15,20 +15,6 @@ */ package it.infn.mw.iam; -import org.mitre.discovery.web.DiscoveryEndpoint; -import org.mitre.oauth2.service.impl.DefaultOAuth2ProviderTokenService; -import org.mitre.oauth2.web.CorsFilter; -import org.mitre.oauth2.web.DeviceEndpoint; -import org.mitre.oauth2.web.IntrospectionEndpoint; -import org.mitre.oauth2.web.OAuthConfirmationController; -import org.mitre.oauth2.web.RevocationEndpoint; -import org.mitre.openid.connect.token.ConnectTokenEnhancer; -import org.mitre.openid.connect.token.TofuUserApprovalHandler; -import org.mitre.openid.connect.view.UserInfoView; -import org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint; -import org.mitre.openid.connect.web.JWKSetPublishingEndpoint; -import org.mitre.openid.connect.web.RootController; -import org.mitre.openid.connect.web.UserInfoEndpoint; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -37,7 +23,6 @@ import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.FilterType; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.ClassPathResource; import org.springframework.transaction.annotation.EnableTransactionManagement; @@ -60,42 +45,13 @@ "it.infn.mw.iam.audit", "it.infn.mw.iam.actuator", "it.infn.mw.iam.rcauth", - "it.infn.mw.iam.service.aup", - "org.mitre.oauth2.web", - "org.mitre.oauth2.view", - "org.mitre.openid.connect.web", - "org.mitre.openid.connect.view", - "org.mitre.discovery.web", - "org.mitre.discovery.view"}, -excludeFilters = { - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=UserInfoEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=RootController.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=DiscoveryEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=JWKSetPublishingEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=DynamicClientRegistrationEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=CorsFilter.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=OAuthConfirmationController.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=DeviceEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=TofuUserApprovalHandler.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=IntrospectionEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=RevocationEndpoint.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=UserInfoView.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=ConnectTokenEnhancer.class), - @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, - value=DefaultOAuth2ProviderTokenService.class) + "it.infn.mw.iam.service.aup" +// "org.mitre.oauth2.web", +// "org.mitre.oauth2.view", +// "org.mitre.openid.connect.web", +// "org.mitre.openid.connect.view", +// "org.mitre.discovery.web", +// "org.mitre.discovery.view" }) @EnableCaching @EnableAutoConfiguration( diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/attributes/AccountAttributesController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/attributes/AccountAttributesController.java index fa594a5425..2a374d68dd 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/attributes/AccountAttributesController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/attributes/AccountAttributesController.java @@ -19,6 +19,7 @@ import static java.lang.String.format; import static org.springframework.http.HttpStatus.NO_CONTENT; +import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpStatus; @@ -34,7 +35,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.google.common.collect.Lists; +//import com.google.common.collect.Lists; import it.infn.mw.iam.api.common.AttributeDTO; import it.infn.mw.iam.api.common.AttributeDTOConverter; @@ -74,7 +75,7 @@ public List getAttributes(@PathVariable String id) { IamAccount account = accountService.findByUuid(id).orElseThrow(() -> NoSuchAccountError.forUuid(id)); - List results = Lists.newArrayList(); + List results = new ArrayList<>(); account.getAttributes().forEach(a -> results.add(converter.dtoFromEntity(a))); return results; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/authority/DefaultAccountAuthorityService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/authority/DefaultAccountAuthorityService.java index 013f6d9526..d1e39e9672 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/authority/DefaultAccountAuthorityService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/authority/DefaultAccountAuthorityService.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.api.account.authority; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import java.util.Set; import java.util.stream.Collectors; @@ -52,7 +52,7 @@ public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { } protected IamAuthority findAuthorityFromString(String authority) { - checkNotNull(authority, "authority must not be null"); + requireNonNull(authority, "authority must not be null"); return authRepo.findByAuthority(authority).orElseThrow( () -> new InvalidAuthorityError(String.format("Invalid authority: '%s'", authority))); @@ -60,7 +60,7 @@ protected IamAuthority findAuthorityFromString(String authority) { @Override public void addAuthorityToAccount(IamAccount account, String authority) { - checkNotNull(account, ACCOUNT_NOT_NULL_MSG); + requireNonNull(account, ACCOUNT_NOT_NULL_MSG); IamAuthority iamAuthority = findAuthorityFromString(authority); @@ -81,7 +81,7 @@ public void addAuthorityToAccount(IamAccount account, String authority) { @Override public void removeAuthorityFromAccount(IamAccount account, String authority) { - checkNotNull(account, ACCOUNT_NOT_NULL_MSG); + requireNonNull(account, ACCOUNT_NOT_NULL_MSG); IamAuthority iamAuthority = findAuthorityFromString(authority); account.getAuthorities().remove(iamAuthority); accountRepo.save(account); @@ -94,7 +94,7 @@ public void removeAuthorityFromAccount(IamAccount account, String authority) { @Override public Set getAccountAuthorities(IamAccount account) { - checkNotNull(account, ACCOUNT_NOT_NULL_MSG); + requireNonNull(account, ACCOUNT_NOT_NULL_MSG); return account.getAuthorities() .stream() diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/group_manager/DefaultAccountGroupManagerService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/group_manager/DefaultAccountGroupManagerService.java index 861d87a83a..87416f00c0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/group_manager/DefaultAccountGroupManagerService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/group_manager/DefaultAccountGroupManagerService.java @@ -21,11 +21,8 @@ import java.util.List; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.account.authority.AccountAuthorityService; import it.infn.mw.iam.api.account.group_manager.error.InvalidManagedGroupError; import it.infn.mw.iam.api.account.group_manager.model.AccountManagedGroupsDTO; @@ -40,13 +37,11 @@ public class DefaultAccountGroupManagerService implements AccountGroupManagerService { public static final String ROLE_GM_TEMPLATE = "ROLE_GM:%s"; - + final IamAccountRepository accountRepo; final IamGroupRepository groupRepo; final AccountAuthorityService authorityService; - - @Autowired public DefaultAccountGroupManagerService(IamAccountRepository accountRepo, IamGroupRepository groupRepo, AccountAuthorityService authorityService) { this.accountRepo = accountRepo; @@ -94,10 +89,10 @@ public AccountManagedGroupsDTO getManagedGroupInfoForAccount(IamAccount account) result.managedGroups(iamGroupsToDTO(managedGroups)); - List unmanagedGroups = null; + List unmanagedGroups = new ArrayList<>(); if (managedGroups.isEmpty()) { - unmanagedGroups = Lists.newArrayList(groupRepo.findAll()); + unmanagedGroups.addAll(groupRepo.findAll()); } else { unmanagedGroups = groupRepo .findByUuidNotIn(managedGroups.stream().map(IamGroup::getUuid).collect(Collectors.toSet())); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/labels/AccountLabelsController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/labels/AccountLabelsController.java index 45f5f37ec2..be18eff9c2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/labels/AccountLabelsController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/labels/AccountLabelsController.java @@ -19,6 +19,7 @@ import static java.lang.String.format; import static org.springframework.http.HttpStatus.NO_CONTENT; +import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; @@ -36,8 +37,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.api.common.LabelDTO; import it.infn.mw.iam.api.common.LabelDTOConverter; @@ -77,7 +76,7 @@ public List getLabels(@PathVariable String id) { IamAccount account = service.findByUuid(id).orElseThrow(noSuchAccountError(id)); - List results = Lists.newArrayList(); + List results = new ArrayList<>(); account.getLabels().forEach(l -> results.add(converter.dtoFromEntity(l))); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/DefaultIamTotpMfaService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/DefaultIamTotpMfaService.java index ed3543025b..52287454dd 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/DefaultIamTotpMfaService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/DefaultIamTotpMfaService.java @@ -17,8 +17,10 @@ import java.util.Optional; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import dev.samstevens.totp.code.CodeVerifier; @@ -38,10 +40,12 @@ import it.infn.mw.iam.util.mfa.IamTotpMfaInvalidArgumentError; @Service +@Profile("mfa") public class DefaultIamTotpMfaService implements IamTotpMfaService, ApplicationEventPublisherAware { public static final int RECOVERY_CODE_QUANTITY = 6; - private static final String MFA_SECRET_NOT_FOUND_MESSAGE = "No multi-factor secret is attached to this account"; + private static final String MFA_SECRET_NOT_FOUND_MESSAGE = + "No multi-factor secret is attached to this account"; private final IamAccountService iamAccountService; private final IamTotpMfaRepository totpMfaRepository; @@ -51,9 +55,10 @@ public class DefaultIamTotpMfaService implements IamTotpMfaService, ApplicationE private ApplicationEventPublisher eventPublisher; public DefaultIamTotpMfaService(IamAccountService iamAccountService, - IamTotpMfaRepository totpMfaRepository, SecretGenerator secretGenerator, - CodeVerifier codeVerifier, ApplicationEventPublisher eventPublisher, - IamTotpMfaProperties iamTotpMfaProperties) { + IamTotpMfaRepository totpMfaRepository, + @Qualifier("secretGenerator") SecretGenerator secretGenerator, + @Qualifier("codeVerifier") CodeVerifier codeVerifier, + ApplicationEventPublisher eventPublisher, IamTotpMfaProperties iamTotpMfaProperties) { this.iamAccountService = iamAccountService; this.totpMfaRepository = totpMfaRepository; this.secretGenerator = secretGenerator; @@ -80,9 +85,9 @@ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEv } /** - * Generates and attaches a TOTP MFA secret to a user account - * This is pre-emptive to actually enabling TOTP MFA on the account - the secret is written for - * server-side TOTP verification during the user's enabling of MFA on their account + * Generates and attaches a TOTP MFA secret to a user account This is pre-emptive to actually + * enabling TOTP MFA on the account - the secret is written for server-side TOTP verification + * during the user's enabling of MFA on their account * * @param account the account to add the secret to * @return the new TOTP secret @@ -176,8 +181,8 @@ public boolean verifyTotp(IamAccount account, String totp) throws IamTotpMfaInva } IamTotpMfa totpMfa = totpMfaOptional.get(); - String mfaSecret = IamTotpMfaEncryptionAndDecryptionUtil.decryptSecret( - totpMfa.getSecret(), iamTotpMfaProperties.getPasswordToEncryptOrDecrypt()); + String mfaSecret = IamTotpMfaEncryptionAndDecryptionUtil.decryptSecret(totpMfa.getSecret(), + iamTotpMfaProperties.getPasswordToEncryptOrDecrypt()); // Verify provided TOTP if (codeVerifier.isValidCode(mfaSecret, totp)) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsController.java index bf449aff54..243ceb4af5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsController.java @@ -19,15 +19,23 @@ import javax.validation.Valid; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + import dev.samstevens.totp.code.HashingAlgorithm; import dev.samstevens.totp.exceptions.QrGenerationException; import dev.samstevens.totp.qr.QrData; @@ -53,7 +61,8 @@ * feature through POST requests to the relevant endpoints */ @SuppressWarnings("deprecation") -@Controller +@RestController +@Profile("mfa") public class AuthenticatorAppSettingsController { public static final String BASE_URL = "/iam/authenticator-app"; @@ -93,7 +102,6 @@ public AuthenticatorAppSettingsController(IamTotpMfaService service, */ @PreAuthorize("hasRole('USER')") @PutMapping(value = ADD_SECRET_URL, produces = MediaType.APPLICATION_JSON_VALUE) - @ResponseBody public SecretAndDataUriDTO addSecret() throws IamTotpMfaInvalidArgumentError { final String username = getUsernameFromSecurityContext(); IamAccount account = accountRepository.findByUsername(username) @@ -125,7 +133,6 @@ public SecretAndDataUriDTO addSecret() throws IamTotpMfaInvalidArgumentError { */ @PreAuthorize("hasRole('USER')") @PostMapping(value = ENABLE_URL, produces = MediaType.TEXT_PLAIN_VALUE) - @ResponseBody public void enableAuthenticatorApp(@ModelAttribute @Valid CodeDTO code, BindingResult validationResult) { if (validationResult.hasErrors()) { @@ -163,7 +170,6 @@ public void enableAuthenticatorApp(@ModelAttribute @Valid CodeDTO code, */ @PreAuthorize("hasRole('USER')") @PostMapping(value = DISABLE_URL, produces = MediaType.TEXT_PLAIN_VALUE) - @ResponseBody public void disableAuthenticatorApp(@Valid CodeDTO code, BindingResult validationResult) { if (validationResult.hasErrors()) { throw new BadMfaCodeError(BAD_CODE); @@ -197,7 +203,6 @@ public void disableAuthenticatorApp(@Valid CodeDTO code, BindingResult validatio */ @PreAuthorize("hasRole('ADMIN')") @DeleteMapping(value = DISABLE_URL_FOR_ACCOUNT_ID, produces = MediaType.TEXT_PLAIN_VALUE) - @ResponseBody public void disableAuthenticatorAppForAccount(@PathVariable String accountId) { IamAccount account = accountRepository.findByUuid(accountId) .orElseThrow(() -> NoSuchAccountError.forUuid(accountId)); @@ -254,7 +259,6 @@ private String generateQRCodeFromSecret(String secret, String username) */ @ResponseStatus(code = HttpStatus.CONFLICT) @ExceptionHandler(MfaSecretNotFoundException.class) - @ResponseBody public ErrorDTO handleMfaSecretNotFoundException(MfaSecretNotFoundException e) { return ErrorDTO.fromString(e.getMessage()); } @@ -267,7 +271,6 @@ public ErrorDTO handleMfaSecretNotFoundException(MfaSecretNotFoundException e) { */ @ResponseStatus(code = HttpStatus.CONFLICT) @ExceptionHandler(MfaSecretAlreadyBoundException.class) - @ResponseBody public ErrorDTO handleMfaSecretAlreadyBoundException(MfaSecretAlreadyBoundException e) { return ErrorDTO.fromString(e.getMessage()); } @@ -280,7 +283,6 @@ public ErrorDTO handleMfaSecretAlreadyBoundException(MfaSecretAlreadyBoundExcept */ @ResponseStatus(code = HttpStatus.CONFLICT) @ExceptionHandler(TotpMfaAlreadyEnabledException.class) - @ResponseBody public ErrorDTO handleTotpMfaAlreadyEnabledException(TotpMfaAlreadyEnabledException e) { return ErrorDTO.fromString(e.getMessage()); } @@ -294,7 +296,6 @@ public ErrorDTO handleTotpMfaAlreadyEnabledException(TotpMfaAlreadyEnabledExcept */ @ResponseStatus(code = HttpStatus.BAD_REQUEST) @ExceptionHandler(BadMfaCodeError.class) - @ResponseBody public ErrorDTO handleBadCodeError(BadMfaCodeError e) { return ErrorDTO.fromString(e.getMessage()); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/search/AbstractSearchController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/search/AbstractSearchController.java index 0fbb558456..0f978f21bb 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/search/AbstractSearchController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/search/AbstractSearchController.java @@ -15,6 +15,7 @@ */ package it.infn.mw.iam.api.account.search; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -28,7 +29,6 @@ import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import com.google.common.collect.Lists; import it.infn.mw.iam.api.common.ListResponseDTO; import it.infn.mw.iam.api.common.OffsetPageable; @@ -121,7 +121,7 @@ protected MappingJacksonValue filterResponseAttributes(ListResponseDTO respon protected List convertFromPage(Page entities, Converter converter) { - List resources = Lists.newArrayList(); + List resources = new ArrayList<>(); entities.getContent().forEach(e -> resources.add(converter.dtoFromEntity(e))); return resources; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java index e59a427f9f..cec02bcad5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java @@ -15,6 +15,11 @@ */ package it.infn.mw.iam.api.client.management; +import static it.infn.mw.iam.api.client.util.ClientSuppliers.clientNotFound; +import static it.infn.mw.iam.api.common.PagingUtils.buildPageRequest; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.NO_CONTENT; + import java.text.ParseException; import java.time.format.DateTimeFormatter; import java.util.Optional; @@ -22,12 +27,9 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolationException; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.NO_CONTENT; import org.springframework.http.converter.json.MappingJacksonValue; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; @@ -52,15 +54,14 @@ import it.infn.mw.iam.api.client.management.service.ClientManagementService; import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.client.util.ClientSuppliers; -import static it.infn.mw.iam.api.client.util.ClientSuppliers.clientNotFound; import it.infn.mw.iam.api.common.ClientViews; import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.api.common.ListResponseDTO; import it.infn.mw.iam.api.common.PagingUtils; -import static it.infn.mw.iam.api.common.PagingUtils.buildPageRequest; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.scim.model.ScimUser; import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; @SuppressWarnings("deprecation") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java index 44cf72a489..390f7948dd 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java @@ -15,6 +15,9 @@ */ package it.infn.mw.iam.api.client.management.service; +import static it.infn.mw.iam.api.client.util.ClientSuppliers.accountNotFound; +import static it.infn.mw.iam.api.client.util.ClientSuppliers.clientNotFound; + import java.text.ParseException; import java.time.Clock; import java.util.Date; @@ -24,9 +27,6 @@ import javax.validation.constraints.NotBlank; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.OIDCTokenService; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -40,8 +40,6 @@ import it.infn.mw.iam.api.client.service.ClientDefaultsService; import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.client.util.ClientSuppliers; -import static it.infn.mw.iam.api.client.util.ClientSuppliers.accountNotFound; -import static it.infn.mw.iam.api.client.util.ClientSuppliers.clientNotFound; import it.infn.mw.iam.api.common.ListResponseDTO; import it.infn.mw.iam.api.common.PagingUtils; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; @@ -55,11 +53,15 @@ import it.infn.mw.iam.audit.events.client.ClientStatusChangedEvent; import it.infn.mw.iam.audit.events.client.ClientUpdatedEvent; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; import it.infn.mw.iam.notification.NotificationFactory; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +@SuppressWarnings("deprecation") @Service @Validated public class DefaultClientManagementService implements ClientManagementService { @@ -70,15 +72,15 @@ public class DefaultClientManagementService implements ClientManagementService { private final ClientDefaultsService defaultsService; private final UserConverter userConverter; private final IamAccountRepository accountRepo; - private final OIDCTokenService oidcTokenService; private final IamTokenService tokenService; + private final TokenRevocationService revocationService; private final ApplicationEventPublisher eventPublisher; private final NotificationFactory notificationFactory; public DefaultClientManagementService(Clock clock, ClientService clientService, ClientConverter converter, ClientDefaultsService defaultsService, UserConverter userConverter, - IamAccountRepository accountRepo, OIDCTokenService oidcTokenService, - IamTokenService tokenService, ApplicationEventPublisher aep, + IamAccountRepository accountRepo, + IamTokenService tokenService, TokenRevocationService revocationService, ApplicationEventPublisher aep, NotificationFactory notificationFactory) { this.clock = clock; this.clientService = clientService; @@ -86,8 +88,8 @@ public DefaultClientManagementService(Clock clock, ClientService clientService, this.defaultsService = defaultsService; this.userConverter = userConverter; this.accountRepo = accountRepo; - this.oidcTokenService = oidcTokenService; this.tokenService = tokenService; + this.revocationService = revocationService; this.eventPublisher = aep; this.notificationFactory = notificationFactory; } @@ -254,9 +256,8 @@ public void removeClientOwner(String clientId, String accountId) { private OAuth2AccessTokenEntity createRegistrationAccessTokenForClient( ClientDetailsEntity client) { - OAuth2AccessTokenEntity token = oidcTokenService.createRegistrationAccessToken(client); - return tokenService.saveAccessToken(token); + return tokenService.createRegistrationAccessToken(client); } @Override @@ -265,11 +266,9 @@ public RegisteredClientDTO rotateRegistrationAccessToken(@NotBlank String client clientService.findClientByClientId(clientId).orElseThrow(clientNotFound(clientId)); OAuth2AccessTokenEntity rat = - Optional.ofNullable(oidcTokenService.rotateRegistrationAccessTokenForClient(client)) + Optional.ofNullable(tokenService.rotateRegistrationAccessTokenForClient(client)) .orElse(createRegistrationAccessTokenForClient(client)); - tokenService.saveAccessToken(rat); - eventPublisher.publishEvent(new ClientRegistrationAccessTokenRotatedEvent(this, client)); RegisteredClientDTO response = converter.registeredClientDtoFromEntity(client); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/validation/ClientIdAvailableValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/validation/ClientIdAvailableValidator.java index e6c085098c..c0dca2ba50 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/validation/ClientIdAvailableValidator.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/validation/ClientIdAvailableValidator.java @@ -18,18 +18,16 @@ import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @Component @Scope("prototype") public class ClientIdAvailableValidator implements ConstraintValidator { private final IamClientRepository clientRepo; - @Autowired public ClientIdAvailableValidator(IamClientRepository clientRepo) { this.clientRepo = clientRepo; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/ClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/ClientRegistrationService.java index be12931642..c231f47432 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/ClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/ClientRegistrationService.java @@ -29,12 +29,18 @@ public interface ClientRegistrationService { RegisteredClientDTO registerClient(@Valid RegisteredClientDTO request, Authentication authentication) throws ParseException; + RegisteredClientDTO registerProtectedResource(@Valid RegisteredClientDTO request, + Authentication authentication) throws ParseException; + RegisteredClientDTO retrieveClient(@NotBlank String clientId, Authentication authentication); RegisteredClientDTO updateClient(@NotBlank String clientId, @Valid RegisteredClientDTO request, Authentication authentication) throws ParseException; + RegisteredClientDTO updateProtectedResource(@NotBlank String clientId, @Valid RegisteredClientDTO request, + Authentication authentication) throws ParseException; + void deleteClient(@NotBlank String clientId, Authentication authentication); RegisteredClientDTO redeemClient(@NotBlank String clientId, diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java index 2acb1397d6..9b47cae82b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java @@ -15,23 +15,25 @@ */ package it.infn.mw.iam.api.client.registration.service; +import static it.infn.mw.iam.api.client.util.ClientSuppliers.clientNotFound; +import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.ADMINISTRATORS; +import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.ANYONE; +import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.REGISTERED_USERS; +import static java.util.Objects.isNull; +import static java.util.stream.Collectors.toSet; + import java.text.ParseException; import java.time.Clock; import java.time.Instant; import java.util.EnumSet; -import static java.util.Objects.isNull; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.function.Supplier; -import static java.util.stream.Collectors.toSet; +import javax.validation.Valid; 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.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.service.OIDCTokenService; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.access.AccessDeniedException; @@ -50,7 +52,7 @@ import it.infn.mw.iam.api.client.service.ClientConverter; import it.infn.mw.iam.api.client.service.ClientDefaultsService; import it.infn.mw.iam.api.client.service.ClientService; -import static it.infn.mw.iam.api.client.util.ClientSuppliers.clientNotFound; +import it.infn.mw.iam.api.client.service.DefaultClientDefaultsService; import it.infn.mw.iam.api.common.client.AuthorizationGrantType; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.audit.events.account.client.AccountClientOwnerAssigned; @@ -60,13 +62,16 @@ import it.infn.mw.iam.audit.events.client.ClientUpdatedEvent; import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties; import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy; -import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.ADMINISTRATORS; -import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.ANYONE; -import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.REGISTERED_USERS; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcher; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcherRegistry; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientRelyingPartyEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @Service @ConditionalOnProperty(name = "client-registration.enable", havingValue = "true", @@ -93,8 +98,8 @@ public class DefaultClientRegistrationService implements ClientRegistrationServi private final AccountUtils accountUtils; private final ClientConverter converter; private final ClientDefaultsService defaultsService; - private final OIDCTokenService clientTokenService; private final IamTokenService tokenService; + private final TokenRevocationService revocationService; private final SystemScopeService systemScopeService; private final ClientRegistrationProperties registrationProperties; private final ScopeMatcherRegistry scopeMatcherRegistry; @@ -102,7 +107,7 @@ public class DefaultClientRegistrationService implements ClientRegistrationServi public DefaultClientRegistrationService(Clock clock, ClientService clientService, AccountUtils accountUtils, ClientConverter converter, ClientDefaultsService defaultsService, - OIDCTokenService clientTokenService, IamTokenService tokenService, + IamTokenService tokenService, TokenRevocationService revocationService, SystemScopeService scopeService, ClientRegistrationProperties registrationProperties, ScopeMatcherRegistry scopeMatcherRegistry, ApplicationEventPublisher aep) { @@ -111,8 +116,8 @@ public DefaultClientRegistrationService(Clock clock, ClientService clientService this.accountUtils = accountUtils; this.converter = converter; this.defaultsService = defaultsService; - this.clientTokenService = clientTokenService; this.tokenService = tokenService; + this.revocationService = revocationService; this.systemScopeService = scopeService; this.registrationProperties = registrationProperties; this.scopeMatcherRegistry = scopeMatcherRegistry; @@ -272,7 +277,18 @@ private boolean registrationAccessTokenAuthenticationValidForClientId(String cli return false; } + private boolean resourceAccessTokenAuthenticationValidForClientId(String clientId, + Authentication authentication) { + if (authentication instanceof OAuth2Authentication oauth) { + return oauth.getOAuth2Request().getClientId().equals(clientId) + && oauth.getOAuth2Request().getScope().contains(SystemScopeService.RESOURCE_TOKEN_SCOPE); + } + + return false; + } + private boolean ratHasExpired(OAuth2AccessTokenEntity token) throws ParseException { + final int defaultRatValiditySeconds = registrationProperties.getClientDefaults() .getDefaultRegistrationAccessTokenValiditySeconds(); @@ -317,19 +333,36 @@ && registrationAccessTokenAuthenticationValidForClientId(client.getClientId(), a try { if (ratHasExpired(token)) { - tokenService.revokeAccessToken(token); - token = clientTokenService.createRegistrationAccessToken(client); - tokenService.saveAccessToken(token); + token = tokenService.createRegistrationAccessToken(client); + return Optional.of(token.getValue()); + } + } catch (ParseException e) { + revocationService.revokeAccessToken(token); + token = tokenService.createRegistrationAccessToken(client); + return Optional.of(token.getValue()); + } + } + + return Optional.empty(); + } + + private Optional maybeUpdateResourceAccessToken(ClientDetailsEntity client, + Authentication auth) { + + if ((auth instanceof OAuth2Authentication oauth) + && resourceAccessTokenAuthenticationValidForClientId(client.getClientId(), auth)) { + + OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) oauth.getDetails(); + OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue()); + + try { + if (ratHasExpired(token)) { + token = tokenService.createResourceAccessToken(client); return Optional.of(token.getValue()); - } else { - return Optional.empty(); } } catch (ParseException e) { - // if there's a problem in parsing the token, we consider it - // expired and issue a new one - tokenService.revokeAccessToken(token); - token = clientTokenService.createRegistrationAccessToken(client); - tokenService.saveAccessToken(token); + revocationService.revokeAccessToken(token); + token = tokenService.createResourceAccessToken(client); return Optional.of(token.getValue()); } } @@ -384,8 +417,7 @@ public RegisteredClientDTO registerClient(RegisteredClientDTO request, if (!hasRelyingParty(request) && isAnonymous(authentication)) { - OAuth2AccessTokenEntity ratEntity = clientTokenService.createRegistrationAccessToken(client); - tokenService.saveAccessToken(ratEntity); + OAuth2AccessTokenEntity ratEntity = tokenService.createRegistrationAccessToken(client); response.setRegistrationAccessToken(ratEntity.getValue()); } else if (!isAnonymous(authentication)) { @@ -403,11 +435,59 @@ public RegisteredClientDTO registerClient(RegisteredClientDTO request, return response; } + @Override + public RegisteredClientDTO registerProtectedResource(@Valid RegisteredClientDTO request, + Authentication authentication) throws ParseException { + + authzChecks(authentication); + + ClientDetailsEntity client = converter.entityFromRegistrationRequest(request); + + defaultsService.setupProtectedResourceDefaults(client); + + client.setClientId(UUID.randomUUID().toString()); + if (isNull(client.getTokenEndpointAuthMethod())) { + client.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC); + } + if (DefaultClientDefaultsService.AUTH_METHODS_REQUIRING_SECRET + .contains(client.getTokenEndpointAuthMethod())) { + client.setClientSecret(defaultsService.generateClientSecret()); + } else { + client.setClientSecret(null); + } + client.setActive(true); + cleanupRequestedScopes(client, authentication); + + client = clientService.saveNewClient(client); + + RegisteredClientDTO response = converter.registrationResponseFromClient(client); + + if (isAnonymous(authentication)) { + + OAuth2AccessTokenEntity ratEntity = tokenService.createResourceAccessToken(client); + response.setRegistrationAccessToken(ratEntity.getValue()); + + } else { + + IamAccount account = + accountUtils.getAuthenticatedUserAccount(authentication).orElseThrow(noAuthUserError()); + + client.getContacts().add(account.getUserInfo().getEmail()); + + clientService.linkClientToAccount(client, account); + } + + eventPublisher.publishEvent(new ClientRegistered(this, client)); + + return response; + } + private Optional lookupClient(String clientId, Authentication authentication) { if (isAnonymous(authentication)) { - if (!registrationAccessTokenAuthenticationValidForClientId(clientId, authentication)) { + if (!registrationAccessTokenAuthenticationValidForClientId(clientId, authentication) + && !resourceAccessTokenAuthenticationValidForClientId(clientId, authentication)) { throw new InvalidClientRegistrationRequest(INVALID_ACCESS_TOKEN_ERROR); } @@ -444,7 +524,6 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req ClientDetailsEntity newClient = converter.entityFromRegistrationRequest(request); newClient.setId(oldClient.getId()); - newClient.setClientSecret(oldClient.getClientSecret()); newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds()); newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds()); newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds()); @@ -480,6 +559,42 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req return response; } + @Validated(OnDynamicClientUpdate.class) + @Override + public RegisteredClientDTO updateProtectedResource(String clientId, RegisteredClientDTO request, + Authentication authentication) throws ParseException { + authzChecks(authentication); + + ClientDetailsEntity oldClient = + lookupClient(clientId, authentication).orElseThrow(clientNotFound(clientId)); + + checkUserUpdatingSuspendedClient(authentication, oldClient); + cleanupRequestedScopesOnUpdate(request, authentication, oldClient); + + ClientDetailsEntity newClient = converter.entityFromRegistrationRequest(request); + defaultsService.setupProtectedResourceDefaults(newClient); + + newClient.setId(oldClient.getId()); + newClient.setClientSecret(oldClient.getClientSecret()); + newClient.setActive(oldClient.isActive()); + + if (registrationProperties.isAdminOnlyCustomScopes() && !accountUtils.isAdmin(authentication)) { + removeCustomScopes(newClient); + } + + ClientDetailsEntity savedClient = clientService.updateClient(newClient); + + eventPublisher.publishEvent(new ClientUpdatedEvent(this, savedClient)); + + RegisteredClientDTO response = converter.registrationResponseFromClient(savedClient); + + maybeUpdateResourceAccessToken(savedClient, authentication).ifPresent(t -> { + eventPublisher.publishEvent(new ClientRegistrationAccessTokenRotatedEvent(this, savedClient)); + response.setRegistrationAccessToken(t); + }); + return response; + } + @Override public void deleteClient(String clientId, Authentication authentication) { authzChecks(authentication); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DisabledClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DisabledClientRegistrationService.java index 2f699c7b62..40fcabe441 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DisabledClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DisabledClientRegistrationService.java @@ -15,6 +15,8 @@ */ package it.infn.mw.iam.api.client.registration.service; +import java.text.ParseException; + import javax.validation.Valid; import javax.validation.constraints.NotBlank; @@ -73,4 +75,16 @@ public RegisteredClientDTO redeemClient(@NotBlank String clientId, @NotBlank String registrationAccessToken, Authentication authentication) { return registrationDisabled(); } + + @Override + public RegisteredClientDTO registerProtectedResource(@Valid RegisteredClientDTO request, + Authentication authentication) throws ParseException { + return registrationDisabled(); + } + + @Override + public RegisteredClientDTO updateProtectedResource(@NotBlank String clientId, + @Valid RegisteredClientDTO request, Authentication authentication) throws ParseException { + return registrationDisabled(); + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidRedirectURIsValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidRedirectURIsValidator.java index 04fb2cf739..ab8906d0cd 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidRedirectURIsValidator.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidRedirectURIsValidator.java @@ -22,11 +22,11 @@ import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; -import org.mitre.openid.connect.service.BlacklistedSiteService; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; +import it.infn.mw.iam.core.oauth.BlacklistedSiteService; @Component @Scope("prototype") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidTokenEndpointAuthMethodValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidTokenEndpointAuthMethodValidator.java index 1773c18d9d..0d6f48e21a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidTokenEndpointAuthMethodValidator.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/validation/ValidTokenEndpointAuthMethodValidator.java @@ -20,8 +20,6 @@ import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; -import com.google.common.base.Strings; - import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; @@ -35,7 +33,7 @@ public boolean isValid(RegisteredClientDTO value, ConstraintValidatorContext con value.setTokenEndpointAuthMethod(TokenEndpointAuthenticationMethod.client_secret_basic); } else if (value.getTokenEndpointAuthMethod() .equals(TokenEndpointAuthenticationMethod.private_key_jwt) - && (Strings.isNullOrEmpty(value.getJwksUri()) && Strings.isNullOrEmpty(value.getJwk()))) { + && (isNullOrEmpty(value.getJwksUri()) && isNullOrEmpty(value.getJwk()))) { context.disableDefaultConstraintViolation(); context .buildConstraintViolationWithTemplate("private_key_jwt requires a jwks uri or a jwk value") @@ -46,4 +44,8 @@ public boolean isValid(RegisteredClientDTO value, ConstraintValidatorContext con return true; } -} \ No newline at end of file + private boolean isNullOrEmpty(String s) { + return s == null || s.isBlank(); + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/search/service/DefaultClientSearchService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/search/service/DefaultClientSearchService.java index 34dec3dbbe..630536d4c7 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/search/service/DefaultClientSearchService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/search/service/DefaultClientSearchService.java @@ -17,7 +17,6 @@ import java.util.stream.Collectors; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -34,11 +33,13 @@ import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.common.error.NoAuthenticatedUserError; import it.infn.mw.iam.api.common.form.PaginatedRequestForm; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.client.ClientSpecs; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; + @Service @Validated diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java index 9c5b4adf65..042c6d288f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java @@ -23,12 +23,8 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; -import org.mitre.oauth2.model.PKCEAlgorithm; import org.springframework.stereotype.Component; -import com.google.common.base.Strings; import com.nimbusds.jose.jwk.JWKSet; import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; @@ -38,6 +34,9 @@ import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; @Component public class ClientConverter { @@ -190,9 +189,9 @@ public ClientDetailsEntity entityFromRegistrationRequest(RegisteredClientDTO dto client.setClientUri(dto.getClientUri()); - if (!Strings.isNullOrEmpty(dto.getJwksUri())) { + if (dto.getJwksUri() != null && !dto.getJwksUri().isBlank()) { client.setJwksUri(dto.getJwksUri()); - } else if (!Strings.isNullOrEmpty(dto.getJwk())) { + } else if (dto.getJwk() != null && !dto.getJwk().isBlank()) { client.setJwks(JWKSet.parse(dto.getJwk())); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientDefaultsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientDefaultsService.java index 7cc1120d71..28f3dc52d8 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientDefaultsService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientDefaultsService.java @@ -15,10 +15,13 @@ */ package it.infn.mw.iam.api.client.service; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public interface ClientDefaultsService { ClientDetailsEntity setupClientDefaults(ClientDetailsEntity client); + + ClientDetailsEntity setupProtectedResourceDefaults(ClientDetailsEntity client); + String generateClientSecret(); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java index 96ee180c75..817563ef82 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java @@ -17,10 +17,10 @@ import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; @@ -50,4 +50,6 @@ Optional findClientByClientIdAndAccount(String clientId, void deleteClient(ClientDetailsEntity client); void useClient(ClientDetailsEntity client); + + void disableExpiredClients(); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientDefaultsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientDefaultsService.java index a88c27ab3c..4cd2cc45f5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientDefaultsService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientDefaultsService.java @@ -24,19 +24,17 @@ import java.util.UUID; import org.apache.commons.codec.binary.Base64; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.springframework.stereotype.Service; -import com.google.common.collect.Sets; - import it.infn.mw.iam.authn.util.Authorities; import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; @Service public class DefaultClientDefaultsService implements ClientDefaultsService { - private static final Set AUTH_METHODS_REQUIRING_SECRET = + public static final Set AUTH_METHODS_REQUIRING_SECRET = EnumSet.of(AuthMethod.SECRET_BASIC, AuthMethod.SECRET_POST, AuthMethod.SECRET_JWT); private static final int SECRET_SIZE = 512; @@ -88,11 +86,48 @@ public ClientDetailsEntity setupClientDefaults(ClientDetailsEntity client) { client.setClientSecret(generateClientSecret()); } - client.setAuthorities(Sets.newHashSet(Authorities.ROLE_CLIENT)); + client.setAuthorities(Set.of(Authorities.ROLE_CLIENT)); client.setClearAccessTokensOnRefresh(false); return client; } + @Override + public ClientDetailsEntity setupProtectedResourceDefaults(ClientDetailsEntity client) { + + client.setAccessTokenValiditySeconds(0); + client.setRefreshTokenValiditySeconds(0); + client.setIdTokenValiditySeconds(0); + client.setDeviceCodeValiditySeconds(0); + + client.setGrantTypes(Set.of()); + client.setResponseTypes(Set.of()); + client.setRedirectUris(Set.of()); + + client.setAllowIntrospection(true); + client.setDynamicallyRegistered(true); + + client.setAuthorities(Set.of(Authorities.ROLE_CLIENT)); + client.setClearAccessTokensOnRefresh(false); + + client.setDefaultACRvalues(Set.of()); + client.setDefaultMaxAge(null); + client.setIdTokenEncryptedResponseAlg(null); + client.setIdTokenEncryptedResponseEnc(null); + client.setIdTokenSignedResponseAlg(null); + client.setInitiateLoginUri(null); + client.setPostLogoutRedirectUris(null); + client.setRequestObjectSigningAlg(null); + client.setRequireAuthTime(null); + client.setReuseRefreshToken(false); + client.setSectorIdentifierUri(null); + client.setSubjectType(null); + client.setUserInfoEncryptedResponseAlg(null); + client.setUserInfoEncryptedResponseEnc(null); + client.setUserInfoSignedResponseAlg(null); + + return client; + } + @Override public String generateClientSecret() { return diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java index 8140381962..0861846a16 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java @@ -21,8 +21,6 @@ import java.util.Optional; import java.util.function.Supplier; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientLastUsedEntity; import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; @@ -32,11 +30,13 @@ import it.infn.mw.iam.audit.events.client.ClientCreatedEvent; import it.infn.mw.iam.core.oauth.scope.matchers.DefaultScopeMatcherRegistry; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientLastUsedEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.client.ClientSpecs; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; @Service @Transactional @@ -50,9 +50,7 @@ public class DefaultClientService implements ClientService { private ApplicationEventPublisher eventPublisher; public DefaultClientService(Clock clock, IamClientRepository clientRepo, - IamAccountClientRepository accountClientRepo, - ApplicationEventPublisher eventPublisher - ) { + IamAccountClientRepository accountClientRepo, ApplicationEventPublisher eventPublisher) { this.clock = clock; this.clientRepo = clientRepo; this.accountClientRepo = accountClientRepo; @@ -81,7 +79,7 @@ private Supplier newAccountClient(IamAccount owner, @Override public ClientDetailsEntity linkClientToAccount(ClientDetailsEntity client, IamAccount owner) { IamAccountClient ac = accountClientRepo.findByAccountAndClient(owner, client) - .orElseGet(newAccountClient(owner, client)); + .orElseGet(newAccountClient(owner, client)); return ac.getClient(); } @@ -101,7 +99,8 @@ public ClientDetailsEntity updateClient(ClientDetailsEntity client) { } @Override - public ClientDetailsEntity updateClientStatus(ClientDetailsEntity client, boolean status, String userId) { + public ClientDetailsEntity updateClientStatus(ClientDetailsEntity client, boolean status, + String userId) { client.setActive(status); client.setStatusChangedBy(userId); client.setStatusChangedOn(Date.from(clock.instant())); @@ -122,7 +121,7 @@ public Optional findClientByClientIdAndAccount(String clien if (maybeClient.isPresent()) { return accountClientRepo.findByAccountAndClientId(account, maybeClient.get().getId()) - .map(IamAccountClient::getClient); + .map(IamAccountClient::getClient); } return Optional.empty(); @@ -167,4 +166,11 @@ public void useClient(ClientDetailsEntity client) { } } + @Override + public void disableExpiredClients() { + + clientRepo.findActiveClientsExpiredBefore(new Date()) + .forEach(client -> updateClientStatus(client, false, "expired_client_task")); + } + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/ListResponseDTO.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/ListResponseDTO.java index 913800bd41..56d7f3590f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/ListResponseDTO.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/ListResponseDTO.java @@ -15,6 +15,7 @@ */ package it.infn.mw.iam.api.common; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -27,7 +28,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonView; -import com.google.common.collect.Lists; @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) @@ -122,7 +122,8 @@ public Builder resources(List resources) { } public Builder zeroIndexedSingleResource(T element) { - this.resources = Lists.newArrayList(element); + this.resources = new ArrayList<>(); + this.resources.add(element); this.totalResults = 1L; this.itemsPerPage = 10; this.startIndex = 0; @@ -130,7 +131,8 @@ public Builder zeroIndexedSingleResource(T element) { } public Builder singleResource(T element) { - this.resources = Lists.newArrayList(element); + this.resources = new ArrayList<>(); + this.resources.add(element); this.totalResults = 1L; this.itemsPerPage = 10; this.startIndex = 1; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/OffsetPageable.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/OffsetPageable.java index 1c3e1eaef1..31c87d02a3 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/OffsetPageable.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/OffsetPageable.java @@ -15,8 +15,6 @@ */ package it.infn.mw.iam.api.common; -import static com.google.common.base.Preconditions.checkArgument; - import java.util.Objects; import javax.annotation.Generated; @@ -48,8 +46,12 @@ public OffsetPageable(int offset, int count) { public OffsetPageable(int offset, int count, Sort sort) { - checkArgument(offset >= 0, "offset must be greater or equal to 0"); - checkArgument(count >= 1, "count must be a positive integer"); + if (offset < 0) { + throw new IllegalArgumentException("offset must be greater or equal to 0"); + } + if (count < 1) { + throw new IllegalArgumentException("count must be a positive integer"); + } this.offset = offset; this.count = count; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java index b496d1de38..f2ad402cfc 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java @@ -17,13 +17,13 @@ import java.time.LocalDate; import java.util.Date; +import java.util.HashSet; import java.util.Set; import javax.validation.Valid; import javax.validation.constraints.Email; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Null; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; @@ -37,7 +37,6 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.Sets; import it.infn.mw.iam.api.client.management.validation.ClientIdAvailable; import it.infn.mw.iam.api.client.management.validation.OnClientCreation; @@ -130,7 +129,6 @@ public class RegisteredClientDTO { private Set<@Email(groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, OnClientCreation.class, OnClientUpdate.class}) String> contacts; - @NotEmpty(message = "Invalid client: empty grant type") @JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class, ClientViews.NoSecretDynamicRegistration.class, ClientViews.DynamicRegistration.class}) private Set grantTypes; @@ -175,7 +173,7 @@ public class RegisteredClientDTO { message = "must not include blank strings") @Size(min = 1, max = 2048, message = "string size must be between 1 and 2048", groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, - OnClientCreation.class, OnClientUpdate.class}) String> scope = Sets.newHashSet(); + OnClientCreation.class, OnClientUpdate.class}) String> scope = new HashSet<>(); @Min(value = 0, groups = OnClientCreation.class) @JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class, @@ -229,10 +227,6 @@ public class RegisteredClientDTO { ClientViews.NoSecretDynamicRegistration.class, ClientViews.DynamicRegistration.class}) private String registrationClientUri; - @JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class, - ClientViews.NoSecretDynamicRegistration.class, ClientViews.DynamicRegistration.class}) - private Date clientSecretExpiresAt; - @JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class, ClientViews.NoSecretDynamicRegistration.class, ClientViews.DynamicRegistration.class}) private Date clientIdIssuedAt; @@ -486,14 +480,6 @@ public void setRegistrationClientUri(String registrationClientUri) { this.registrationClientUri = registrationClientUri; } - public Date getClientSecretExpiresAt() { - return clientSecretExpiresAt; - } - - public void setClientSecretExpiresAt(Date clientSecretExpiresAt) { - this.clientSecretExpiresAt = clientSecretExpiresAt; - } - public Date getClientIdIssuedAt() { return clientIdIssuedAt; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyConverter.java index 3aaea06ae3..138dfca318 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyConverter.java @@ -15,9 +15,9 @@ */ package it.infn.mw.iam.api.exchange_policy; -import static com.google.common.collect.Lists.newArrayList; import static java.util.stream.Collectors.toSet; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -98,7 +98,7 @@ public ExchangePolicyDTO dtoFromEntity(IamTokenExchangePolicyEntity entity) { dto.setDestinationClient(destination); - List scopePolicies = newArrayList(); + List scopePolicies = new ArrayList<>(); for (IamTokenExchangeScopePolicy p : entity.getScopePolicies()) { scopePolicies.add(dtoFromScopePolicy(p)); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyDTO.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyDTO.java index 57c9c97ac0..de2584217e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyDTO.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/exchange_policy/ExchangePolicyDTO.java @@ -15,6 +15,7 @@ */ package it.infn.mw.iam.api.exchange_policy; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -24,7 +25,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.Lists; import it.infn.mw.iam.api.scim.controller.utils.JsonDateSerializer; import it.infn.mw.iam.persistence.model.PolicyRule; @@ -55,8 +55,8 @@ public class ExchangePolicyDTO { private ClientMatchingPolicyDTO destinationClient; @Valid - private List scopePolicies = Lists.newArrayList(); - + private List scopePolicies = new ArrayList<>(); + public ExchangePolicyDTO() { // empty on purpose } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupController.java index c96dadb188..1efde9e42d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupController.java @@ -15,6 +15,7 @@ */ package it.infn.mw.iam.api.group; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -34,8 +35,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.common.AttributeDTO; import it.infn.mw.iam.api.common.AttributeDTOConverter; import it.infn.mw.iam.api.common.ErrorDTO; @@ -107,7 +106,7 @@ public List getAttributes(@PathVariable String id){ IamGroup entity = groupService.findByUuid(id).orElseThrow(()->NoSuchGroupError.forUuid(id)); - List results = Lists.newArrayList(); + List results = new ArrayList<>(); entity.getAttributes().forEach(a -> results.add(attributeConverter.dtoFromEntity(a))); return results; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupLabelsController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupLabelsController.java index bdd73abef2..dd40b91804 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupLabelsController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/group/GroupLabelsController.java @@ -19,6 +19,7 @@ import static java.lang.String.format; import static org.springframework.http.HttpStatus.NO_CONTENT; +import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpStatus; @@ -35,8 +36,6 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.api.common.LabelDTO; import it.infn.mw.iam.api.common.LabelDTOConverter; @@ -72,7 +71,7 @@ public List getLabels(@PathVariable String id) { IamGroup group = service.findByUuid(id).orElseThrow(() -> NoSuchGroupError.forUuid(id)); - List results = Lists.newArrayList(); + List results = new ArrayList<>(); group.getLabels().forEach(l -> results.add(converter.dtoFromEntity(l))); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/AccessTokenConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/AccessTokenConverter.java new file mode 100644 index 0000000000..26441baeb9 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/AccessTokenConverter.java @@ -0,0 +1,43 @@ +/** + * 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.api.legacy; + +import org.springframework.stereotype.Component; + +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; + +@Component +public class AccessTokenConverter { + + AccessTokenDTO fromEntityToDTO(OAuth2AccessTokenEntity entity) { + + AccessTokenDTO.Builder builder = AccessTokenDTO.builder(); + + builder.id(entity.getId()); + builder.value(entity.getValue()); + builder.expiration(entity.getExpiration()); + builder.clientId(entity.getClient().getClientId()); + builder.scopes(entity.getScope()); + + if (entity.getAuthenticationHolder().getUserAuth() != null) { + builder.userId(entity.getAuthenticationHolder().getUserAuth().getName()); + } + if (entity.getRefreshToken() != null) { + builder.refreshTokenId(entity.getRefreshToken().getId()); + } + return builder.build(); + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/AccessTokenDTO.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/AccessTokenDTO.java new file mode 100644 index 0000000000..fa14f02662 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/AccessTokenDTO.java @@ -0,0 +1,126 @@ +/** + * 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.api.legacy; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +public class AccessTokenDTO { + + private final Long id; + private final String value; + private final Long refreshTokenId; + private final Set scopes; + private final String clientId; + private final String userId; + private final Date expiration; + + private AccessTokenDTO(Long id, String value, Long refreshTokenId, Set scopes, + String clientId, String userId, Date expiration) { + this.id = id; + this.value = value; + this.refreshTokenId = refreshTokenId; + this.scopes = scopes; + this.clientId = clientId; + this.userId = userId; + this.expiration = expiration; + } + + public Long getId() { + return id; + } + + public String getValue() { + return value; + } + + public Long getRefreshTokenId() { + return refreshTokenId; + } + + public Set getScopes() { + return scopes; + } + + public String getClientId() { + return clientId; + } + + public String getUserId() { + return userId; + } + + public Date getExpiration() { + return expiration; + } + + public static class Builder { + private Long id; + private String value; + private Long refreshTokenId; + private Set scopes; + private String clientId; + private String userId; + private Date expiration; + + private Builder() {} + + public Builder id(Long id) { + this.id = id; + return this; + } + + public Builder value(String value) { + this.value = value; + return this; + } + + public Builder refreshTokenId(Long refreshTokenId) { + this.refreshTokenId = refreshTokenId; + return this; + } + + public Builder scopes(Set scopes) { + this.scopes = new HashSet<>(); + this.scopes.addAll(scopes); + return this; + } + + public Builder clientId(String clientId) { + this.clientId = clientId; + return this; + } + + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + public Builder expiration(Date expiration) { + this.expiration = expiration; + return this; + } + + public AccessTokenDTO build() { + return new AccessTokenDTO(id, value, refreshTokenId, scopes, clientId, userId, expiration); + } + } + + public static Builder builder() { + return new Builder(); + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyApiClientsController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyApiClientsController.java new file mode 100644 index 0000000000..2b653433f8 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyApiClientsController.java @@ -0,0 +1,54 @@ +/** + * 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.api.legacy; + +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.fasterxml.jackson.annotation.JsonView; + +import it.infn.mw.iam.api.client.service.ClientConverter; +import it.infn.mw.iam.api.client.service.ClientService; +import it.infn.mw.iam.api.common.ClientViews; +import it.infn.mw.iam.api.common.client.RegisteredClientDTO; + +@RestController +public class LegacyApiClientsController { + + private final ClientService service; + private final ClientConverter converter; + + public LegacyApiClientsController(ClientService service, ClientConverter converter) { + this.service = service; + this.converter = converter; + } + + @JsonView({ClientViews.Limited.class}) + @GetMapping(value = "/api/clients") + @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN')") + List getAllClients() { + return service.findAll(Pageable.unpaged()) + .getContent() + .stream() + .map(converter::registeredClientDtoFromEntity) + .collect(Collectors.toList()); + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyApiTokensController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyApiTokensController.java new file mode 100644 index 0000000000..8d47f01431 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyApiTokensController.java @@ -0,0 +1,57 @@ +/** + * 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.api.legacy; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import it.infn.mw.iam.api.account.AccountUtils; +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.persistence.model.IamAccount; + +@RestController +public class LegacyApiTokensController { + + private final OAuth2TokenEntityService tokenService; + private final AccessTokenConverter accessTokenConverter; + private final AccountUtils accountUtils; + + public LegacyApiTokensController(AccessTokenConverter accessTokenConverter, + OAuth2TokenEntityService tokenService, AccountUtils accountUtils) { + this.accessTokenConverter = accessTokenConverter; + this.tokenService = tokenService; + this.accountUtils = accountUtils; + } + + @GetMapping(value = "/api/tokens/access") + @PreAuthorize("hasRole('ROLE_USER')") + List getAccessTokensForAuthenticatedUser() { + + Optional account = accountUtils.getAuthenticatedUserAccount(); + if (account.isPresent()) { + tokenService.getAllAccessTokensForUser(account.get().getUsername()) + .stream() + .map(accessTokenConverter::fromEntityToDTO) + .collect(Collectors.toList()); + } + return List.of(); + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyProtectedResourceController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyProtectedResourceController.java new file mode 100644 index 0000000000..843cfd75fc --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/legacy/LegacyProtectedResourceController.java @@ -0,0 +1,103 @@ +/** + * 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.api.legacy; + +import static org.springframework.http.HttpStatus.CREATED; + +import java.text.ParseException; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.fasterxml.jackson.annotation.JsonView; + +import it.infn.mw.iam.api.client.registration.service.ClientRegistrationService; +import it.infn.mw.iam.api.common.ClientViews; +import it.infn.mw.iam.api.common.ErrorDTO; +import it.infn.mw.iam.api.common.client.RegisteredClientDTO; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; + +@SuppressWarnings("deprecation") +@RestController +@RequestMapping(value = "/resource") +public class LegacyProtectedResourceController { + + private final ClientRegistrationService service; + + public LegacyProtectedResourceController(ClientRegistrationService service) { + this.service = service; + } + + @PostMapping + @ResponseStatus(code = CREATED) + @JsonView({ClientViews.DynamicRegistration.class}) + public RegisteredClientDTO registerNewProtectedResource(@RequestBody RegisteredClientDTO request, + Authentication authentication) throws ParseException { + + return service.registerProtectedResource(request, authentication); + } + + @PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + @JsonView({ClientViews.NoSecretDynamicRegistration.class}) + public RegisteredClientDTO readResourceConfiguration(@PathVariable("id") String clientId, + OAuth2Authentication auth) { + + return service.retrieveClient(clientId, auth); + } + + @PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") + @PutMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + @JsonView({ClientViews.NoSecretDynamicRegistration.class}) + public RegisteredClientDTO updateProtectedResource(@PathVariable("id") String clientId, + @RequestBody RegisteredClientDTO clientDTO, OAuth2Authentication auth) throws ParseException { + + return service.updateProtectedResource(clientId, clientDTO, auth); + } + + @PreAuthorize("hasRole('ROLE_CLIENT') and #oauth2.hasScope('" + + SystemScopeService.RESOURCE_TOKEN_SCOPE + "')") + @DeleteMapping(value = "/{id}") + public void deleteResource(@PathVariable("id") String clientId, + OAuth2Authentication auth) { + + service.deleteClient(clientId, auth); + } + + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + @ExceptionHandler(ParseException.class) + public ErrorDTO badRequestedDtoError(HttpServletRequest req, Exception ex) { + return ErrorDTO.fromString(ex.getMessage()); + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationRegistrationController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationRegistrationController.java index 6349f660ca..3fe213bd76 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationRegistrationController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationRegistrationController.java @@ -22,9 +22,8 @@ import java.util.Set; import java.util.stream.Collectors; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -52,10 +51,11 @@ import it.infn.mw.iam.core.oidc.InvalidClientMetadataException; import it.infn.mw.iam.core.oidc.InvalidTrustChainException; import it.infn.mw.iam.core.oidc.TrustChainService; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @RestController -@Profile("openid-federation") +@ConditionalOnProperty(prefix = "openid-federation", name = "enabled", havingValue = "true") public class FederationRegistrationController { @Value("${iam.issuer}") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationResponseBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationResponseBuilder.java index 286c82137c..9c96cd623b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationResponseBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/openid_federation/FederationResponseBuilder.java @@ -20,9 +20,8 @@ import java.util.List; import java.util.Map; -import org.mitre.jose.keystore.JWKSetKeyStore; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import com.nimbusds.jose.JOSEException; @@ -41,9 +40,10 @@ import it.infn.mw.iam.api.common.client.OAuthResponseType; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.core.jwk.JWKUtils; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; @Service -@Profile("openid-federation") +@ConditionalOnProperty(prefix = "openid-federation", name = "enabled", havingValue = "true") public class FederationResponseBuilder { @Value("${iam.issuer}") @@ -53,7 +53,7 @@ public class FederationResponseBuilder { private final RSAKey signingKey; private static final JWSAlgorithm alg = JWSAlgorithm.RS256; - public FederationResponseBuilder(JWKSetKeyStore keyStore) { + public FederationResponseBuilder(JwkSetKeyStore keyStore) { this.signingKey = keyStore.getKeys() .stream() .filter(k -> k instanceof RSAKey && k.isPrivate()) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/proxy/DefaultProxyCertificateService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/proxy/DefaultProxyCertificateService.java index fc6169925c..ed24d8bdb5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/proxy/DefaultProxyCertificateService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/proxy/DefaultProxyCertificateService.java @@ -22,15 +22,13 @@ import java.security.Principal; import java.time.Clock; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import com.google.common.collect.Lists; - import eu.emi.security.authn.x509.impl.PEMCredential; import eu.emi.security.authn.x509.impl.X500NameUtils; import eu.emi.security.authn.x509.proxy.ProxyCertificate; @@ -50,7 +48,6 @@ public class DefaultProxyCertificateService implements ProxyCertificateService { final ProxyCertificateProperties properties; final ProxyHelperService proxyHelper; - @Autowired public DefaultProxyCertificateService(Clock clock, IamAccountRepository accountRepository, ProxyCertificateProperties properties, ProxyHelperService proxyHelper) { this.clock = clock; @@ -159,7 +156,7 @@ public ProxyCertificateDTO generateProxy(Principal principal, public List listProxies(Principal principal) { IamAccount account = findAccountByPrincipal(principal); - List proxies = Lists.newArrayList(); + List proxies = new ArrayList<>(); for (IamX509Certificate c : account.getX509Certificates()) { if (c.hasProxy()) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/GroupRequestUtils.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/GroupRequestUtils.java index 36e480bed1..bf62d1b8f4 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/GroupRequestUtils.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/GroupRequestUtils.java @@ -27,8 +27,6 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import com.google.common.base.Strings; - import it.infn.mw.iam.api.requests.exception.GroupRequestValidationError; import it.infn.mw.iam.api.requests.model.GroupRequestDto; import it.infn.mw.iam.core.IamGroupRequestStatus; @@ -80,7 +78,7 @@ public void validateRejectMotivation(String motivation) { value = motivation.trim(); } - if (Strings.isNullOrEmpty(value)) { + if (value == null || value.isBlank()) { throw new GroupRequestValidationError("Reject motivation cannot be empty"); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/service/DefaultGroupRequestsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/service/DefaultGroupRequestsService.java index a4d290f14f..d69d8404cf 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/service/DefaultGroupRequestsService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/requests/service/DefaultGroupRequestsService.java @@ -40,10 +40,6 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Lists; -import com.google.common.collect.Table; - import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.common.ListResponseDTO; import it.infn.mw.iam.api.common.OffsetPageable; @@ -95,12 +91,6 @@ public class DefaultGroupRequestsService implements GroupRequestsService { @Autowired private ApplicationEventPublisher eventPublisher; - private static final Table ALLOWED_STATE_TRANSITIONS = - new ImmutableTable.Builder() - .put(PENDING, APPROVED, true) - .put(PENDING, REJECTED, true) - .build(); - private static String GROUP = "group"; private static String ACCOUNT = "account"; @@ -126,11 +116,11 @@ public GroupRequestDto createGroupRequest(GroupRequestDto groupRequest) { iamGroupRequest.setGroup(group.get()); iamGroupRequest.setNotes(groupRequest.getNotes()); iamGroupRequest.setStatus(PENDING); - + Date creationTime = new Date(timeProvider.currentTimeMillis()); iamGroupRequest.setCreationTime(creationTime); iamGroupRequest.setLastUpdateTime(creationTime); - + result = groupRequestRepository.save(iamGroupRequest); notificationFactory.createAdminHandleGroupRequestMessage(iamGroupRequest); eventPublisher.publishEvent(new GroupRequestCreatedEvent(this, result)); @@ -159,12 +149,13 @@ public GroupRequestDto approveGroupRequest(String requestId) { notificationFactory.createGroupMembershipApprovedMessage(request); eventPublisher.publishEvent(new GroupRequestApprovedEvent(this, request)); - while(!isNull(group)) { + while (!isNull(group)) { // Approve all other PENDING requests for any intermediate groups up to the root - Optional hasPendingRequest = - groupRequestRepository.findByGroupIdAndAccountIdAndStatus(group.getId(), account.getId(), PENDING); + Optional hasPendingRequest = groupRequestRepository + .findByGroupIdAndAccountIdAndStatus(group.getId(), account.getId(), PENDING); - if (hasPendingRequest.isPresent() && !hasPendingRequest.get().getId().equals(request.getId())) { + if (hasPendingRequest.isPresent() + && !hasPendingRequest.get().getId().equals(request.getId())) { IamGroupRequest pendingRequest = hasPendingRequest.get(); updateGroupRequestStatus(pendingRequest, APPROVED); notificationFactory.createGroupMembershipApprovedMessage(pendingRequest); @@ -196,10 +187,11 @@ public GroupRequestDto rejectGroupRequest(String requestId, String motivation) { while (!queue.isEmpty()) { IamGroup child = queue.poll(); - Optional hasPendingRequest = - groupRequestRepository.findByGroupIdAndAccountIdAndStatus(child.getId(), account.getId(), PENDING); + Optional hasPendingRequest = groupRequestRepository + .findByGroupIdAndAccountIdAndStatus(child.getId(), account.getId(), PENDING); - if (hasPendingRequest.isPresent() && !hasPendingRequest.get().getId().equals(request.getId())) { + if (hasPendingRequest.isPresent() + && !hasPendingRequest.get().getId().equals(request.getId())) { IamGroupRequest pendingRequest = hasPendingRequest.get(); pendingRequest.setMotivation(motivation); updateGroupRequestStatus(pendingRequest, REJECTED); @@ -240,7 +232,7 @@ public ListResponseDTO listGroupRequests(String username, Strin } } - List results = Lists.newArrayList(); + List results = new ArrayList<>(); Page pagedResults = lookupGroupRequests(usernameFilter, groupNameFilter, statusFilter, managedGroups, pageRequest); @@ -252,8 +244,8 @@ public ListResponseDTO listGroupRequests(String username, Strin } @Override - public ListResponseDTO searchGroupRequests(String username, String userFullName, String groupName, - String notes, String status, OffsetPageable pageRequest) { + public ListResponseDTO searchGroupRequests(String username, String userFullName, + String groupName, String notes, String status, OffsetPageable pageRequest) { Optional usernameFilter = Optional.ofNullable(username); Optional userFullNameFilter = Optional.ofNullable(userFullName); Optional groupNameFilter = Optional.ofNullable(groupName); @@ -270,10 +262,10 @@ public ListResponseDTO searchGroupRequests(String username, Str } } - List results = Lists.newArrayList(); + List results = new ArrayList<>(); - Page pagedResults = lookupGroupRequests(usernameFilter, userFullNameFilter, groupNameFilter, notesFilter, - statusFilter, managedGroups, pageRequest); + Page pagedResults = lookupGroupRequests(usernameFilter, userFullNameFilter, + groupNameFilter, notesFilter, statusFilter, managedGroups, pageRequest); pagedResults.getContent().forEach(request -> results.add(converter.fromEntity(request))); @@ -284,13 +276,13 @@ public ListResponseDTO searchGroupRequests(String username, Str private IamGroupRequest updateGroupRequestStatus(IamGroupRequest request, IamGroupRequestStatus status) { - if (!ALLOWED_STATE_TRANSITIONS.contains(request.getStatus(), status)) { - throw new InvalidGroupRequestStatusError( - String.format("Invalid group request transition: %s -> %s", request.getStatus(), status)); + if (PENDING.equals(request.getStatus()) && Set.of(APPROVED, REJECTED).contains(status)) { + request.setStatus(status); + request.setLastUpdateTime(new Date(timeProvider.currentTimeMillis())); + return groupRequestRepository.save(request); } - request.setStatus(status); - request.setLastUpdateTime(new Date(timeProvider.currentTimeMillis())); - return groupRequestRepository.save(request); + throw new InvalidGroupRequestStatusError( + String.format("Invalid group request transition: %s -> %s", request.getStatus(), status)); } static Specification baseSpec() { @@ -302,7 +294,8 @@ static Specification forUser(String username) { } static Specification forUserNameLike(String username) { - return (req, cq, cb) -> cb.like(cb.lower(req.get(ACCOUNT).get("username")), "%" + username.toLowerCase() + "%"); + return (req, cq, cb) -> cb.like(cb.lower(req.get(ACCOUNT).get("username")), + "%" + username.toLowerCase() + "%"); } static Specification forUserFullNameLike(String userFullName) { @@ -318,19 +311,16 @@ static Specification forUserFullNameLike(String userFullName) { Expression familyName = cb.lower(familyNamePath); Expression middleName = cb.selectCase() - .when(cb.isNotNull(middleNamePath), cb.concat(" ", cb.lower(middleNamePath))) - .otherwise("") - .as(String.class); - - Expression fullName = cb.concat( - cb.concat(cb.coalesce(cb.lower(givenNamePath), ""), middleName), - cb.concat(" ", cb.coalesce(cb.lower(familyNamePath), ""))); - - return cb.or( - cb.like(givenName, searchTerm), - cb.like(middleName, searchTerm), - cb.like(familyName, searchTerm), - cb.like(fullName, searchTerm)); + .when(cb.isNotNull(middleNamePath), cb.concat(" ", cb.lower(middleNamePath))) + .otherwise("") + .as(String.class); + + Expression fullName = + cb.concat(cb.concat(cb.coalesce(cb.lower(givenNamePath), ""), middleName), + cb.concat(" ", cb.coalesce(cb.lower(familyNamePath), ""))); + + return cb.or(cb.like(givenName, searchTerm), cb.like(middleName, searchTerm), + cb.like(familyName, searchTerm), cb.like(fullName, searchTerm)); }; } @@ -339,7 +329,8 @@ static Specification forGroupName(String groupName) { } static Specification forGroupNameLike(String groupName) { - return (req, cq, cb) -> cb.like(cb.lower(req.get(GROUP).get("name")), "%" + groupName.toLowerCase() + "%"); + return (req, cq, cb) -> cb.like(cb.lower(req.get(GROUP).get("name")), + "%" + groupName.toLowerCase() + "%"); } static Specification forNotesLike(String notes) { @@ -380,8 +371,9 @@ private Page lookupGroupRequests(Optional usernameFilte } private Page lookupGroupRequests(Optional usernameFilter, - Optional userFullnameFilter, Optional groupNameFilter, Optional notesFilter, - Optional statusFilter, Set managedGroups, OffsetPageable pageRequest) { + Optional userFullnameFilter, Optional groupNameFilter, + Optional notesFilter, Optional statusFilter, Set managedGroups, + OffsetPageable pageRequest) { Specification spec = baseSpec(); List> orSpecs = new ArrayList<>(); @@ -396,9 +388,8 @@ private Page lookupGroupRequests(Optional usernameFilte notesFilter.ifPresent(n -> orSpecs.add(forNotesLike(n))); if (!orSpecs.isEmpty()) { - Specification combinedOrSpec = orSpecs.stream() - .reduce(Specification::or) - .orElse(null); + Specification combinedOrSpec = + orSpecs.stream().reduce(Specification::or).orElse(null); if (combinedOrSpec != null) { spec = spec.and(combinedOrSpec); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimControllerSupport.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimControllerSupport.java index f85b63e17c..8dd769e45f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimControllerSupport.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimControllerSupport.java @@ -15,6 +15,9 @@ */ package it.infn.mw.iam.api.scim.controller; +import java.util.HashSet; +import java.util.Set; + import it.infn.mw.iam.api.scim.provisioning.paging.DefaultScimPageRequest; import it.infn.mw.iam.api.scim.provisioning.paging.ScimPageRequest; @@ -28,8 +31,6 @@ protected ScimPageRequest buildUserPageRequest(Integer count, Integer startIndex return buildPageRequest(count, startIndex, SCIM_USER_MAX_PAGE_SIZE); } - - protected ScimPageRequest buildGroupPageRequest(Integer count, Integer startIndex) { return buildPageRequest(count, startIndex, SCIM_GROUP_MAX_PAGE_SIZE); } @@ -67,4 +68,20 @@ protected ScimPageRequest buildPageRequest(Integer count, Integer startIndex, in .build(); } + protected Set parseAttributes(final String attributesParameter) { + + Set result = new HashSet<>(); + if (attributesParameter != null && !attributesParameter.isBlank()) { + String[] parts = attributesParameter.split("[\\.,]+"); + for (String part : parts) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) { + result.add(trimmed); + } + } + } + result.add("schemas"); + result.add("id"); + return result; + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimGroupController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimGroupController.java index e28a36b217..08b33b1135 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimGroupController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimGroupController.java @@ -17,7 +17,6 @@ import static it.infn.mw.iam.api.scim.controller.utils.ValidationHelper.handleValidationError; -import java.util.HashSet; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; @@ -41,10 +40,6 @@ import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.Sets; import it.infn.mw.iam.api.scim.model.ScimConstants; import it.infn.mw.iam.api.scim.model.ScimGroup; @@ -60,20 +55,6 @@ public class ScimGroupController extends ScimControllerSupport { public static final String INVALID_GROUP_MSG = "Invalid Scim Group"; - private Set parseAttributes(final String attributesParameter) { - - Set result = new HashSet<>(); - if (!Strings.isNullOrEmpty(attributesParameter)) { - result = Sets.newHashSet(Splitter.on(CharMatcher.anyOf(".,")) - .trimResults() - .omitEmptyStrings() - .split(attributesParameter)); - } - result.add("schemas"); - result.add("id"); - return result; - } - @Autowired ScimGroupProvisioning groupProvisioningService; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimUserController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimUserController.java index 960fea2d1f..f07addc4a2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimUserController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/controller/ScimUserController.java @@ -17,7 +17,6 @@ import static it.infn.mw.iam.api.scim.controller.utils.ValidationHelper.handleValidationError; -import java.util.HashSet; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; @@ -42,10 +41,6 @@ import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.Sets; import it.infn.mw.iam.api.scim.model.ScimConstants; import it.infn.mw.iam.api.scim.model.ScimListResponse; @@ -65,20 +60,6 @@ public class ScimUserController extends ScimControllerSupport { FilterProvider excludePasswordFilter = new SimpleFilterProvider().addFilter("passwordFilter", SimpleBeanPropertyFilter.serializeAllExcept("password")); - private Set parseAttributes(final String attributesParameter) { - - Set result = new HashSet<>(); - if (!Strings.isNullOrEmpty(attributesParameter)) { - result = Sets.newHashSet(Splitter.on(CharMatcher.anyOf(".,")) - .trimResults() - .omitEmptyStrings() - .split(attributesParameter)); - } - result.add("schemas"); - result.add("id"); - return result; - } - @PreAuthorize("#iam.hasScope('scim:read') or #iam.hasAnyDashboardRole('ROLE_ADMIN', 'ROLE_READER')") @GetMapping(produces = ScimConstants.SCIM_CONTENT_TYPE) public MappingJacksonValue listUsers(@RequestParam(required = false) final Integer count, @@ -86,7 +67,6 @@ public MappingJacksonValue listUsers(@RequestParam(required = false) final Integ @RequestParam(required = false) final String attributes, @RequestParam(required = false) final String filter) { - ScimPageRequest pr = buildUserPageRequest(count, startIndex); ScimListResponse result = userProvisioningService.list(pr, filter); @@ -94,7 +74,6 @@ public MappingJacksonValue listUsers(@RequestParam(required = false) final Integ MappingJacksonValue wrapper = new MappingJacksonValue(result); SimpleFilterProvider filterProvider = new SimpleFilterProvider(); - if (attributes != null) { Set includeAttributes = parseAttributes(attributes); filterProvider.addFilter("attributeFilter", diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/converter/UserConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/converter/UserConverter.java index 426e006950..efd41a5c62 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/converter/UserConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/converter/UserConverter.java @@ -15,8 +15,7 @@ */ package it.infn.mw.iam.api.scim.converter; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import org.springframework.stereotype.Service; @@ -76,10 +75,12 @@ public UserConverter(ScimProperties properties, ScimResourceLocationProvider rlp @Override public IamAccount entityFromDto(ScimUser scimUser) { - checkNotNull(scimUser); - checkNotNull(scimUser.getEmails(), "Missing mandatory e-mail"); - checkArgument(!scimUser.getEmails().isEmpty(), "Missing mandatory e-mail"); - checkNotNull(scimUser.getName(), "Missing mandatory user given and family name"); + requireNonNull(scimUser); + requireNonNull(scimUser.getEmails(), "Missing mandatory e-mail"); + if (scimUser.getEmails().isEmpty()) { + throw new IllegalArgumentException("Missing mandatory e-mail"); + } + requireNonNull(scimUser.getName(), "Missing mandatory user given and family name"); IamAccount account = new IamAccount(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimIndigoUser.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimIndigoUser.java index 021ababae2..15011b1457 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimIndigoUser.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimIndigoUser.java @@ -26,7 +26,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.Lists; import it.infn.mw.iam.api.scim.controller.utils.JsonDateSerializer; @@ -94,8 +93,7 @@ private ScimIndigoUser(@JsonProperty("oidcIds") List oidcIds, @JsonProperty("samlIds") List samlIds, @JsonProperty("x509Certificates") List certs, @JsonProperty("aupSignatureTime") Date aupSignatureTime, - @JsonProperty("endTime") Date endTime, - @JsonProperty("serviceAccount") Boolean serviceAccount, + @JsonProperty("endTime") Date endTime, @JsonProperty("serviceAccount") Boolean serviceAccount, @JsonProperty("affiliation") String affiliation) { this.oidcIds = oidcIds != null ? oidcIds : new LinkedList<>(); @@ -185,20 +183,20 @@ public static Builder builder() { public static class Builder { - private List sshKeys = Lists.newLinkedList(); - private List oidcIds = Lists.newLinkedList(); - private List samlIds = Lists.newLinkedList(); - private List certificates = Lists.newLinkedList(); - private List labels = Lists.newLinkedList(); + private List sshKeys = new LinkedList<>(); + private List oidcIds = new LinkedList<>(); + private List samlIds = new LinkedList<>(); + private List certificates = new LinkedList<>(); + private List labels = new LinkedList<>(); private Date aupSignatureTime; private Date endTime; private Boolean serviceAccount; private String affiliation; - private List authorities = Lists.newLinkedList(); - private List attributes = Lists.newLinkedList(); - private List managedGroups = Lists.newLinkedList(); + private List authorities = new LinkedList<>(); + private List attributes = new LinkedList<>(); + private List managedGroups = new LinkedList<>(); public Builder addSshKey(ScimSshKey sshKey) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimMeta.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimMeta.java index e819f7958f..e13f6bf0d8 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimMeta.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimMeta.java @@ -15,13 +15,14 @@ */ package it.infn.mw.iam.api.scim.model; +import static java.util.Objects.requireNonNull; + import java.util.Date; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.base.Preconditions; import it.infn.mw.iam.api.scim.controller.utils.JsonDateSerializer; @@ -123,8 +124,8 @@ public Builder resourceType(String resourceType) { public ScimMeta build() { - Preconditions.checkNotNull(resourceType, "resourceType must be non-null"); - Preconditions.checkNotNull(location, "location must be non-null"); + requireNonNull(resourceType, "resourceType must be non-null"); + requireNonNull(location, "location must be non-null"); return new ScimMeta(this); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUser.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUser.java index b18a4097a4..df685abc1c 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUser.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUser.java @@ -16,6 +16,7 @@ package it.infn.mw.iam.api.scim.model; import static it.infn.mw.iam.api.scim.model.ScimConstants.INDIGO_USER_SCHEMA; +import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Date; @@ -34,7 +35,6 @@ import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Preconditions; @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonFilter("attributeFilter") @@ -416,7 +416,7 @@ public Builder active(Boolean active) { public Builder addGroupRef(ScimGroupRef scimGroupRef) { - Preconditions.checkNotNull(scimGroupRef, "Null group ref"); + requireNonNull(scimGroupRef, "Null group ref"); groups.add(scimGroupRef); return this; @@ -442,7 +442,7 @@ public Builder buildPhoto(String value) { public Builder addPhoto(ScimPhoto scimPhoto) { - Preconditions.checkNotNull(scimPhoto, "Null photo"); + requireNonNull(scimPhoto, "Null photo"); photos.add(scimPhoto); return this; @@ -450,7 +450,7 @@ public Builder addPhoto(ScimPhoto scimPhoto) { public Builder addEmail(ScimEmail scimEmail) { - Preconditions.checkNotNull(scimEmail, "Null email"); + requireNonNull(scimEmail, "Null email"); emails.add(scimEmail); return this; @@ -464,7 +464,7 @@ public Builder addAddress(ScimAddress scimAddress) { public Builder aupSignatureTime(Date signatureTime) { - Preconditions.checkNotNull(signatureTime, "Null signature time"); + requireNonNull(signatureTime, "Null signature time"); indigoUserBuilder.aupSignatureTime(signatureTime); return this; @@ -472,7 +472,7 @@ public Builder aupSignatureTime(Date signatureTime) { public Builder endTime(Date endTime) { - Preconditions.checkNotNull(endTime, "Null membership end-time"); + requireNonNull(endTime, "Null membership end-time"); indigoUserBuilder.endTime(endTime); return this; @@ -490,7 +490,7 @@ public Builder affiliation(String affiliation) { public Builder addAuthority(String authority) { - Preconditions.checkNotNull(authority, "Null authority"); + requireNonNull(authority, "Null authority"); indigoUserBuilder.addAuthority(authority); return this; @@ -498,7 +498,7 @@ public Builder addAuthority(String authority) { public Builder addX509Certificate(ScimX509Certificate scimX509Certificate) { - Preconditions.checkNotNull(scimX509Certificate, "Null x509 certificate"); + requireNonNull(scimX509Certificate, "Null x509 certificate"); indigoUserBuilder.addCertificate(scimX509Certificate); return this; @@ -506,7 +506,7 @@ public Builder addX509Certificate(ScimX509Certificate scimX509Certificate) { public Builder addOidcId(ScimOidcId oidcId) { - Preconditions.checkNotNull(oidcId, "Null OpenID Connect ID"); + requireNonNull(oidcId, "Null OpenID Connect ID"); indigoUserBuilder.addOidcId(oidcId); return this; @@ -519,7 +519,7 @@ public Builder buildOidcId(String issuer, String subject) { public Builder addSshKey(ScimSshKey sshKey) { - Preconditions.checkNotNull(sshKey, "Null ssh key"); + requireNonNull(sshKey, "Null ssh key"); indigoUserBuilder.addSshKey(sshKey); return this; @@ -537,7 +537,7 @@ public Builder buildSshKey(String label, String key, String fingerprint, boolean public Builder addSamlId(ScimSamlId samlId) { - Preconditions.checkNotNull(samlId, "Null saml id"); + requireNonNull(samlId, "Null saml id"); indigoUserBuilder.addSamlId(samlId); return this; @@ -550,7 +550,7 @@ public Builder buildSamlId(String idpId, String userId) { public Builder addLabel(ScimLabel label) { - Preconditions.checkNotNull(label, "Null label"); + requireNonNull(label, "Null label"); indigoUserBuilder.addLabel(label); return this; @@ -558,7 +558,7 @@ public Builder addLabel(ScimLabel label) { public Builder addAttribute(ScimAttribute attribute) { - Preconditions.checkNotNull(attribute, "Null attribute"); + requireNonNull(attribute, "Null attribute"); indigoUserBuilder.addAttribute(attribute); return this; @@ -571,7 +571,7 @@ public Builder addAttribute(String name, String value) { public Builder addManagedGroup(ScimGroupRef groupRef) { - Preconditions.checkNotNull(groupRef, "Null group reference"); + requireNonNull(groupRef, "Null group reference"); indigoUserBuilder.addManagedGroup(groupRef); return this; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUserPatchRequest.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUserPatchRequest.java index 4a81e70433..f494befd7d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUserPatchRequest.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/model/ScimUserPatchRequest.java @@ -15,20 +15,20 @@ */ package it.infn.mw.iam.api.scim.model; +import static java.util.Objects.requireNonNull; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.validation.Valid; - import javax.validation.constraints.NotEmpty; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Preconditions; @JsonInclude(Include.NON_EMPTY) public class ScimUserPatchRequest { @@ -45,7 +45,7 @@ public class ScimUserPatchRequest { private ScimUserPatchRequest(@JsonProperty("schemas") Set schemas, @JsonProperty("operations") List> operations) { - Preconditions.checkNotNull(operations, "Operation list is null"); + requireNonNull(operations, "Operation list is null"); this.schemas = schemas; this.operations = operations; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/provisioning/ScimGroupProvisioning.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/provisioning/ScimGroupProvisioning.java index 869bd9bc87..642565542f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/provisioning/ScimGroupProvisioning.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/provisioning/ScimGroupProvisioning.java @@ -28,9 +28,6 @@ import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; -import com.google.common.base.Strings; -import static com.google.common.collect.Lists.newArrayList; - import it.infn.mw.iam.api.common.OffsetPageable; import it.infn.mw.iam.api.requests.service.GroupRequestsService; import it.infn.mw.iam.api.scim.converter.GroupConverter; @@ -142,7 +139,7 @@ public void delete(String id) { } private void displayNameSanityChecks(String displayName) { - if (Strings.isNullOrEmpty(displayName)) { + if (displayName == null || displayName.isBlank()) { throw new IllegalArgumentException("Group displayName cannot be empty"); } @@ -282,7 +279,7 @@ public ScimListResponse listAccountMembers(String id, OffsetPageable pr = new OffsetPageable(pageRequest.getStartIndex(), pageRequest.getCount()); Page accounts = accountService.findGroupMembers(iamGroup, pr); - List resources = newArrayList(); + List resources = new ArrayList<>(); for (IamAccount a : accounts.getContent()) { resources.add(ScimMemberRef.builder() @@ -305,7 +302,7 @@ public ScimListResponse listGroupMembers(String id, ScimPageReque OffsetPageable pr = new OffsetPageable(pageRequest.getStartIndex(), pageRequest.getCount()); Page subgroups = groupService.findSubgroups(iamGroup, pr); - List resources = newArrayList(); + List resources = new ArrayList<>(); for (IamGroup g : subgroups.getContent()) { resources.add(ScimMemberRef.builder() .value(g.getUuid()) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/UsernameUpdater.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/UsernameUpdater.java index 858246cb7c..2db0b17248 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/UsernameUpdater.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/UsernameUpdater.java @@ -19,11 +19,10 @@ import java.util.function.Consumer; import java.util.function.Predicate; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; - import it.infn.mw.iam.audit.events.account.UsernameReplacedEvent; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/builders/Adders.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/builders/Adders.java index 8e38de501d..68e61d235a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/builders/Adders.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/builders/Adders.java @@ -29,8 +29,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; -import com.google.common.base.Strings; - import it.infn.mw.iam.api.scim.exception.IllegalArgumentException; import it.infn.mw.iam.api.scim.exception.ScimResourceExistsException; import it.infn.mw.iam.api.scim.updater.AccountUpdater; @@ -117,15 +115,13 @@ private Predicate> buildSamlIdsAddChecks() { Predicate> samlIdWellFormed = c -> { c.removeIf(Objects::isNull); c.stream().forEach(id -> { - if (Strings.isNullOrEmpty(id.getIdpId())) { + if (id.getIdpId() == null || id.getIdpId().isBlank()) { throw new IllegalArgumentException("idpId cannot be null or empty!"); } - - if (Strings.isNullOrEmpty(id.getAttributeId())) { + if (id.getAttributeId() == null || id.getAttributeId().isBlank()) { throw new IllegalArgumentException("attributeId cannot be null or empty!"); } - - if (Strings.isNullOrEmpty(id.getUserId())) { + if (id.getUserId() == null || id.getUserId().isBlank()) { throw new IllegalArgumentException("userId cannot be null or empty!"); } }); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultAccountUpdaterFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultAccountUpdaterFactory.java index d276cf92ea..216b72f59a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultAccountUpdaterFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultAccountUpdaterFactory.java @@ -19,6 +19,7 @@ import static it.infn.mw.iam.api.scim.model.ScimPatchOperation.ScimPatchOperationType.remove; import static it.infn.mw.iam.api.scim.model.ScimPatchOperation.ScimPatchOperationType.replace; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Predicate; @@ -26,8 +27,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.scim.converter.OidcIdConverter; import it.infn.mw.iam.api.scim.converter.SamlIdConverter; import it.infn.mw.iam.api.scim.converter.SshKeyConverter; @@ -250,7 +249,7 @@ private void prepareReplacers(List updaters, ScimUser user, IamA public List getUpdatersForPatchOperation(IamAccount account, ScimPatchOperation op) throws ScimPatchOperationNotSupported { - final List updaters = Lists.newArrayList(); + final List updaters = new ArrayList<>(); final ScimUser user = op.getValue(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultGroupMembershipUpdaterFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultGroupMembershipUpdaterFactory.java index 3aa2ae78dc..97737fcb2a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultGroupMembershipUpdaterFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/factory/DefaultGroupMembershipUpdaterFactory.java @@ -19,12 +19,11 @@ import static it.infn.mw.iam.api.scim.model.ScimPatchOperation.ScimPatchOperationType.remove; import static it.infn.mw.iam.api.scim.model.ScimPatchOperation.ScimPatchOperationType.replace; +import java.util.ArrayList; import java.util.List; import org.springframework.data.domain.Page; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.common.OffsetPageable; import it.infn.mw.iam.api.scim.converter.ScimResourceLocationProvider; import it.infn.mw.iam.api.scim.exception.ScimValidationException; @@ -55,7 +54,7 @@ public DefaultGroupMembershipUpdaterFactory(IamAccountService accountService, public List getUpdatersForPatchOperation(IamGroup group, ScimPatchOperation> op) { - final List updaters = Lists.newArrayList(); + final List updaters = new ArrayList<>(); final List members = memberRefToAccountConverter(op.getValue()); @@ -73,7 +72,7 @@ public List getUpdatersForPatchOperation(IamGroup group, long totalUsers = accountRepo.count(); OffsetPageable pr = new OffsetPageable(0, (int) totalUsers); Page accounts = accountService.findGroupMembers(group, pr); - List oldMembers = Lists.newArrayList(); + List oldMembers = new ArrayList<>(); for (IamAccount a : accounts.getContent()) { oldMembers.add(a); @@ -107,7 +106,7 @@ private void prepareRemovers(List updaters, List mem private List memberRefToAccountConverter(List members) { - List newAccounts = Lists.newArrayList(); + List newAccounts = new ArrayList<>(); if (members == null) { return newAccounts; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/util/IdNotBoundChecker.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/util/IdNotBoundChecker.java index 1d3907ebd9..8d56f3c42e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/util/IdNotBoundChecker.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scim/updater/util/IdNotBoundChecker.java @@ -15,8 +15,7 @@ */ package it.infn.mw.iam.api.scim.updater.util; -import static com.google.common.base.Preconditions.checkNotNull; - +import java.util.Objects; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Predicate; @@ -41,7 +40,8 @@ public IdNotBoundChecker(AccountFinder finder, IamAccount account, @Override public boolean test(T id) { - checkNotNull(id); + + Objects.requireNonNull(id); Optional a = finder.find(id); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/DefaultScopePolicyConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/DefaultScopePolicyConverter.java index 304bce9344..d9b26de186 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/DefaultScopePolicyConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/DefaultScopePolicyConverter.java @@ -15,11 +15,8 @@ */ package it.infn.mw.iam.api.scope_policy; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import com.google.common.collect.Sets; - import it.infn.mw.iam.api.scim.converter.ScimResourceLocationProvider; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamGroup; @@ -36,7 +33,6 @@ public class DefaultScopePolicyConverter implements IamScopePolicyConverter { private final IamAccountRepository accountRepo; private final IamGroupRepository groupRepo; - @Autowired public DefaultScopePolicyConverter(ScimResourceLocationProvider locationProvider, IamAccountRepository accountRepo, IamGroupRepository groupRepo) { this.resourceLocationProvider = locationProvider; @@ -55,8 +51,7 @@ public ScopePolicyDTO fromModel(IamScopePolicy sp) { dto.setMatchingPolicy(sp.getMatchingPolicy().name()); if (!sp.getScopes().isEmpty()) { - dto.setScopes(Sets.newHashSet()); - dto.getScopes().addAll(sp.getScopes()); + dto.setScopes(sp.getScopes()); } if (sp.getAccount() != null) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/ScopePolicyDTO.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/ScopePolicyDTO.java index ee520f080f..c5cc6d2f82 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/ScopePolicyDTO.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scope_policy/ScopePolicyDTO.java @@ -16,14 +16,14 @@ package it.infn.mw.iam.api.scope_policy; import java.util.Date; +import java.util.HashSet; import java.util.Set; import javax.validation.Valid; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import javax.validation.constraints.NotBlank; - import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -146,7 +146,8 @@ public Set getScopes() { } public void setScopes(Set scopes) { - this.scopes = scopes; + this.scopes = new HashSet<>(); + this.scopes.addAll(scopes); } public String getMatchingPolicy() { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/scopes/ScopesController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/scopes/ScopesController.java new file mode 100644 index 0000000000..3a0b987332 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/scopes/ScopesController.java @@ -0,0 +1,168 @@ +/** + * 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.api.scopes; + +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.google.gson.Gson; + +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.core.web.view.HttpCodeView; +import it.infn.mw.iam.core.web.view.JsonEntityView; +import it.infn.mw.iam.core.web.view.JsonErrorView; +import it.infn.mw.iam.persistence.model.SystemScope; + +@Controller +@RequestMapping("/api/scopes") +@PreAuthorize("hasRole('ROLE_USER')") +public class ScopesController { + + private static final Logger logger = LoggerFactory.getLogger(ScopesController.class); + + private final SystemScopeService scopeService; + private final Gson gson = new Gson(); + + public ScopesController(SystemScopeService scopeService) { + this.scopeService = scopeService; + } + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public String getAll(ModelMap m) { + + Set allScopes = scopeService.getAll(); + m.put(JsonEntityView.ENTITY, allScopes); + return JsonEntityView.VIEWNAME; + } + + @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + public String getScope(@PathVariable Long id, ModelMap m) { + + SystemScope scope = scopeService.getById(id); + + if (scope != null) { + m.put(JsonEntityView.ENTITY, scope); + return JsonEntityView.VIEWNAME; + } + + logger.error("getScope failed; scope not found: " + id); + + m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); + m.put(JsonErrorView.ERROR_MESSAGE, + "The requested scope with id " + id + " could not be found."); + return JsonErrorView.VIEWNAME; + } + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @PutMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + public String updateScope(@PathVariable Long id, @RequestBody String json, ModelMap m) { + + SystemScope existing = scopeService.getById(id); + + SystemScope scope = gson.fromJson(json, SystemScope.class); + + if (existing != null && scope != null) { + + if (existing.getId().equals(scope.getId())) { + + scope = scopeService.save(scope); + m.put(JsonEntityView.ENTITY, scope); + return JsonEntityView.VIEWNAME; + } + + logger.error("updateScope failed; scope ids to not match: got " + existing.getId() + " and " + + scope.getId()); + + m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + m.put(JsonErrorView.ERROR_MESSAGE, "Could not update scope. Scope ids to not match: got " + + existing.getId() + " and " + scope.getId()); + return JsonErrorView.VIEWNAME; + } + + logger.error("updateScope failed; scope with id " + id + " not found."); + m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); + m.put(JsonErrorView.ERROR_MESSAGE, + "Could not update scope. The scope with id " + id + " could not be found."); + return JsonErrorView.VIEWNAME; + } + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @PostMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + public String createScope(@RequestBody String json, ModelMap m) { + + SystemScope scope = gson.fromJson(json, SystemScope.class); + + SystemScope alreadyExists = scopeService.getByValue(scope.getValue()); + if (alreadyExists != null) { + // Error, cannot save a scope with the same value as an existing one + logger.error("Error: attempting to save a scope with a value that already exists: " + + scope.getValue()); + m.put(HttpCodeView.CODE, HttpStatus.CONFLICT); + m.put(JsonErrorView.ERROR_MESSAGE, "A scope with value " + scope.getValue() + + " already exists, please choose a different value."); + return JsonErrorView.VIEWNAME; + } + + scope = scopeService.save(scope); + + if (scope != null && scope.getId() != null) { + + m.put(JsonEntityView.ENTITY, scope); + + return JsonEntityView.VIEWNAME; + } + + logger.error("createScope failed; JSON was invalid: " + json); + m.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + m.put(JsonErrorView.ERROR_MESSAGE, "Could not save new scope " + scope + + ". The scope service failed to return a saved entity."); + return JsonErrorView.VIEWNAME; + + } + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @DeleteMapping(value = "/{id}") + public String deleteScope(@PathVariable Long id, ModelMap m) { + SystemScope existing = scopeService.getById(id); + + if (existing != null) { + scopeService.remove(existing); + return HttpCodeView.VIEWNAME; + } + + logger.error("deleteScope failed; scope with id " + id + " not found."); + m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); + m.put(JsonErrorView.ERROR_MESSAGE, + "Could not delete scope. The requested scope with id " + id + " could not be found."); + return JsonErrorView.VIEWNAME; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/site/ApprovedSiteController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/site/ApprovedSiteController.java new file mode 100644 index 0000000000..c7a9cc164a --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/site/ApprovedSiteController.java @@ -0,0 +1,118 @@ +/** + * 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.api.site; + +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +import it.infn.mw.iam.api.account.AccountUtils; +import it.infn.mw.iam.core.oauth.approvedsite.ApprovedSiteService; +import it.infn.mw.iam.core.web.view.HttpCodeView; +import it.infn.mw.iam.core.web.view.JsonApprovedSiteView; +import it.infn.mw.iam.core.web.view.JsonEntityView; +import it.infn.mw.iam.core.web.view.JsonErrorView; +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.IamAccount; + +@Controller +public class ApprovedSiteController { + + private static final Logger logger = LoggerFactory.getLogger(ApprovedSiteController.class); + + private final ApprovedSiteService approvedSiteService; + private final AccountUtils accountUtils; + + public ApprovedSiteController(ApprovedSiteService approvedSiteService, + AccountUtils accountUtils) { + + this.approvedSiteService = approvedSiteService; + this.accountUtils = accountUtils; + } + + @GetMapping(value = "/api/approved", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_USER')") + public String getAllApprovedSites(ModelMap m, Authentication a) { + + IamAccount account = accountUtils.getAuthenticatedUserAccount(a).orElseThrow(); + Collection all = approvedSiteService.getByUser(account); + m.put(JsonEntityView.ENTITY, all); + return JsonApprovedSiteView.VIEWNAME; + } + + @DeleteMapping(value = "/api/approved/{id}") + @PreAuthorize("hasRole('ROLE_USER')") + public String deleteApprovedSite(@PathVariable Long id, ModelMap m, Authentication a) { + + IamAccount account = accountUtils.getAuthenticatedUserAccount(a).orElseThrow(); + + ApprovedSite approvedSite = approvedSiteService.getById(id); + if (approvedSite == null) { + logger.error("deleteApprovedSite failed; no approved site found for id: " + id); + m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); + m.put(JsonErrorView.ERROR_MESSAGE, + "Could not delete approved site. The requested approved site with id: " + id + + " could not be found."); + return JsonErrorView.VIEWNAME; + } else if (!approvedSite.getAccount().equals(account)) { + logger.error("deleteApprovedSite failed; principal " + account.getUsername() + + " does not own approved site" + id); + m.put(HttpCodeView.CODE, HttpStatus.FORBIDDEN); + m.put(JsonErrorView.ERROR_MESSAGE, + "You do not have permission to delete this approved site. The approved site decision will not be deleted."); + return JsonErrorView.VIEWNAME; + } else { + m.put(HttpCodeView.CODE, HttpStatus.OK); + approvedSiteService.remove(approvedSite); + } + return HttpCodeView.VIEWNAME; + } + + @GetMapping(value = "/api/approved/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('ROLE_USER')") + public String getApprovedSite(@PathVariable Long id, ModelMap m, Authentication a) { + + IamAccount account = accountUtils.getAuthenticatedUserAccount(a).orElseThrow(); + + ApprovedSite approvedSite = approvedSiteService.getById(id); + if (approvedSite == null) { + logger.error("getApprovedSite failed; no approved site found for id: " + id); + m.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); + m.put(JsonErrorView.ERROR_MESSAGE, + "The requested approved site with id: " + id + " could not be found."); + return JsonErrorView.VIEWNAME; + } else if (!approvedSite.getAccount().equals(account)) { + logger.error( + "getApprovedSite failed; principal " + account.getUsername() + " does not own approved site" + id); + m.put(HttpCodeView.CODE, HttpStatus.FORBIDDEN); + m.put(JsonErrorView.ERROR_MESSAGE, "You do not have permission to view this approved site."); + return JsonErrorView.VIEWNAME; + } else { + m.put(JsonEntityView.ENTITY, approvedSite); + return JsonApprovedSiteView.VIEWNAME; + } + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/TokensControllerSupport.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/TokensControllerSupport.java index 2794bb1c01..98f1f743af 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/TokensControllerSupport.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/TokensControllerSupport.java @@ -15,11 +15,6 @@ */ package it.infn.mw.iam.api.tokens; -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.Sets; - import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; @@ -77,11 +72,14 @@ private TokensPageRequest buildPageRequest(Integer count, Integer startIndex, in protected Set parseAttributes(final String attributesParameter) { Set result = new HashSet<>(); - if (!Strings.isNullOrEmpty(attributesParameter)) { - result = Sets.newHashSet(Splitter.on(CharMatcher.anyOf(".,")) - .trimResults() - .omitEmptyStrings() - .split(attributesParameter)); + if (attributesParameter != null && !attributesParameter.isBlank()) { + String[] parts = attributesParameter.split("[\\.,]+"); + for (String part : parts) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) { + result.add(trimmed); + } + } } result.add("id"); return result; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/converter/TokensConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/converter/TokensConverter.java index 4d7c8541eb..71cdbf761e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/converter/TokensConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/converter/TokensConverter.java @@ -15,12 +15,6 @@ */ package it.infn.mw.iam.api.tokens.converter; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.model.SavedUserAuthentication; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -29,15 +23,21 @@ import it.infn.mw.iam.api.tokens.model.ClientRef; import it.infn.mw.iam.api.tokens.model.RefreshToken; import it.infn.mw.iam.api.tokens.model.UserRef; +import it.infn.mw.iam.core.client.IamClientDetailsService; import it.infn.mw.iam.core.user.exception.IamAccountException; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; import it.infn.mw.iam.persistence.repository.IamAccountRepository; @Component public class TokensConverter { @Autowired - private ClientDetailsEntityService clientDetailsService; + private IamClientDetailsService clientDetailsService; @Autowired private IamAccountRepository accountRepository; @@ -49,7 +49,7 @@ public AccessToken toAccessToken(OAuth2AccessTokenEntity at) { AuthenticationHolderEntity ah = at.getAuthenticationHolder(); - ClientRef clientRef = buildClientRef(ah.getClientId()); + ClientRef clientRef = buildClientRef(ah.getClient().getClientId()); UserRef userRef = buildUserRef(ah.getUserAuth()); return AccessToken.builder() @@ -65,7 +65,7 @@ public RefreshToken toRefreshToken(OAuth2RefreshTokenEntity rt) { AuthenticationHolderEntity ah = rt.getAuthenticationHolder(); - ClientRef clientRef = buildClientRef(ah.getClientId()); + ClientRef clientRef = buildClientRef(ah.getClient().getClientId()); UserRef userRef = buildUserRef(ah.getUserAuth()); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultAccessTokenService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultAccessTokenService.java index 701b77ef8e..4066789e16 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultAccessTokenService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultAccessTokenService.java @@ -21,8 +21,6 @@ import java.util.List; import java.util.Optional; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -33,6 +31,9 @@ import it.infn.mw.iam.api.tokens.exception.TokenNotFoundException; import it.infn.mw.iam.api.tokens.model.AccessToken; import it.infn.mw.iam.api.tokens.service.paging.TokensPageRequest; +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; @Service @@ -44,6 +45,9 @@ public class DefaultAccessTokenService extends AbstractTokenService @Autowired private OAuth2TokenEntityService tokenService; + @Autowired + private TokenRevocationService tokenRevocationService; + @Autowired private IamOAuthAccessTokenRepository tokenRepository; @@ -60,7 +64,7 @@ public void revokeTokenById(Long id) { OAuth2AccessTokenEntity at = getAccessTokenById(id).orElseThrow(() -> new TokenNotFoundException(id)); - tokenService.revokeAccessToken(at); + tokenRevocationService.revokeAccessToken(at); } private Optional getAccessTokenById(Long accessTokenId) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultRefreshTokenService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultRefreshTokenService.java index a292dde8fa..11f732efb2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultRefreshTokenService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/tokens/service/DefaultRefreshTokenService.java @@ -21,8 +21,6 @@ import java.util.List; import java.util.Optional; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -33,6 +31,9 @@ import it.infn.mw.iam.api.tokens.exception.TokenNotFoundException; import it.infn.mw.iam.api.tokens.model.RefreshToken; import it.infn.mw.iam.api.tokens.service.paging.TokensPageRequest; +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; @Service @@ -44,6 +45,9 @@ public class DefaultRefreshTokenService extends AbstractTokenService new TokenNotFoundException(id)); - tokenService.revokeRefreshToken(rt); + tokenRevocationService.revokeRefreshToken(rt); } private Optional getRefreshTokenById(Long refreshTokenId) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/validators/IamGroupRequestNotesValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/validators/IamGroupRequestNotesValidator.java index ac7cc3b983..9a432431a0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/validators/IamGroupRequestNotesValidator.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/validators/IamGroupRequestNotesValidator.java @@ -21,8 +21,6 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import com.google.common.base.Strings; - @Component @Scope("prototype") public class IamGroupRequestNotesValidator @@ -35,6 +33,6 @@ public void initialize(IamGroupRequestNotes constraintAnnotation) { @Override public boolean isValid(String value, ConstraintValidatorContext context) { - return value != null && !Strings.isNullOrEmpty(value.trim()); + return value != null && !value.isBlank(); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditEventLogger.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditEventLogger.java index 0465c149ea..6868c1bafc 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditEventLogger.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditEventLogger.java @@ -19,23 +19,21 @@ import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.MarkerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import it.infn.mw.iam.audit.events.IamAuditApplicationEvent; @Component public class IamAuditEventLogger implements AuditEventLogger { - + public static final String AUDIT_MARKER_STRING = "AUDIT"; public static final Marker AUDIT_MARKER = MarkerFactory.getMarker(AUDIT_MARKER_STRING); - + public static final Logger LOG = LoggerFactory.getLogger(AUDIT_MARKER_STRING); final AuditDataSerializer serializer; - + private IamAuditApplicationEvent lastEvent; - - @Autowired + public IamAuditEventLogger(AuditDataSerializer serializer) { this.serializer = serializer; } @@ -43,12 +41,12 @@ public IamAuditEventLogger(AuditDataSerializer serializer) { @Override public void logAuditEvent(IamAuditApplicationEvent event) { lastEvent = event; - if (LOG.isInfoEnabled()){ + if (LOG.isInfoEnabled()) { final String serializedEvent = serializer.serialize(event); LOG.info(AUDIT_MARKER, serializedEvent); } } - + public IamAuditApplicationEvent getLastEvent() { IamAuditApplicationEvent e = lastEvent; lastEvent = null; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditLoggingListener.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditLoggingListener.java index 0cb13b413a..981fa4f03a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditLoggingListener.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuditLoggingListener.java @@ -15,19 +15,16 @@ */ package it.infn.mw.iam.audit; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import it.infn.mw.iam.audit.events.IamAuditApplicationEvent; @Component -public class IamAuditLoggingListener implements ApplicationListener -{ +public class IamAuditLoggingListener implements ApplicationListener { private final AuditEventLogger logger; - - @Autowired + public IamAuditLoggingListener(AuditEventLogger logger) { this.logger = logger; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationFailureAuditListener.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationFailureAuditListener.java index 0e4579fde5..6f48f64b74 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationFailureAuditListener.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationFailureAuditListener.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.audit; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.stereotype.Component; @@ -23,16 +22,15 @@ import it.infn.mw.iam.audit.events.auth.IamAuthenticationFailureEvent; @Component -public class IamAuthenticationFailureAuditListener - implements ApplicationListener{ +public class IamAuthenticationFailureAuditListener + implements ApplicationListener { private final AuditEventLogger logger; - - @Autowired + public IamAuthenticationFailureAuditListener(AuditEventLogger logger) { this.logger = logger; } - + @Override public void onApplicationEvent(AbstractAuthenticationFailureEvent event) { IamAuthenticationFailureEvent ev = new IamAuthenticationFailureEvent(event); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationSuccessAuditListener.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationSuccessAuditListener.java index 2fb65bfdcc..118758d458 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationSuccessAuditListener.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthenticationSuccessAuditListener.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.audit; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; @@ -30,7 +29,6 @@ public class IamAuthenticationSuccessAuditListener private final AuditEventLogger logger; - @Autowired public IamAuthenticationSuccessAuditListener(AuditEventLogger logger) { this.logger = logger; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthorizationAuditListener.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthorizationAuditListener.java index 5e2e49a978..22a6ba98ba 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthorizationAuditListener.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/IamAuthorizationAuditListener.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.audit; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.security.access.event.AuthorizationFailureEvent; import org.springframework.stereotype.Component; @@ -28,18 +27,16 @@ public class IamAuthorizationAuditListener private final AuditEventLogger logger; - - @Autowired public IamAuthorizationAuditListener(AuditEventLogger logger) { this.logger = logger; } - + @Override public void onApplicationEvent(AuthorizationFailureEvent event) { - + logger.logAuditEvent(new IamAuthorizationFailureEvent(event)); } - + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientEvent.java index c2d1911db0..dcf49b021a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientEvent.java @@ -15,12 +15,11 @@ */ package it.infn.mw.iam.audit.events.account.client; -import org.mitre.oauth2.model.ClientDetailsEntity; - import com.fasterxml.jackson.databind.annotation.JsonSerialize; import it.infn.mw.iam.audit.events.account.AccountEvent; import it.infn.mw.iam.audit.utils.IamClientSerializer; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; public abstract class AccountClientEvent extends AccountEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerAssigned.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerAssigned.java index ad89c3e040..de9847e3e1 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerAssigned.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerAssigned.java @@ -15,8 +15,7 @@ */ package it.infn.mw.iam.audit.events.account.client; -import org.mitre.oauth2.model.ClientDetailsEntity; - +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; public class AccountClientOwnerAssigned extends AccountClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerRemoved.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerRemoved.java index caa24bde16..5bc61a06d6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerRemoved.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/account/client/AccountClientOwnerRemoved.java @@ -15,8 +15,7 @@ */ package it.infn.mw.iam.audit.events.account.client; -import org.mitre.oauth2.model.ClientDetailsEntity; - +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; public class AccountClientOwnerRemoved extends AccountClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientCreatedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientCreatedEvent.java index e3422f23fd..87fcec1a51 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientCreatedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientCreatedEvent.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientCreatedEvent extends ClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientEvent.java index dbc1ebbacc..4972ea4d63 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientEvent.java @@ -15,13 +15,12 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; - import com.fasterxml.jackson.databind.annotation.JsonSerialize; import it.infn.mw.iam.audit.events.IamAuditApplicationEvent; import it.infn.mw.iam.audit.events.IamEventCategory; import it.infn.mw.iam.audit.utils.IamClientSerializer; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public abstract class ClientEvent extends IamAuditApplicationEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistered.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistered.java index 9af8d6ecc0..fe98e0080e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistered.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistered.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientRegistered extends ClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistrationAccessTokenRotatedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistrationAccessTokenRotatedEvent.java index 0f8b9cf4df..5ce917e6e2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistrationAccessTokenRotatedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRegistrationAccessTokenRotatedEvent.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientRegistrationAccessTokenRotatedEvent extends ClientUpdatedEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRemovedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRemovedEvent.java index bfa43ae66b..0a72fabf39 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRemovedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientRemovedEvent.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientRemovedEvent extends ClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientSecretUpdatedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientSecretUpdatedEvent.java index 6b6757a685..0c202127c3 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientSecretUpdatedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientSecretUpdatedEvent.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientSecretUpdatedEvent extends ClientUpdatedEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java index c48243d509..0431e7dd65 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientStatusChangedEvent extends ClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientUpdatedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientUpdatedEvent.java index 9770c74b43..31a1322b71 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientUpdatedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientUpdatedEvent.java @@ -15,7 +15,7 @@ */ package it.infn.mw.iam.audit.events.client; -import org.mitre.oauth2.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientUpdatedEvent extends ClientEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/AccessTokenIssuedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/AccessTokenIssuedEvent.java index 625ebc0e1f..c4ca607015 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/AccessTokenIssuedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/AccessTokenIssuedEvent.java @@ -17,12 +17,12 @@ import java.text.ParseException; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; - import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.nimbusds.jose.JWSHeader; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; + @JsonPropertyOrder({"timestamp", "@type", "category", "principal", "message", "scopes", "subject", "grantType", "header", "payload", "refreshTokenJti", "source"}) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/RefreshTokenIssuedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/RefreshTokenIssuedEvent.java index cc07b261ed..60ca0d4a35 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/RefreshTokenIssuedEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/RefreshTokenIssuedEvent.java @@ -15,8 +15,7 @@ */ package it.infn.mw.iam.audit.events.tokens; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; - +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; public class RefreshTokenIssuedEvent extends TokenEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/TokenEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/TokenEvent.java index fc7b56edcc..b568933080 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/TokenEvent.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/tokens/TokenEvent.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Set; -import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +27,7 @@ import it.infn.mw.iam.audit.events.IamAuditApplicationEvent; import it.infn.mw.iam.audit.events.IamEventCategory; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; @SuppressWarnings("deprecation") public class TokenEvent extends IamAuditApplicationEvent { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/utils/IamClientSerializer.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/utils/IamClientSerializer.java index c7dc4bb8de..5867693f70 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/utils/IamClientSerializer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/utils/IamClientSerializer.java @@ -21,13 +21,13 @@ import java.io.IOException; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + public class IamClientSerializer extends JsonSerializer { @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultAARCHintService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultAARCHintService.java index 921d04a665..26746be037 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultAARCHintService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultAARCHintService.java @@ -25,8 +25,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import com.google.common.base.Strings; - import it.infn.mw.iam.authn.error.InvalidAARCHintError; import it.infn.mw.iam.authn.saml.DefaultMetadataLookupService; import it.infn.mw.iam.authn.saml.model.IdpDescription; @@ -41,8 +39,6 @@ public class DefaultAARCHintService implements AARCHintService { private DefaultMetadataLookupService samlProviders; - - @Autowired public DefaultAARCHintService(@Value("${iam.baseUrl}") String url, OidcValidatedProviders oidcProvicers) { this.baseUrl = url; @@ -54,7 +50,7 @@ protected void hintSanityChecks(String hint) { throw new InvalidAARCHintError("null hint"); } - if (Strings.isNullOrEmpty(hint.trim())) { + if (hint.isBlank()) { throw new InvalidAARCHintError("empty hint"); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationHintService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationHintService.java index 2639c8a512..0e6d938c39 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationHintService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationHintService.java @@ -17,7 +17,6 @@ import java.util.Objects; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -28,15 +27,14 @@ @Service public class DefaultExternalAuthenticationHintService implements ExternalAuthenticationHintService { - private static final String SAML_COLON="saml:"; - + private static final String SAML_COLON = "saml:"; + private String baseUrl; - - @Autowired + public DefaultExternalAuthenticationHintService(@Value("${iam.baseUrl}") String url) { this.baseUrl = url; } - + protected void hintSanityChecks(String hint) { if (Objects.isNull(hint)) { throw new InvalidExternalAuthenticationHintError("null hint"); @@ -52,7 +50,7 @@ public String resolve(String externalAuthnHint) { hintSanityChecks(externalAuthnHint); if (externalAuthnHint.startsWith(SAML_COLON)) { if (SAML_COLON.equals(externalAuthnHint)) { - return String.format("%s/saml/login", baseUrl); + return String.format("%s/saml/login", baseUrl); } return String.format("%s/saml/login?idp=%s", baseUrl, externalAuthnHint.substring(5)); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationInfoProcessor.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationInfoProcessor.java index e925e57d3f..90e28ec102 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationInfoProcessor.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultExternalAuthenticationInfoProcessor.java @@ -18,10 +18,11 @@ import java.util.Collections; import java.util.Map; -import org.mitre.oauth2.model.SavedUserAuthentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.stereotype.Component; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; + @Component @SuppressWarnings("deprecation") public class DefaultExternalAuthenticationInfoProcessor diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultInactiveAccountAuthenticationHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultInactiveAccountAuthenticationHandler.java index 92a43a864b..d3d0406fbf 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultInactiveAccountAuthenticationHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/DefaultInactiveAccountAuthenticationHandler.java @@ -20,7 +20,6 @@ import java.util.EnumSet; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -40,7 +39,6 @@ public class DefaultInactiveAccountAuthenticationHandler public final String waitingConfirmationMsg; public final String waitingApprovalMsg; - @Autowired public DefaultInactiveAccountAuthenticationHandler( @Value("${iam.organisation.name}") String organisationName) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/EnforceAupSignatureSuccessHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/EnforceAupSignatureSuccessHandler.java index a8a5257ed3..bf717d066b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/EnforceAupSignatureSuccessHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/EnforceAupSignatureSuccessHandler.java @@ -26,13 +26,13 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.core.util.IamAuthenticationLogger; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.service.aup.AUPSignatureCheckService; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/TimestamperSuccessHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/TimestamperSuccessHandler.java index 2f1a27530e..a2f6838aa7 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/TimestamperSuccessHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/TimestamperSuccessHandler.java @@ -25,13 +25,13 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.slf4j.Logger; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import it.infn.mw.iam.core.util.IamAuthenticationLogger; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; import it.infn.mw.iam.persistence.repository.IamAccountRepository; @SuppressWarnings("deprecation") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/Disjunction.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/Disjunction.java index a8ff079525..a417b859eb 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/Disjunction.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/Disjunction.java @@ -18,15 +18,14 @@ import static it.infn.mw.iam.authn.common.ValidatorResult.error; import static it.infn.mw.iam.authn.common.ValidatorResult.failure; +import java.util.ArrayList; import java.util.List; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; public class Disjunction extends CompositeValidatorCheck { - static final Joiner JOINER = Joiner.on(',').skipNulls(); - public Disjunction(List> checks, String message) { super(checks, message); } @@ -34,10 +33,10 @@ public Disjunction(List> checks, String message) { @Override public ValidatorResult validate(T credential) { - List messages = Lists.newArrayList(); + List messages = new ArrayList<>(); boolean hadErrors = false; - + for (ValidatorCheck c : getChecks()) { ValidatorResult result = c.validate(credential); @@ -50,8 +49,14 @@ public ValidatorResult validate(T credential) { } } - final String errorMsg = JOINER.join(messages); + final String errorMsg = joinSkippingNulls(messages); return handleFailure(hadErrors ? error(errorMsg) : failure(errorMsg)); } + static String joinSkippingNulls(Iterable items) { + return StreamSupport.stream(items.spliterator(), false) + .filter(Objects::nonNull) + .map(String::valueOf) + .collect(Collectors.joining(",")); + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/DefaultValidatorConfigParser.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/DefaultValidatorConfigParser.java index ae634627dd..404c493dfb 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/DefaultValidatorConfigParser.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/DefaultValidatorConfigParser.java @@ -15,12 +15,10 @@ */ package it.infn.mw.iam.authn.common.config; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.isNullOrEmpty; import static it.infn.mw.iam.authn.saml.validator.check.SamlHasAttributeCheck.hasAttribute; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -98,8 +96,10 @@ protected ValidatorCheck attrValueMatches(ValidatorProperties p) { @Override public ValidatorCheck parseValidatorProperties(ValidatorProperties p) { try { - checkNotNull(p, "p must be non-null"); - checkArgument(!isNullOrEmpty(p.getKind()), "kind must be non-null and not empty"); + Objects.requireNonNull(p, "p must be non-null"); + if (p.getKind() == null || p.getKind().isEmpty()) { + throw new IllegalArgumentException("kind must be non-null and not empty"); + } if (!KIND.contains(p.getKind())) { throw new ValidatorConfigError("Unsupported validator kind: " + p.getKind()); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/ValidatorProperties.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/ValidatorProperties.java index 23f406a18f..a8c24317c2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/ValidatorProperties.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/common/config/ValidatorProperties.java @@ -15,29 +15,25 @@ */ package it.infn.mw.iam.authn.common.config; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.validation.constraints.NotBlank; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - public class ValidatorProperties { @NotBlank private String kind; - private Map params = Maps.newHashMap(); + private Map params = new HashMap<>(); - private List childrens = Lists.newArrayList(); + private List childrens = new ArrayList<>(); public String getKind() { return kind; @@ -68,8 +64,11 @@ public void setChildrens(List childrens) { } public String getRequiredNonEmptyParam(String paramName) { - checkArgument(nonNull(getParams()), "params required"); - checkArgument(!isNullOrEmpty(getParams().get(paramName)), format("%s param required", paramName)); + + Objects.requireNonNull(getParams(), "params required"); + if (getParams().get(paramName) == null || getParams().get(paramName).isBlank()) { + throw new IllegalArgumentException(format("%s param required", paramName)); + } return getParams().get(paramName); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/jwt/JwtBearerAssertionAuthenticationToken.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/jwt/JwtBearerAssertionAuthenticationToken.java new file mode 100644 index 0000000000..1a33912e05 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/jwt/JwtBearerAssertionAuthenticationToken.java @@ -0,0 +1,73 @@ +/** + * 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.authn.jwt; + +import java.text.ParseException; +import java.util.Collection; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import com.nimbusds.jwt.JWT; + +public class JwtBearerAssertionAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = -3138213539914074617L; + + private String subject; + private JWT jwt; + + public JwtBearerAssertionAuthenticationToken(JWT jwt) { + this(jwt, null); + } + + public JwtBearerAssertionAuthenticationToken(JWT jwt, + Collection authorities) { + super(authorities); + try { + this.subject = jwt.getJWTClaimsSet().getSubject(); + } catch (ParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + this.jwt = jwt; + setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return jwt; + } + + @Override + public Object getPrincipal() { + return subject; + } + + public JWT getJwt() { + return jwt; + } + + public void setJwt(JWT jwt) { + this.jwt = jwt; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + setJwt(null); + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/jwt/JwtBearerClientAssertionTokenEndpointFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/jwt/JwtBearerClientAssertionTokenEndpointFilter.java new file mode 100644 index 0000000000..3b7b2a7c51 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/jwt/JwtBearerClientAssertionTokenEndpointFilter.java @@ -0,0 +1,122 @@ +/** + * 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.authn.jwt; + +import java.io.IOException; +import java.text.ParseException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; + +import com.google.common.base.Strings; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +@SuppressWarnings("deprecation") +public class JwtBearerClientAssertionTokenEndpointFilter + extends AbstractAuthenticationProcessingFilter { + + private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + + public JwtBearerClientAssertionTokenEndpointFilter(RequestMatcher additionalMatcher) { + super(new ClientAssertionRequestMatcher(additionalMatcher)); + ((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setTypeName("Form"); + } + + @Override + public void afterPropertiesSet() { + + super.afterPropertiesSet(); + + setAuthenticationFailureHandler(new AuthenticationFailureHandler() { + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + authenticationEntryPoint.commence(request, response, exception); + } + }); + + setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + // no-op - just allow filter chain to continue to token endpoint + } + }); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, IOException, ServletException { + + String assertion = request.getParameter("client_assertion"); + + try { + + JWT jwt = JWTParser.parse(assertion); + Authentication authRequest = new JwtBearerAssertionAuthenticationToken(jwt); + return this.getAuthenticationManager().authenticate(authRequest); + + } catch (ParseException e) { + throw new BadCredentialsException("Invalid JWT credential: " + assertion); + } + } + + @Override + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, + FilterChain chain, Authentication authResult) throws IOException, ServletException { + super.successfulAuthentication(request, response, chain, authResult); + chain.doFilter(request, response); + } + + private static class ClientAssertionRequestMatcher implements RequestMatcher { + + private RequestMatcher additionalMatcher; + + public ClientAssertionRequestMatcher(RequestMatcher additionalMatcher) { + this.additionalMatcher = additionalMatcher; + } + + @Override + public boolean matches(HttpServletRequest request) { + + String assertionType = request.getParameter("client_assertion_type"); + String assertion = request.getParameter("client_assertion"); + + if (Strings.isNullOrEmpty(assertionType) || Strings.isNullOrEmpty(assertion)) { + return false; + } + if (!assertionType.equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")) { + return false; + } + + return additionalMatcher.matches(request); + } + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/ExtendedAuthenticationFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/ExtendedAuthenticationFilter.java index f734d88869..7db6dc5961 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/ExtendedAuthenticationFilter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/ExtendedAuthenticationFilter.java @@ -15,10 +15,10 @@ */ package it.infn.mw.iam.authn.multi_factor_authentication; -import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.lang.Nullable; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/MultiFactorVerificationFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/MultiFactorVerificationFilter.java index eb8532a598..cb415c0a8e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/MultiFactorVerificationFilter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/multi_factor_authentication/MultiFactorVerificationFilter.java @@ -33,6 +33,7 @@ import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.stereotype.Component; import it.infn.mw.iam.authn.AbstractExternalAuthenticationToken; import it.infn.mw.iam.core.ExtendedAuthenticationToken; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/AuthRequestUrlBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/AuthRequestUrlBuilder.java new file mode 100644 index 0000000000..4457ae7cf5 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/AuthRequestUrlBuilder.java @@ -0,0 +1,27 @@ +/** + * 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.authn.oidc; + +import java.util.Map; + +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; + +public interface AuthRequestUrlBuilder { + + public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options, String loginHint); + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/AuthorizationRequestFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/AuthorizationRequestFilter.java new file mode 100644 index 0000000000..06882c588d --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/AuthorizationRequestFilter.java @@ -0,0 +1,239 @@ +/** + * 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.authn.oidc; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.http.client.utils.URIBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.GenericFilterBean; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; + +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.core.oauth.IamOAuth2ParameterNames; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + +@SuppressWarnings("deprecation") +@Component +public class AuthorizationRequestFilter extends GenericFilterBean { + + private static final Logger logger = LoggerFactory.getLogger(AuthorizationRequestFilter.class); + + public final static String PROMPTED = "PROMPT_FILTER_PROMPTED"; + public final static String PROMPT_REQUESTED = "PROMPT_FILTER_REQUESTED"; + + @Autowired + private IamClientDetailsService clientService; + + @Autowired + private RedirectResolver redirectResolver; + + @Autowired(required = false) + private LoginHintExtracter loginHintExtracter = new RemoveLoginHintsWithHTTP(); + + private RequestMatcher requestMatcher = new AntPathRequestMatcher("/authorize"); + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + HttpSession session = request.getSession(); + + // skip everything that's not an authorize URL + if (!requestMatcher.matches(request)) { + chain.doFilter(req, res); + return; + } + + Map params = createRequestMap(request.getParameterMap()); + + ClientDetailsEntity client = null; + + if (params.get(IamOAuth2ParameterNames.CLIENT_ID) != null) { + client = clientService.loadClientByClientId(params.get(IamOAuth2ParameterNames.CLIENT_ID)); + } + + // save the login hint to the session + // but first check to see if the login hint makes any sense + String loginHint = loginHintExtracter.extractHint(params.get(IamOAuth2ParameterNames.LOGIN_HINT)); + if (!Strings.isNullOrEmpty(loginHint)) { + session.setAttribute(IamOAuth2ParameterNames.LOGIN_HINT, loginHint); + } else { + session.removeAttribute(IamOAuth2ParameterNames.LOGIN_HINT); + } + + if (params.get(IamOAuth2ParameterNames.PROMPT) != null) { + // we have a "prompt" parameter + String prompt = params.get(IamOAuth2ParameterNames.PROMPT); + List prompts = Splitter.on(IamOAuth2ParameterNames.PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); + + if (prompts.contains(IamOAuth2ParameterNames.PROMPT_NONE)) { + // see if the user's logged in + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + + if (auth != null) { + // user's been logged in already (by session management) + // we're OK, continue without prompting + chain.doFilter(req, res); + } else { + logger.info("Client requested no prompt"); + // user hasn't been logged in, we need to "return an error" + if (client != null && params.get(IamOAuth2ParameterNames.REDIRECT_URI) != null) { + + // if we've got a redirect URI then we'll send it + String url = redirectResolver.resolveRedirect(params.get(IamOAuth2ParameterNames.REDIRECT_URI), client); + + try { + URIBuilder uriBuilder = new URIBuilder(url); + + uriBuilder.addParameter(IamOAuth2ParameterNames.ERROR, IamOAuth2ParameterNames.LOGIN_REQUIRED); + if (!Strings.isNullOrEmpty(params.get(IamOAuth2ParameterNames.STATE))) { + uriBuilder.addParameter(IamOAuth2ParameterNames.STATE, params.get(IamOAuth2ParameterNames.STATE)); + } + + response.sendRedirect(uriBuilder.toString()); + return; + + } catch (URISyntaxException e) { + logger.error("Can't build redirect URI for prompt=none, sending error instead", e); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); + return; + } + } + + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); + return; + } + } else if (prompts.contains(IamOAuth2ParameterNames.PROMPT_LOGIN)) { + + // first see if the user's already been prompted in this session + if (session.getAttribute(PROMPTED) == null) { + // user hasn't been PROMPTED yet, we need to check + + session.setAttribute(PROMPT_REQUESTED, Boolean.TRUE); + + // see if the user's logged in + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + // user's been logged in already (by session management) + // log them out and continue + SecurityContextHolder.getContext().setAuthentication(null); + chain.doFilter(req, res); + } else { + // user hasn't been logged in yet, we can keep going since we'll get there + chain.doFilter(req, res); + } + } else { + // user has been PROMPTED, we're fine + + // but first, undo the prompt tag + session.removeAttribute(PROMPTED); + chain.doFilter(req, res); + } + } else { + // prompt parameter is a value we don't care about, not our business + chain.doFilter(req, res); + } + + } else if (params.get(IamOAuth2ParameterNames.MAX_AGE) != null + || (client != null && client.getDefaultMaxAge() != null)) { + + // default to the client's stored value, check the string parameter + Integer max = (client != null ? client.getDefaultMaxAge() : null); + String maxAge = params.get(IamOAuth2ParameterNames.MAX_AGE); + if (maxAge != null) { + max = Integer.parseInt(maxAge); + } + + if (max != null) { + + Date authTime = (Date) session.getAttribute(AuthenticationTimeStamper.AUTH_TIMESTAMP); + + Date now = new Date(); + if (authTime != null) { + long seconds = (now.getTime() - authTime.getTime()) / 1000; + if (seconds > max) { + // session is too old, log the user out and continue + SecurityContextHolder.getContext().setAuthentication(null); + } + } + } + chain.doFilter(req, res); + } else { + // no prompt parameter, not our business + chain.doFilter(req, res); + } + + + } + + /** + * @param parameterMap + * @return + */ + private Map createRequestMap(Map parameterMap) { + Map requestMap = new HashMap<>(); + for (String key : parameterMap.keySet()) { + String[] val = parameterMap.get(key); + if (val != null && val.length > 0) { + requestMap.put(key, val[0]); // add the first value only (which is what Spring seems to do) + } + } + + return requestMap; + } + + /** + * @return the requestMatcher + */ + public RequestMatcher getRequestMatcher() { + return requestMatcher; + } + + /** + * @param requestMatcher the requestMatcher to set + */ + public void setRequestMatcher(RequestMatcher requestMatcher) { + this.requestMatcher = requestMatcher; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/DefaultOidcTokenRequestor.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/DefaultOidcTokenRequestor.java index f26d6532ea..06fbea567f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/DefaultOidcTokenRequestor.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/DefaultOidcTokenRequestor.java @@ -21,10 +21,8 @@ import java.util.Optional; import org.apache.commons.lang.NotImplementedException; -import org.mitre.oauth2.model.RegisteredClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.AuthenticationServiceException; @@ -35,7 +33,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import it.infn.mw.iam.authn.oidc.OidcClientFilter.OidcProviderConfiguration; import it.infn.mw.iam.authn.oidc.model.TokenEndpointErrorResponse; public class DefaultOidcTokenRequestor implements OidcTokenRequestor { @@ -45,12 +42,9 @@ public class DefaultOidcTokenRequestor implements OidcTokenRequestor { public static final String REDIRECT_URI_SESSION_VARIABLE = "redirect_uri"; final RestTemplateFactory restTemplateFactory; - final ObjectMapper jacksonObjectMapper; - @Autowired - public DefaultOidcTokenRequestor(RestTemplateFactory restTemplateFactory, ObjectMapper mapper) { + public DefaultOidcTokenRequestor(RestTemplateFactory restTemplateFactory) { this.restTemplateFactory = restTemplateFactory; - this.jacksonObjectMapper = mapper; } private void basicAuthRequest(RegisteredClient clientConfig, HttpHeaders headers) { @@ -86,19 +80,19 @@ protected HttpEntity> prepareTokenRequest( HttpHeaders headers = new HttpHeaders(); - switch (config.clientConfig.getTokenEndpointAuthMethod()) { + switch (config.getClientConfig().getTokenEndpointAuthMethod()) { case SECRET_BASIC: - basicAuthRequest(config.clientConfig, headers); + basicAuthRequest(config.getClientConfig(), headers); break; case SECRET_JWT: - jwtAuthRequest(config.clientConfig); + jwtAuthRequest(config.getClientConfig()); break; case PRIVATE_KEY: - jwtPrivateKeyAuthRequest(config.clientConfig); + jwtPrivateKeyAuthRequest(config.getClientConfig()); break; case SECRET_POST: - formAuthRequest(config.clientConfig, tokenRequestParams); + formAuthRequest(config.getClientConfig(), tokenRequestParams); break; case NONE: break; @@ -108,14 +102,13 @@ protected HttpEntity> prepareTokenRequest( "Unsupported token endpoint authentication method"); } - return - new HttpEntity<>(tokenRequestParams, headers); + return new HttpEntity<>(tokenRequestParams, headers); } Optional parseErrorResponse(HttpClientErrorException e) { try { - TokenEndpointErrorResponse response = jacksonObjectMapper + TokenEndpointErrorResponse response = (new ObjectMapper()) .readValue(e.getResponseBodyAsByteArray(), TokenEndpointErrorResponse.class); return Optional.of(response); @@ -136,7 +129,7 @@ public String requestTokens(OidcProviderConfiguration conf, try { - return restTemplate.postForObject(conf.serverConfig.getTokenEndpointUri(), + return restTemplate.postForObject(conf.getServerConfig().getTokenEndpointUri(), prepareTokenRequest(conf, tokenRequestParams), String.class); } catch (HttpClientErrorException e) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/client/ClientUserDetailsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/LoginHintExtracter.java similarity index 68% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/client/ClientUserDetailsService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/LoginHintExtracter.java index ae3097a129..d76336510f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/client/ClientUserDetailsService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/LoginHintExtracter.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.core.client; +package it.infn.mw.iam.authn.oidc; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.springframework.security.core.userdetails.UserDetailsService; +public interface LoginHintExtracter { + + public String extractHint(String loginHint); -public interface ClientUserDetailsService extends UserDetailsService { - ClientDetailsEntityService getClientDetailsService(); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcAuthenticationProvider.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcAuthenticationProvider.java index 90c48489e0..0a2d6ab8c0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcAuthenticationProvider.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcAuthenticationProvider.java @@ -29,33 +29,38 @@ import java.util.Set; import java.util.stream.Collectors; -import org.mitre.openid.connect.client.OIDCAuthenticationProvider; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; -import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.google.common.base.Strings; import com.google.common.collect.Sets; +import com.nimbusds.jwt.JWT; import it.infn.mw.iam.authn.InactiveAccountAuthenticationHander; import it.infn.mw.iam.authn.common.config.AuthenticationValidator; import it.infn.mw.iam.authn.multi_factor_authentication.IamAuthenticationMethodReference; -import it.infn.mw.iam.authn.oidc.service.OidcAccountProvisioningService; +import it.infn.mw.iam.authn.oidc.mapper.OidcAuthoritiesMapper; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.PendingOIDCAuthenticationToken; +import it.infn.mw.iam.authn.oidc.provisioning.OidcAccountProvisioningService; +import it.infn.mw.iam.authn.oidc.userinfo.UserInfoFetcher; import it.infn.mw.iam.authn.util.Authorities; import it.infn.mw.iam.authn.util.SessionTimeoutHelper; import it.infn.mw.iam.config.oidc.IamOidcJITAccountProvisioningProperties; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAuthority; import it.infn.mw.iam.persistence.model.IamTotpMfa; +import it.infn.mw.iam.persistence.model.IamUserInfo; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; -public class OidcAuthenticationProvider extends OIDCAuthenticationProvider { +public class OidcAuthenticationProvider implements AuthenticationProvider { public static final Logger LOG = LoggerFactory.getLogger(OidcAuthenticationProvider.class); @@ -68,13 +73,16 @@ public class OidcAuthenticationProvider extends OIDCAuthenticationProvider { private final SessionTimeoutHelper sessionTimeoutHelper; private final IamOidcJITAccountProvisioningProperties jitProperties; private final OidcAccountProvisioningService oidcProvisioningService; + private final UserInfoFetcher userInfoFetcher; + private final OidcAuthoritiesMapper authoritiesMapper; public OidcAuthenticationProvider( AuthenticationValidator tokenValidatorService, SessionTimeoutHelper sessionTimeoutHelper, IamAccountRepository accountRepo, InactiveAccountAuthenticationHander inactiveAccountHandler, IamTotpMfaRepository totpMfaRepository, IamOidcJITAccountProvisioningProperties jitProperties, - OidcAccountProvisioningService oidcProvisioningService) { + OidcAccountProvisioningService oidcProvisioningService, UserInfoFetcher userInfoFetcher, + OidcAuthoritiesMapper authoritiesMapper) { this.tokenValidatorService = tokenValidatorService; this.sessionTimeoutHelper = sessionTimeoutHelper; @@ -83,30 +91,53 @@ public OidcAuthenticationProvider( this.totpMfaRepository = totpMfaRepository; this.jitProperties = jitProperties; this.oidcProvisioningService = oidcProvisioningService; + this.userInfoFetcher = userInfoFetcher; + this.authoritiesMapper = authoritiesMapper; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OIDCAuthenticationToken token = (OIDCAuthenticationToken) super.authenticate(authentication); - - if (token == null) { + if (!supports(authentication.getClass())) { return null; } - tokenValidatorService.validateAuthentication(token); + if (authentication instanceof PendingOIDCAuthenticationToken pendingToken) { + + JWT idToken = pendingToken.getIdToken(); + IamUserInfo userInfo = userInfoFetcher.loadUserInfo(pendingToken); + + if (userInfo != null) { + + if (!Strings.isNullOrEmpty(userInfo.getSub()) + && !userInfo.getSub().equals(pendingToken.getSub())) { + + throw new UsernameNotFoundException( + "user_id mismatch between id_token and user_info call: " + pendingToken.getSub() + + " / " + userInfo.getSub()); + } + } + + OIDCAuthenticationToken token = + new OIDCAuthenticationToken(pendingToken.getSub(), pendingToken.getIssuer(), userInfo, + authoritiesMapper.mapAuthorities(idToken, userInfo), pendingToken.getIdToken(), + pendingToken.getAccessTokenValue(), pendingToken.getRefreshTokenValue()); + + tokenValidatorService.validateAuthentication(token); - Optional account = accountRepo.findByOidcId(token.getIssuer(), token.getSub()); - if (account.isEmpty()) { - if (Boolean.TRUE.equals(jitProperties.getEnabled())) { - IamAccount newAccount = oidcProvisioningService.provisionAccount(token); - return registeredOidcAuthentication(newAccount, token); - } else { + Optional account = accountRepo.findByOidcId(token.getIssuer(), token.getSub()); + if (account.isEmpty()) { + if (Boolean.TRUE.equals(jitProperties.getEnabled())) { + IamAccount newAccount = oidcProvisioningService.provisionAccount(token); + return registeredOidcAuthentication(newAccount, token); + } return unregisteredOidcAuthentication(token); } + inactiveAccountHandler.handleInactiveAccount(account.get()); + return registeredOidcAuthentication(account.get(), token); } - inactiveAccountHandler.handleInactiveAccount(account.get()); - return registeredOidcAuthentication(account.get(), token); + + return null; } private Authentication registeredOidcAuthentication(IamAccount account, diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcClientFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcClientFilter.java index 09cc3d1024..4acc2db7e3 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcClientFilter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcClientFilter.java @@ -15,67 +15,104 @@ */ package it.infn.mw.iam.authn.oidc; -import static it.infn.mw.iam.authn.util.SessionUtils.getStoredSessionString; - import java.io.IOException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.text.ParseException; +import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.oauth2.model.RegisteredClient; -import org.mitre.openid.connect.client.OIDCAuthenticationFilter; -import org.mitre.openid.connect.config.ServerConfiguration; -import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.core.env.Environment; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.nimbusds.jose.Algorithm; import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.PlainJWT; import com.nimbusds.jwt.SignedJWT; -/** - * A slightly modified version of mitreid client filter that allows to provide a custom - * {@link ClientHttpRequestFactory} object. This is needed to accomodate SSL connections to - * providers that use EUGridPMA certificates. - * - */ -@SuppressWarnings("deprecation") -public class OidcClientFilter extends OIDCAuthenticationFilter { +import it.infn.mw.iam.authn.oidc.configuration.ClientConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.model.PendingOIDCAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; +import it.infn.mw.iam.authn.util.SessionUtils; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.SymmetricKeyJWTValidatorCacheService; +import it.infn.mw.iam.core.oidc.service.IssuerService; +import it.infn.mw.iam.core.oidc.service.IssuerServiceResponse; +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; - public static class OidcProviderConfiguration { +@SuppressWarnings("deprecation") +public class OidcClientFilter extends AbstractAuthenticationProcessingFilter { - public OidcProviderConfiguration(ServerConfiguration sc, RegisteredClient cc) { - this.serverConfig = sc; - this.clientConfig = cc; - } + public final static String FILTER_PROCESSES_URL = "/openid_connect_login"; - ServerConfiguration serverConfig; - RegisteredClient clientConfig; - } + protected final static String ACR_SESSION_VARIABLE = "acr_values"; + protected final static String CODE_VERIFIER_SESSION_VARIABLE = "code_verifier"; + protected final static String ISSUER_SESSION_VARIABLE = "issuer"; + protected final static String NONCE_SESSION_VARIABLE = "nonce"; + protected final static String REDIRECT_URI_SESSION_VARIABLE = "redirect_uri"; + protected final static String STATE_SESSION_VARIABLE = "state"; + protected final static String TARGET_SESSION_VARIABLE = "target"; public static final Logger LOG = LoggerFactory.getLogger(OidcClientFilter.class); - OidcTokenRequestor tokenRequestor; - - // Allow for time sync issues by having a window of X seconds. - private int timeSkewAllowance = 300; + private final ServerConfigurationService servers; + private final ClientConfigurationService clients; + private final SymmetricKeyJWTValidatorCacheService symmetricCacheService; + private final JwkSetCacheService validationServices; + private final OidcTokenRequestor tokenRequestor; + private final IssuerService issuerService; + private final AuthRequestUrlBuilder authRequestBuilder; + private final Environment env; + private final int timeSkewAllowance = 300; + + public OidcClientFilter(AuthenticationManager authenticationManager, ServerConfigurationService servers, ClientConfigurationService clients, + SymmetricKeyJWTValidatorCacheService symmetricCacheService, + JwkSetCacheService validationServices, OidcTokenRequestor tokenRequestor, + IssuerService issuerService, AuthRequestUrlBuilder authRequestBuilder, Environment env) { + + super(FILTER_PROCESSES_URL, authenticationManager); + this.servers = servers; + this.clients = clients; + this.symmetricCacheService = symmetricCacheService; + this.validationServices = validationServices; + this.issuerService = issuerService; + this.authRequestBuilder = authRequestBuilder; + this.tokenRequestor = tokenRequestor; + this.env = env; + } private void validateState(HttpServletRequest request, HttpServletResponse response) { @@ -91,10 +128,48 @@ private void validateState(HttpServletRequest request, HttpServletResponse respo } } + protected static String createNonce(HttpSession session) { + + String nonce = new BigInteger(50, new SecureRandom()).toString(16); + session.setAttribute(NONCE_SESSION_VARIABLE, nonce); + return nonce; + } + + protected static String getStoredNonce(HttpSession session) { + + return SessionUtils.getStoredSessionString(session, NONCE_SESSION_VARIABLE); + } + + protected static String createState(HttpSession session) { + + String state = new BigInteger(50, new SecureRandom()).toString(16); + session.setAttribute(STATE_SESSION_VARIABLE, state); + return state; + } + + protected static String getStoredState(HttpSession session) { + + return SessionUtils.getStoredSessionString(session, STATE_SESSION_VARIABLE); + } + + protected static String createCodeVerifier(HttpSession session) { + String challenge = new BigInteger(50, new SecureRandom()).toString(16); + session.setAttribute(CODE_VERIFIER_SESSION_VARIABLE, challenge); + return challenge; + } + + protected static String getStoredCodeVerifier(HttpSession session) { + return SessionUtils.getStoredSessionString(session, CODE_VERIFIER_SESSION_VARIABLE); + } + + public ServerConfigurationService getServerConfigurationService() { + return servers; + } protected OidcProviderConfiguration lookupProvider(HttpServletRequest request) { - String issuer = getStoredSessionString(request.getSession(), ISSUER_SESSION_VARIABLE); + String issuer = + SessionUtils.getStoredSessionString(request.getSession(), ISSUER_SESSION_VARIABLE); if (issuer == null) { throw new AuthenticationServiceException("Issuer not found in session."); } @@ -105,8 +180,7 @@ protected OidcProviderConfiguration lookupProvider(HttpServletRequest request) { throw new AuthenticationServiceException("Unknow OpenID provider :" + issuer); } - RegisteredClient clientConfig = - getClientConfigurationService().getClientConfiguration(serverConfig); + RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig.getIssuer()); if (clientConfig == null) { throw new AuthenticationServiceException( @@ -124,10 +198,7 @@ protected MultiValueMap initTokenRequestParameters(HttpServletRe form.add("grant_type", "authorization_code"); form.add("code", request.getParameter("code")); - form.setAll(getAuthRequestOptionsService().getTokenOptions(config.serverConfig, - config.clientConfig, request)); - - String redirectUri = getStoredSessionString(request.getSession(), "redirect_uri"); + String redirectUri = SessionUtils.getStoredSessionString(request.getSession(), "redirect_uri"); if (redirectUri != null) { form.add("redirect_uri", redirectUri); @@ -158,16 +229,13 @@ private JWT parseToken(String tokenValue) { } } - @Override protected void handleError(HttpServletRequest request, HttpServletResponse response) throws IOException { throw new OidcClientError("External authentication error", request.getParameter("error"), request.getParameter("error_description"), request.getParameter("error_uri")); - } - @Override protected Authentication handleAuthorizationCodeResponse(HttpServletRequest request, HttpServletResponse response) { @@ -183,7 +251,7 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ } catch (OidcClientError e) { LOG.error("Error executing token request against endpoint {}: {}", - config.serverConfig.getTokenEndpointUri(), e.getMessage(), e); + config.getServerConfig().getTokenEndpointUri(), e.getMessage(), e); throw e; } @@ -221,10 +289,9 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ PendingOIDCAuthenticationToken oidcToken = new PendingOIDCAuthenticationToken(idClaims.getSubject(), idClaims.getIssuer(), - config.serverConfig, idToken, accessTokenValue, refreshTokenValue); + config.getServerConfig(), idToken, accessTokenValue, refreshTokenValue); return getAuthenticationManager().authenticate(oidcToken); - } private JWTClaimsSet parseClaims(JWT idToken) { @@ -241,9 +308,9 @@ protected void validateSignature(JWT idToken, OidcProviderConfiguration config) Algorithm tokenAlg = idToken.getHeader().getAlgorithm(); - Algorithm clientAlg = config.clientConfig.getIdTokenSignedResponseAlg(); + Algorithm clientAlg = config.getClientConfig().getIdTokenSignedResponseAlg(); - JWTSigningAndValidationService jwtValidator = null; + JwtSigningAndValidationService jwtValidator = null; if (clientAlg != null && !clientAlg.equals(tokenAlg)) { throw new AuthenticationServiceException( @@ -270,10 +337,10 @@ protected void validateSignature(JWT idToken, OidcProviderConfiguration config) // generate one based on client secret jwtValidator = - getSymmetricCacheService().getSymmetricValidtor(config.clientConfig.getClient()); + this.symmetricCacheService.getSymmetricValidator(config.getClientConfig().getClient()); } else { // otherwise load from the server's public key - jwtValidator = getValidationServices().getValidator(config.serverConfig.getJwksUri()); + jwtValidator = validationServices.getValidator(config.getServerConfig().getJwksUri()); } if (jwtValidator != null) { @@ -297,9 +364,9 @@ protected void validateClaims(HttpSession session, JWT idToken, JWTClaimsSet idC throw new AuthenticationServiceException("Id Token Issuer is null"); - } else if (!idClaims.getIssuer().equals(config.serverConfig.getIssuer())) { + } else if (!idClaims.getIssuer().equals(config.getServerConfig().getIssuer())) { throw new AuthenticationServiceException("Issuers do not match, expected " - + config.serverConfig.getIssuer() + " got " + idClaims.getIssuer()); + + config.getServerConfig().getIssuer() + " got " + idClaims.getIssuer()); } // check expiration @@ -347,10 +414,10 @@ protected void validateClaims(HttpSession session, JWT idToken, JWTClaimsSet idC throw new AuthenticationServiceException("Id token audience is null"); - } else if (!idClaims.getAudience().contains(config.clientConfig.getClientId())) { + } else if (!idClaims.getAudience().contains(config.getClientConfig().getClientId())) { throw new AuthenticationServiceException("Audience does not match, expected " - + config.clientConfig.getClientId() + " got " + idClaims.getAudience()); + + config.getClientConfig().getClientId() + " got " + idClaims.getAudience()); } // compare the nonce to our stored claim @@ -383,21 +450,151 @@ protected void validateClaims(HttpSession session, JWT idToken, JWTClaimsSet idC } } - @Override public int getTimeSkewAllowance() { return timeSkewAllowance; } + // public void setTimeSkewAllowance(int timeSkewAllowance) { + // + // this.timeSkewAllowance = timeSkewAllowance; + // } + // + // + // public void setTokenRequestor(OidcTokenRequestor tokenRequestor) { + // this.tokenRequestor = tokenRequestor; + // } + @Override - public void setTimeSkewAllowance(int timeSkewAllowance) { + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) throws AuthenticationException, IOException, ServletException { - this.timeSkewAllowance = timeSkewAllowance; + if (!Strings.isNullOrEmpty(request.getParameter("error"))) { + + handleError(request, response); + return null; + } + if (!Strings.isNullOrEmpty(request.getParameter("code"))) { + + Authentication auth = handleAuthorizationCodeResponse(request, response); + return auth; + } + + handleAuthorizationRequest(request, response); + return null; } + protected void handleAuthorizationRequest(HttpServletRequest request, + HttpServletResponse response) throws IOException { - public void setTokenRequestor(OidcTokenRequestor tokenRequestor) { - this.tokenRequestor = tokenRequestor; + HttpSession session = request.getSession(); + + IssuerServiceResponse issResp = issuerService.getIssuer(request); + + if (issResp == null) { + logger.error("Null issuer response returned from service."); + throw new AuthenticationServiceException("No issuer found."); + } + + if (issResp.shouldRedirect()) { + response.sendRedirect(issResp.getRedirectUrl()); + } else { + String issuer = issResp.getIssuer(); + + if (!Strings.isNullOrEmpty(issResp.getTargetLinkUri())) { + // there's a target URL in the response, we should save this so we can forward to it later + session.setAttribute(TARGET_SESSION_VARIABLE, issResp.getTargetLinkUri()); + } + + if (Strings.isNullOrEmpty(issuer)) { + logger.error("No issuer found: " + issuer); + throw new AuthenticationServiceException("No issuer found: " + issuer); + } + + ServerConfiguration serverConfig = servers.getServerConfiguration(issuer); + if (serverConfig == null) { + logger.error("No server configuration found for issuer: " + issuer); + throw new AuthenticationServiceException( + "No server configuration found for issuer: " + issuer); + } + + + session.setAttribute(ISSUER_SESSION_VARIABLE, serverConfig.getIssuer()); + + RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig.getIssuer()); + if (clientConfig == null) { + logger.error("No client configuration found for issuer: " + issuer); + throw new AuthenticationServiceException( + "No client configuration found for issuer: " + issuer); + } + + String redirectUri = null; + if (clientConfig.getRegisteredRedirectUri() != null + && clientConfig.getRegisteredRedirectUri().size() == 1) { + // if there's a redirect uri configured (and only one), use that + redirectUri = Iterables.getOnlyElement(clientConfig.getRegisteredRedirectUri()); + } else { + // otherwise our redirect URI is this current URL, with no query parameters + redirectUri = request.getRequestURL().toString(); + } + session.setAttribute(REDIRECT_URI_SESSION_VARIABLE, redirectUri); + + // this value comes back in the id token and is checked there + String nonce = createNonce(session); + + // this value comes back in the auth code response + String state = createState(session); + + // Map options = authOptions.getOptions(serverConfig, clientConfig, request); + Map options = new HashMap<>(); + + // if the client requests MFA using claims request parameter, IAM transforms it into the + // acr_values one + if (request.getParameter("acr_values") != null) { + options.put("acr_values", request.getParameter("acr_values")); + } else if (request.getParameter("claims") != null) { + JsonNode claimsNode = (new ObjectMapper()).readTree(request.getParameter("claims")); + JsonNode acrNodeValues = claimsNode.path("id_token").path("acr").path("values"); + if (acrNodeValues.isArray() && acrNodeValues.size() > 0) { + String acrValues = StreamSupport.stream(acrNodeValues.spliterator(), false) + .map(JsonNode::asText) + .collect(Collectors.joining(" ")); + session.setAttribute(ACR_SESSION_VARIABLE, acrValues); + options.put("acr_values", acrValues); + } + } else { + if (Arrays.asList(env.getActiveProfiles()).contains("mfa")) { + options.put("acr_values", "https://refeds.org/profile/mfa"); + } + } + + // if we're using PKCE, handle the challenge here + if (clientConfig.getCodeChallengeMethod() != null) { + String codeVerifier = createCodeVerifier(session); + options.put("code_challenge_method", clientConfig.getCodeChallengeMethod().getName()); + if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.plain)) { + options.put("code_challenge", codeVerifier); + } else if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.S256)) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + String hash = + Base64URL.encode(digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII))) + .toString(); + options.put("code_challenge", hash); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + String authRequest = authRequestBuilder.buildAuthRequestUrl(serverConfig, clientConfig, + redirectUri, nonce, state, options, issResp.getLoginHint()); + + logger.debug("Auth Request: " + authRequest); + + response.sendRedirect(authRequest); + } } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcExternalAuthenticationToken.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcExternalAuthenticationToken.java index 5e1e707d48..6eeab32818 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcExternalAuthenticationToken.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcExternalAuthenticationToken.java @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.Map; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import com.google.common.collect.Maps; @@ -35,6 +34,7 @@ import it.infn.mw.iam.authn.ExternalAuthenticationInfoBuilder; import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo; import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo.ExternalAuthenticationType; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; import it.infn.mw.iam.persistence.model.IamAccount; public class OidcExternalAuthenticationToken diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcProviderConfiguration.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcProviderConfiguration.java new file mode 100644 index 0000000000..485c0d9283 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcProviderConfiguration.java @@ -0,0 +1,38 @@ +/** + * 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.authn.oidc; + +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; + +public class OidcProviderConfiguration { + + private final ServerConfiguration serverConfig; + private final RegisteredClient clientConfig; + + public OidcProviderConfiguration(ServerConfiguration sc, RegisteredClient cc) { + this.serverConfig = sc; + this.clientConfig = cc; + } + + public ServerConfiguration getServerConfig() { + return serverConfig; + } + + public RegisteredClient getClientConfig() { + return clientConfig; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcTokenRequestor.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcTokenRequestor.java index 7dc0c3792e..bf2ac63b66 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcTokenRequestor.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/OidcTokenRequestor.java @@ -17,7 +17,6 @@ import org.springframework.util.MultiValueMap; -import it.infn.mw.iam.authn.oidc.OidcClientFilter.OidcProviderConfiguration; @FunctionalInterface public interface OidcTokenRequestor { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/PlainAuthRequestUrlBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/PlainAuthRequestUrlBuilder.java new file mode 100644 index 0000000000..0bfc01d851 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/PlainAuthRequestUrlBuilder.java @@ -0,0 +1,59 @@ +/** + * 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.authn.oidc; + +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.client.utils.URIBuilder; +import org.springframework.security.authentication.AuthenticationServiceException; + +import com.google.common.base.Joiner; +import com.google.common.base.Strings; + +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; + +public class PlainAuthRequestUrlBuilder implements AuthRequestUrlBuilder { + + @Override + public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, + String redirectUri, String nonce, String state, Map options, + String loginHint) { + + try { + URIBuilder uriBuilder = new URIBuilder(serverConfig.getAuthorizationEndpointUri()); + uriBuilder.addParameter("response_type", "code"); + uriBuilder.addParameter("client_id", clientConfig.getClientId()); + uriBuilder.addParameter("scope", Joiner.on(" ").join(clientConfig.getScope())); + uriBuilder.addParameter("redirect_uri", redirectUri); + uriBuilder.addParameter("nonce", nonce); + uriBuilder.addParameter("state", state); + // Optional parameters: + for (Entry option : options.entrySet()) { + uriBuilder.addParameter(option.getKey(), option.getValue()); + } + // if there's a login hint, send it + if (!Strings.isNullOrEmpty(loginHint)) { + uriBuilder.addParameter("login_hint", loginHint); + } + return uriBuilder.build().toString(); + } catch (URISyntaxException e) { + throw new AuthenticationServiceException("Malformed Authorization Endpoint Uri", e); + } + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/RegisteredClient.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/RegisteredClient.java new file mode 100644 index 0000000000..7ea3cba026 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/RegisteredClient.java @@ -0,0 +1,517 @@ +/** + * 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.authn.oidc; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.GrantedAuthority; + +import com.google.gson.JsonObject; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; + +import it.infn.mw.iam.persistence.model.AppType; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; +import it.infn.mw.iam.persistence.model.SubjectType; + +public class RegisteredClient { + + private String registrationAccessToken; + private String registrationClientUri; + private Date clientSecretExpiresAt; + private Date clientIdIssuedAt; + private ClientDetailsEntity client; + private JsonObject src; + + public RegisteredClient() { + this.client = new ClientDetailsEntity(); + } + + public RegisteredClient(ClientDetailsEntity client) { + this.client = client; + } + + public RegisteredClient(ClientDetailsEntity client, String registrationAccessToken, + String registrationClientUri) { + this.client = client; + this.registrationAccessToken = registrationAccessToken; + this.registrationClientUri = registrationClientUri; + } + + public ClientDetailsEntity getClient() { + return client; + } + + public void setClient(ClientDetailsEntity client) { + this.client = client; + } + + public String getClientDescription() { + return client.getClientDescription(); + } + + public void setClientDescription(String clientDescription) { + client.setClientDescription(clientDescription); + } + + public boolean isAllowRefresh() { + return client.isAllowRefresh(); + } + + public boolean isReuseRefreshToken() { + return client.isReuseRefreshToken(); + } + + public void setReuseRefreshToken(boolean reuseRefreshToken) { + client.setReuseRefreshToken(reuseRefreshToken); + } + + public Integer getIdTokenValiditySeconds() { + return client.getIdTokenValiditySeconds(); + } + + public void setIdTokenValiditySeconds(Integer idTokenValiditySeconds) { + client.setIdTokenValiditySeconds(idTokenValiditySeconds); + } + + public boolean isDynamicallyRegistered() { + return client.isDynamicallyRegistered(); + } + + public void setDynamicallyRegistered(boolean dynamicallyRegistered) { + client.setDynamicallyRegistered(dynamicallyRegistered); + } + + public boolean isAllowIntrospection() { + return client.isAllowIntrospection(); + } + + public void setAllowIntrospection(boolean allowIntrospection) { + client.setAllowIntrospection(allowIntrospection); + } + + public boolean isSecretRequired() { + return client.isSecretRequired(); + } + + public boolean isScoped() { + return client.isScoped(); + } + + public String getClientId() { + return client.getClientId(); + } + + public void setClientId(String clientId) { + client.setClientId(clientId); + } + + public String getClientSecret() { + return client.getClientSecret(); + } + + public void setClientSecret(String clientSecret) { + client.setClientSecret(clientSecret); + } + + public Set getScope() { + return client.getScope(); + } + + public void setScope(Set scope) { + client.setScope(scope); + } + + public Set getGrantTypes() { + return client.getGrantTypes(); + } + + public void setGrantTypes(Set grantTypes) { + client.setGrantTypes(grantTypes); + } + + public Set getAuthorizedGrantTypes() { + return client.getAuthorizedGrantTypes(); + } + + public Set getAuthorities() { + return client.getAuthorities(); + } + + public void setAuthorities(Set authorities) { + client.setAuthorities(authorities); + } + + public Integer getAccessTokenValiditySeconds() { + return client.getAccessTokenValiditySeconds(); + } + + public void setAccessTokenValiditySeconds(Integer accessTokenValiditySeconds) { + client.setAccessTokenValiditySeconds(accessTokenValiditySeconds); + } + + public Integer getRefreshTokenValiditySeconds() { + return client.getRefreshTokenValiditySeconds(); + } + + public void setRefreshTokenValiditySeconds(Integer refreshTokenValiditySeconds) { + client.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds); + } + + public Set getRedirectUris() { + return client.getRedirectUris(); + } + + public void setRedirectUris(Set redirectUris) { + client.setRedirectUris(redirectUris); + } + + public Set getRegisteredRedirectUri() { + return client.getRegisteredRedirectUri(); + } + + public Set getResourceIds() { + return client.getResourceIds(); + } + + public void setResourceIds(Set resourceIds) { + client.setResourceIds(resourceIds); + } + + public Map getAdditionalInformation() { + return client.getAdditionalInformation(); + } + + public AppType getApplicationType() { + return client.getApplicationType(); + } + + public void setApplicationType(AppType applicationType) { + client.setApplicationType(applicationType); + } + + public String getClientName() { + return client.getClientName(); + } + + public void setClientName(String clientName) { + client.setClientName(clientName); + } + + public AuthMethod getTokenEndpointAuthMethod() { + return client.getTokenEndpointAuthMethod(); + } + + public void setTokenEndpointAuthMethod(AuthMethod tokenEndpointAuthMethod) { + client.setTokenEndpointAuthMethod(tokenEndpointAuthMethod); + } + + public SubjectType getSubjectType() { + return client.getSubjectType(); + } + + public void setSubjectType(SubjectType subjectType) { + client.setSubjectType(subjectType); + } + + public Set getContacts() { + return client.getContacts(); + } + + public void setContacts(Set contacts) { + client.setContacts(contacts); + } + + public String getLogoUri() { + return client.getLogoUri(); + } + + public void setLogoUri(String logoUri) { + client.setLogoUri(logoUri); + } + + public String getPolicyUri() { + return client.getPolicyUri(); + } + + public void setPolicyUri(String policyUri) { + client.setPolicyUri(policyUri); + } + + public String getClientUri() { + return client.getClientUri(); + } + + public void setClientUri(String clientUri) { + client.setClientUri(clientUri); + } + + public String getTosUri() { + return client.getTosUri(); + } + + public void setTosUri(String tosUri) { + client.setTosUri(tosUri); + } + + public String getJwksUri() { + return client.getJwksUri(); + } + + public void setJwksUri(String jwksUri) { + client.setJwksUri(jwksUri); + } + + public JWKSet getJwks() { + return client.getJwks(); + } + + public void setJwks(JWKSet jwks) { + client.setJwks(jwks); + } + + public String getSectorIdentifierUri() { + return client.getSectorIdentifierUri(); + } + + public void setSectorIdentifierUri(String sectorIdentifierUri) { + client.setSectorIdentifierUri(sectorIdentifierUri); + } + + public Integer getDefaultMaxAge() { + return client.getDefaultMaxAge(); + } + + public void setDefaultMaxAge(Integer defaultMaxAge) { + client.setDefaultMaxAge(defaultMaxAge); + } + + public Boolean getRequireAuthTime() { + return client.getRequireAuthTime(); + } + + public void setRequireAuthTime(Boolean requireAuthTime) { + client.setRequireAuthTime(requireAuthTime); + } + + public Set getResponseTypes() { + return client.getResponseTypes(); + } + + public void setResponseTypes(Set responseTypes) { + client.setResponseTypes(responseTypes); + } + + public Set getDefaultACRvalues() { + return client.getDefaultACRvalues(); + } + + public void setDefaultACRvalues(Set defaultACRvalues) { + client.setDefaultACRvalues(defaultACRvalues); + } + + public String getInitiateLoginUri() { + return client.getInitiateLoginUri(); + } + + public void setInitiateLoginUri(String initiateLoginUri) { + client.setInitiateLoginUri(initiateLoginUri); + } + + public Set getPostLogoutRedirectUris() { + return client.getPostLogoutRedirectUris(); + } + + public void setPostLogoutRedirectUris(Set postLogoutRedirectUri) { + client.setPostLogoutRedirectUris(postLogoutRedirectUri); + } + + public Set getRequestUris() { + return client.getRequestUris(); + } + + public void setRequestUris(Set requestUris) { + client.setRequestUris(requestUris); + } + + public JWSAlgorithm getRequestObjectSigningAlg() { + return client.getRequestObjectSigningAlg(); + } + + public void setRequestObjectSigningAlg(JWSAlgorithm requestObjectSigningAlg) { + client.setRequestObjectSigningAlg(requestObjectSigningAlg); + } + + public JWSAlgorithm getUserInfoSignedResponseAlg() { + return client.getUserInfoSignedResponseAlg(); + } + + public void setUserInfoSignedResponseAlg(JWSAlgorithm userInfoSignedResponseAlg) { + client.setUserInfoSignedResponseAlg(userInfoSignedResponseAlg); + } + + public JWEAlgorithm getUserInfoEncryptedResponseAlg() { + return client.getUserInfoEncryptedResponseAlg(); + } + + public void setUserInfoEncryptedResponseAlg(JWEAlgorithm userInfoEncryptedResponseAlg) { + client.setUserInfoEncryptedResponseAlg(userInfoEncryptedResponseAlg); + } + + public EncryptionMethod getUserInfoEncryptedResponseEnc() { + return client.getUserInfoEncryptedResponseEnc(); + } + + public void setUserInfoEncryptedResponseEnc(EncryptionMethod userInfoEncryptedResponseEnc) { + client.setUserInfoEncryptedResponseEnc(userInfoEncryptedResponseEnc); + } + + public JWSAlgorithm getIdTokenSignedResponseAlg() { + return client.getIdTokenSignedResponseAlg(); + } + + public void setIdTokenSignedResponseAlg(JWSAlgorithm idTokenSignedResponseAlg) { + client.setIdTokenSignedResponseAlg(idTokenSignedResponseAlg); + } + + public JWEAlgorithm getIdTokenEncryptedResponseAlg() { + return client.getIdTokenEncryptedResponseAlg(); + } + + public void setIdTokenEncryptedResponseAlg(JWEAlgorithm idTokenEncryptedResponseAlg) { + client.setIdTokenEncryptedResponseAlg(idTokenEncryptedResponseAlg); + } + + public EncryptionMethod getIdTokenEncryptedResponseEnc() { + return client.getIdTokenEncryptedResponseEnc(); + } + + public void setIdTokenEncryptedResponseEnc(EncryptionMethod idTokenEncryptedResponseEnc) { + client.setIdTokenEncryptedResponseEnc(idTokenEncryptedResponseEnc); + } + + public JWSAlgorithm getTokenEndpointAuthSigningAlg() { + return client.getTokenEndpointAuthSigningAlg(); + } + + public void setTokenEndpointAuthSigningAlg(JWSAlgorithm tokenEndpointAuthSigningAlg) { + client.setTokenEndpointAuthSigningAlg(tokenEndpointAuthSigningAlg); + } + + public Date getCreatedAt() { + return client.getCreatedAt(); + } + + public void setCreatedAt(Date createdAt) { + client.setCreatedAt(createdAt); + } + + public String getRegistrationAccessToken() { + return registrationAccessToken; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } + + public String getRegistrationClientUri() { + return registrationClientUri; + } + + public void setRegistrationClientUri(String registrationClientUri) { + this.registrationClientUri = registrationClientUri; + } + + public Date getClientSecretExpiresAt() { + return clientSecretExpiresAt; + } + + public void setClientSecretExpiresAt(Date expiresAt) { + this.clientSecretExpiresAt = expiresAt; + } + + public Date getClientIdIssuedAt() { + return clientIdIssuedAt; + } + + public void setClientIdIssuedAt(Date issuedAt) { + this.clientIdIssuedAt = issuedAt; + } + + public Set getClaimsRedirectUris() { + return client.getClaimsRedirectUris(); + } + + public void setClaimsRedirectUris(Set claimsRedirectUris) { + client.setClaimsRedirectUris(claimsRedirectUris); + } + + public JWT getSoftwareStatement() { + return client.getSoftwareStatement(); + } + + public void setSoftwareStatement(JWT softwareStatement) { + client.setSoftwareStatement(softwareStatement); + } + + public PKCEAlgorithm getCodeChallengeMethod() { + return client.getCodeChallengeMethod(); + } + + public void setCodeChallengeMethod(PKCEAlgorithm codeChallengeMethod) { + client.setCodeChallengeMethod(codeChallengeMethod); + } + + public JsonObject getSource() { + return src; + } + + public void setSource(JsonObject src) { + this.src = src; + } + + public Integer getDeviceCodeValiditySeconds() { + return client.getDeviceCodeValiditySeconds(); + } + + public void setDeviceCodeValiditySeconds(Integer deviceCodeValiditySeconds) { + client.setDeviceCodeValiditySeconds(deviceCodeValiditySeconds); + } + + public String getSoftwareId() { + return client.getSoftwareId(); + } + + public void setSoftwareId(String softwareId) { + client.setSoftwareId(softwareId); + } + + public String getSoftwareVersion() { + return client.getSoftwareVersion(); + } + + public void setSoftwareVersion(String softwareVersion) { + client.setSoftwareVersion(softwareVersion); + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/RemoveLoginHintsWithHTTP.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/RemoveLoginHintsWithHTTP.java new file mode 100644 index 0000000000..3448999fca --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/RemoveLoginHintsWithHTTP.java @@ -0,0 +1,34 @@ +/** + * 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.authn.oidc; + +import com.google.common.base.Strings; + +public class RemoveLoginHintsWithHTTP implements LoginHintExtracter { + + @Override + public String extractHint(String loginHint) { + + if (Strings.isNullOrEmpty(loginHint)) { + return null; + } + if (loginHint.startsWith("http")) { + return null; + } + return loginHint; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/ClientConfigurationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/ClientConfigurationService.java new file mode 100644 index 0000000000..8a0b38bccf --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/ClientConfigurationService.java @@ -0,0 +1,24 @@ +/** + * 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.authn.oidc.configuration; + +import it.infn.mw.iam.authn.oidc.RegisteredClient; + +public interface ClientConfigurationService { + + public RegisteredClient getClientConfiguration(String issuer); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/DynamicServerConfigurationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/DynamicServerConfigurationService.java new file mode 100644 index 0000000000..53688dcbd6 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/DynamicServerConfigurationService.java @@ -0,0 +1,188 @@ +/** + * 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.authn.oidc.configuration; + +import static it.infn.mw.iam.util.JsonUtils.getAsBoolean; +import static it.infn.mw.iam.util.JsonUtils.getAsEncryptionMethodList; +import static it.infn.mw.iam.util.JsonUtils.getAsJweAlgorithmList; +import static it.infn.mw.iam.util.JsonUtils.getAsJwsAlgorithmList; +import static it.infn.mw.iam.util.JsonUtils.getAsString; +import static it.infn.mw.iam.util.JsonUtils.getAsStringList; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.web.client.RestTemplate; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; + +public class DynamicServerConfigurationService implements ServerConfigurationService { + + private static final Logger logger = + LoggerFactory.getLogger(DynamicServerConfigurationService.class); + + private LoadingCache servers; + + private Set whitelist = new HashSet<>(); + private Set blacklist = new HashSet<>(); + + public DynamicServerConfigurationService() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public DynamicServerConfigurationService(HttpClient httpClient) { + servers = + CacheBuilder.newBuilder().build(new OpenIDConnectServiceConfigurationFetcher(httpClient)); + } + + public Set getWhitelist() { + return whitelist; + } + + public void setWhitelist(Set whitelist) { + this.whitelist = whitelist; + } + + public Set getBlacklist() { + return blacklist; + } + + public void setBlacklist(Set blacklist) { + this.blacklist = blacklist; + } + + @Override + public ServerConfiguration getServerConfiguration(String issuer) { + try { + + if (!whitelist.isEmpty() && !whitelist.contains(issuer)) { + throw new AuthenticationServiceException( + "Whitelist was nonempty, issuer was not in whitelist: " + issuer); + } + + if (blacklist.contains(issuer)) { + throw new AuthenticationServiceException("Issuer was in blacklist: " + issuer); + } + + return servers.get(issuer); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Couldn't load configuration for " + issuer + ": " + e); + return null; + } + + } + + private class OpenIDConnectServiceConfigurationFetcher + extends CacheLoader { + private HttpComponentsClientHttpRequestFactory httpFactory; + + OpenIDConnectServiceConfigurationFetcher(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + } + + @Override + public ServerConfiguration load(String issuer) throws Exception { + + RestTemplate restTemplate = new RestTemplate(httpFactory); + ServerConfiguration conf = new ServerConfiguration(); + String url = issuer + "/.well-known/openid-configuration"; + String jsonString = restTemplate.getForObject(url, String.class); + + JsonElement parsed = JsonParser.parseString(jsonString); + if (parsed.isJsonObject()) { + + JsonObject o = parsed.getAsJsonObject(); + + if (!o.has("issuer")) { + throw new IllegalStateException("Returned object did not have an 'issuer' field"); + } + + if (!issuer.equals(o.get("issuer").getAsString())) { + logger.info("Issuer used for discover was " + issuer + " but final issuer is " + + o.get("issuer").getAsString()); + } + + conf.setIssuer(o.get("issuer").getAsString()); + + conf.setAuthorizationEndpointUri(getAsString(o, "authorization_endpoint")); + conf.setTokenEndpointUri(getAsString(o, "token_endpoint")); + conf.setJwksUri(getAsString(o, "jwks_uri")); + conf.setUserInfoUri(getAsString(o, "userinfo_endpoint")); + conf.setRegistrationEndpointUri(getAsString(o, "registration_endpoint")); + conf.setIntrospectionEndpointUri(getAsString(o, "introspection_endpoint")); + conf.setAcrValuesSupported(getAsStringList(o, "acr_values_supported")); + conf.setCheckSessionIframe(getAsString(o, "check_session_iframe")); + conf.setClaimsLocalesSupported(getAsStringList(o, "claims_locales_supported")); + conf.setClaimsParameterSupported(getAsBoolean(o, "claims_parameter_supported")); + conf.setClaimsSupported(getAsStringList(o, "claims_supported")); + conf.setDisplayValuesSupported(getAsStringList(o, "display_values_supported")); + conf.setEndSessionEndpoint(getAsString(o, "end_session_endpoint")); + conf.setGrantTypesSupported(getAsStringList(o, "grant_types_supported")); + conf.setIdTokenSigningAlgValuesSupported( + getAsJwsAlgorithmList(o, "id_token_signing_alg_values_supported")); + conf.setIdTokenEncryptionAlgValuesSupported( + getAsJweAlgorithmList(o, "id_token_encryption_alg_values_supported")); + conf.setIdTokenEncryptionEncValuesSupported( + getAsEncryptionMethodList(o, "id_token_encryption_enc_values_supported")); + conf.setOpPolicyUri(getAsString(o, "op_policy_uri")); + conf.setOpTosUri(getAsString(o, "op_tos_uri")); + conf.setRequestObjectEncryptionAlgValuesSupported( + getAsJweAlgorithmList(o, "request_object_encryption_alg_values_supported")); + conf.setRequestObjectEncryptionEncValuesSupported( + getAsEncryptionMethodList(o, "request_object_encryption_enc_values_supported")); + conf.setRequestObjectSigningAlgValuesSupported( + getAsJwsAlgorithmList(o, "request_object_signing_alg_values_supported")); + conf.setRequestParameterSupported(getAsBoolean(o, "request_parameter_supported")); + conf.setRequestUriParameterSupported(getAsBoolean(o, "request_uri_parameter_supported")); + conf.setResponseTypesSupported(getAsStringList(o, "response_types_supported")); + conf.setScopesSupported(getAsStringList(o, "scopes_supported")); + conf.setSubjectTypesSupported(getAsStringList(o, "subject_types_supported")); + conf.setServiceDocumentation(getAsString(o, "service_documentation")); + conf + .setTokenEndpointAuthMethodsSupported(getAsStringList(o, "token_endpoint_auth_methods")); + conf.setTokenEndpointAuthSigningAlgValuesSupported( + getAsJwsAlgorithmList(o, "token_endpoint_auth_signing_alg_values_supported")); + conf.setUiLocalesSupported(getAsStringList(o, "ui_locales_supported")); + conf.setUserinfoEncryptionAlgValuesSupported( + getAsJweAlgorithmList(o, "userinfo_encryption_alg_values_supported")); + conf.setUserinfoEncryptionEncValuesSupported( + getAsEncryptionMethodList(o, "userinfo_encryption_enc_values_supported")); + conf.setUserinfoSigningAlgValuesSupported( + getAsJwsAlgorithmList(o, "userinfo_signing_alg_values_supported")); + + return conf; + } + throw new IllegalStateException("Couldn't parse server discovery results for " + url); + } + + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/service/NullClientConfigurationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/NullClientConfigurationService.java similarity index 71% rename from iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/service/NullClientConfigurationService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/NullClientConfigurationService.java index 3edef2da88..b4d88a2e46 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/service/NullClientConfigurationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/NullClientConfigurationService.java @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.authn.oidc.service; +package it.infn.mw.iam.authn.oidc.configuration; -import org.mitre.oauth2.model.RegisteredClient; -import org.mitre.openid.connect.client.service.ClientConfigurationService; -import org.mitre.openid.connect.config.ServerConfiguration; +import it.infn.mw.iam.authn.oidc.RegisteredClient; public class NullClientConfigurationService implements ClientConfigurationService { @Override - public RegisteredClient getClientConfiguration(ServerConfiguration issuer) { + public RegisteredClient getClientConfiguration(String issuer) { return null; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/ServerConfigurationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/ServerConfigurationService.java new file mode 100644 index 0000000000..a33015ff04 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/ServerConfigurationService.java @@ -0,0 +1,24 @@ +/** + * 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.authn.oidc.configuration; + +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; + +public interface ServerConfigurationService { + + public ServerConfiguration getServerConfiguration(String issuer); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/StaticClientConfigurationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/StaticClientConfigurationService.java new file mode 100644 index 0000000000..e321257a83 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/configuration/StaticClientConfigurationService.java @@ -0,0 +1,46 @@ +/** + * 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.authn.oidc.configuration; + +import java.util.HashMap; +import java.util.Map; + +import it.infn.mw.iam.authn.oidc.RegisteredClient; + +public class StaticClientConfigurationService implements ClientConfigurationService { + + private final Map clients; + + public Map getClients() { + return clients; + } + + public StaticClientConfigurationService(Map clients) { + + if (clients == null || clients.isEmpty()) { + throw new IllegalArgumentException("Clients map cannot be null or empty"); + } + this.clients = new HashMap<>(); + this.clients.putAll(clients); + } + + @Override + public RegisteredClient getClientConfiguration(String issuer) { + + return clients.get(issuer); + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/GrantedAuthority.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/GrantedAuthority.java new file mode 100644 index 0000000000..9099e3df10 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/GrantedAuthority.java @@ -0,0 +1,24 @@ +/** + * 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.authn.oidc.mapper; + +import java.io.Serializable; + +public interface GrantedAuthority extends Serializable { + + String getAuthority(); + +} \ No newline at end of file diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/NamedAdminAuthoritiesMapper.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/NamedAdminAuthoritiesMapper.java new file mode 100644 index 0000000000..ac293d3874 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/NamedAdminAuthoritiesMapper.java @@ -0,0 +1,76 @@ +/** + * 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.authn.oidc.mapper; + +import java.text.ParseException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; + +import it.infn.mw.iam.persistence.model.IamUserInfo; + +@Component +public class NamedAdminAuthoritiesMapper implements OidcAuthoritiesMapper { + + private static Logger logger = LoggerFactory.getLogger(NamedAdminAuthoritiesMapper.class); + + private static final SimpleGrantedAuthority ROLE_ADMIN = new SimpleGrantedAuthority("ROLE_ADMIN"); + private static final SimpleGrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER"); + + private Set admins = new HashSet<>(); + + @Override + public Collection mapAuthorities(JWT idToken, IamUserInfo userInfo) { + + Set out = new HashSet<>(); + try { + JWTClaimsSet claims = idToken.getJWTClaimsSet(); + + SubjectIssuerGrantedAuthority authority = + new SubjectIssuerGrantedAuthority(claims.getSubject(), claims.getIssuer()); + out.add(authority); + + if (admins.contains(authority)) { + out.add(ROLE_ADMIN); + } + + // everybody's a user by default + out.add(ROLE_USER); + + } catch (ParseException e) { + logger.error("Unable to parse ID Token inside of authorities mapper (huh?)"); + } + return out; + } + + public Set getAdmins() { + return admins; + } + + public void setAdmins(Set admins) { + this.admins = admins; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/OidcAuthoritiesMapper.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/OidcAuthoritiesMapper.java new file mode 100644 index 0000000000..2f4ba539ce --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/OidcAuthoritiesMapper.java @@ -0,0 +1,29 @@ +/** + * 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.authn.oidc.mapper; + +import java.util.Collection; + +import org.springframework.security.core.GrantedAuthority; + +import com.nimbusds.jwt.JWT; + +import it.infn.mw.iam.persistence.model.IamUserInfo; + +public interface OidcAuthoritiesMapper { + + Collection mapAuthorities(JWT idToken, IamUserInfo userInfo); +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/SubjectIssuerGrantedAuthority.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/SubjectIssuerGrantedAuthority.java new file mode 100644 index 0000000000..cc8736a522 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/mapper/SubjectIssuerGrantedAuthority.java @@ -0,0 +1,93 @@ +/** + * 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.authn.oidc.mapper; + +import org.springframework.security.core.GrantedAuthority; + +import com.google.common.base.Strings; + +public class SubjectIssuerGrantedAuthority implements GrantedAuthority { + + private static final long serialVersionUID = 5584978219226664794L; + + private final String subject; + private final String issuer; + + public SubjectIssuerGrantedAuthority(String subject, String issuer) { + if (Strings.isNullOrEmpty(subject) || Strings.isNullOrEmpty(issuer)) { + throw new IllegalArgumentException("Neither subject nor issuer may be null or empty"); + } + this.subject = subject; + this.issuer = issuer; + } + + @Override + public String getAuthority() { + return "OIDC_" + subject + "_" + issuer; + } + + public String getSubject() { + return subject; + } + + public String getIssuer() { + return issuer; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((issuer == null) ? 0 : issuer.hashCode()); + result = prime * result + ((subject == null) ? 0 : subject.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof SubjectIssuerGrantedAuthority)) { + return false; + } + SubjectIssuerGrantedAuthority other = (SubjectIssuerGrantedAuthority) obj; + if (issuer == null) { + if (other.issuer != null) { + return false; + } + } else if (!issuer.equals(other.issuer)) { + return false; + } + if (subject == null) { + if (other.subject != null) { + return false; + } + } else if (!subject.equals(other.subject)) { + return false; + } + return true; + } + + @Override + public String toString() { + return getAuthority(); + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/OIDCAuthenticationToken.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/OIDCAuthenticationToken.java new file mode 100644 index 0000000000..e8f8606d72 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/OIDCAuthenticationToken.java @@ -0,0 +1,114 @@ +/** + * 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.authn.oidc.model; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.ParseException; +import java.util.Collection; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import com.google.common.collect.ImmutableMap; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +import it.infn.mw.iam.persistence.model.IamUserInfo; + +public class OIDCAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 22100073066377804L; + + private final ImmutableMap principal; + private final String accessTokenValue; + private final String refreshTokenValue; + private transient JWT idToken; + private final String issuer; + private final String sub; + private final IamUserInfo userInfo; + + public OIDCAuthenticationToken(String subject, String issuer, IamUserInfo userInfo, + Collection authorities, JWT idToken, String accessTokenValue, + String refreshTokenValue) { + + super(authorities); + + this.principal = ImmutableMap.of("sub", subject, "iss", issuer); + this.userInfo = userInfo; + this.sub = subject; + this.issuer = issuer; + this.idToken = idToken; + this.accessTokenValue = accessTokenValue; + this.refreshTokenValue = refreshTokenValue; + + setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return accessTokenValue; + } + + @Override + public Object getPrincipal() { + return principal; + } + + public String getSub() { + return sub; + } + + public JWT getIdToken() { + return idToken; + } + + public String getAccessTokenValue() { + return accessTokenValue; + } + + public String getRefreshTokenValue() { + return refreshTokenValue; + } + + public String getIssuer() { + return issuer; + } + + public IamUserInfo getUserInfo() { + return userInfo; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + if (idToken == null) { + out.writeObject(null); + } else { + out.writeObject(idToken.serialize()); + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException, ParseException { + in.defaultReadObject(); + Object o = in.readObject(); + if (o != null) { + idToken = JWTParser.parse((String) o); + } + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/PendingOIDCAuthenticationToken.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/PendingOIDCAuthenticationToken.java new file mode 100644 index 0000000000..9802f18d22 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/PendingOIDCAuthenticationToken.java @@ -0,0 +1,112 @@ +/** + * 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.authn.oidc.model; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.ParseException; +import java.util.ArrayList; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import com.google.common.collect.ImmutableMap; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +public class PendingOIDCAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 22100073066377804L; + + private final ImmutableMap principal; + private final String accessTokenValue; + private final String refreshTokenValue; + private transient JWT idToken; + private final String issuer; + private final String sub; + private final transient ServerConfiguration serverConfiguration; + + public PendingOIDCAuthenticationToken(String subject, String issuer, + ServerConfiguration serverConfiguration, JWT idToken, String accessTokenValue, + String refreshTokenValue) { + + super(new ArrayList(0)); + + this.principal = ImmutableMap.of("sub", subject, "iss", issuer); + this.sub = subject; + this.issuer = issuer; + this.idToken = idToken; + this.accessTokenValue = accessTokenValue; + this.refreshTokenValue = refreshTokenValue; + this.serverConfiguration = serverConfiguration; + + setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return accessTokenValue; + } + + @Override + public Object getPrincipal() { + return principal; + } + + public String getSub() { + return sub; + } + + public JWT getIdToken() { + return idToken; + } + + public String getAccessTokenValue() { + return accessTokenValue; + } + + public String getRefreshTokenValue() { + return refreshTokenValue; + } + + public ServerConfiguration getServerConfiguration() { + return serverConfiguration; + } + + public String getIssuer() { + return issuer; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + if (idToken == null) { + out.writeObject(null); + } else { + out.writeObject(idToken.serialize()); + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException, ParseException { + in.defaultReadObject(); + Object o = in.readObject(); + if (o != null) { + idToken = JWTParser.parse((String) o); + } + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/ServerConfiguration.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/ServerConfiguration.java new file mode 100644 index 0000000000..103997f231 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/model/ServerConfiguration.java @@ -0,0 +1,423 @@ +/** + * 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.authn.oidc.model; + +import java.util.List; + +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; + +public class ServerConfiguration { + + private String authorizationEndpointUri; + + private String tokenEndpointUri; + + private String registrationEndpointUri; + + private String issuer; + + private String jwksUri; + + private String userInfoUri; + + private String introspectionEndpointUri; + + private String revocationEndpointUri; + + private String checkSessionIframe; + + private String endSessionEndpoint; + + private List scopesSupported; + + private List responseTypesSupported; + + private List grantTypesSupported; + + private List acrValuesSupported; + + private List subjectTypesSupported; + + private List userinfoSigningAlgValuesSupported; + + private List userinfoEncryptionAlgValuesSupported; + + private List userinfoEncryptionEncValuesSupported; + + private List idTokenSigningAlgValuesSupported; + + private List idTokenEncryptionAlgValuesSupported; + + private List idTokenEncryptionEncValuesSupported; + + private List requestObjectSigningAlgValuesSupported; + + private List requestObjectEncryptionAlgValuesSupported; + + private List requestObjectEncryptionEncValuesSupported; + + private List tokenEndpointAuthMethodsSupported; + + private List tokenEndpointAuthSigningAlgValuesSupported; + + private List displayValuesSupported; + + private List claimTypesSupported; + + private List claimsSupported; + + private String serviceDocumentation; + + private List claimsLocalesSupported; + + private List uiLocalesSupported; + + private Boolean claimsParameterSupported; + + private Boolean requestParameterSupported; + + private Boolean requestUriParameterSupported; + + private Boolean requireRequestUriRegistration; + + private String opPolicyUri; + + private String opTosUri; + + private UserInfoTokenMethod userInfoTokenMethod; + + public enum UserInfoTokenMethod { + HEADER, + FORM, + QUERY; + } + + public String getAuthorizationEndpointUri() { + return authorizationEndpointUri; + } + + public void setAuthorizationEndpointUri(String authorizationEndpointUri) { + this.authorizationEndpointUri = authorizationEndpointUri; + } + + public String getTokenEndpointUri() { + return tokenEndpointUri; + } + + public void setTokenEndpointUri(String tokenEndpointUri) { + this.tokenEndpointUri = tokenEndpointUri; + } + + public String getRegistrationEndpointUri() { + return registrationEndpointUri; + } + + public void setRegistrationEndpointUri(String registrationEndpointUri) { + this.registrationEndpointUri = registrationEndpointUri; + } + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getJwksUri() { + return jwksUri; + } + + public void setJwksUri(String jwksUri) { + this.jwksUri = jwksUri; + } + + public String getUserInfoUri() { + return userInfoUri; + } + + public void setUserInfoUri(String userInfoUri) { + this.userInfoUri = userInfoUri; + } + + public String getIntrospectionEndpointUri() { + return introspectionEndpointUri; + } + + public void setIntrospectionEndpointUri(String introspectionEndpointUri) { + this.introspectionEndpointUri = introspectionEndpointUri; + } + + public String getCheckSessionIframe() { + return checkSessionIframe; + } + + public void setCheckSessionIframe(String checkSessionIframe) { + this.checkSessionIframe = checkSessionIframe; + } + + public String getEndSessionEndpoint() { + return endSessionEndpoint; + } + + public void setEndSessionEndpoint(String endSessionEndpoint) { + this.endSessionEndpoint = endSessionEndpoint; + } + + public List getScopesSupported() { + return scopesSupported; + } + + public void setScopesSupported(List scopesSupported) { + this.scopesSupported = scopesSupported; + } + + public List getResponseTypesSupported() { + return responseTypesSupported; + } + + public void setResponseTypesSupported(List responseTypesSupported) { + this.responseTypesSupported = responseTypesSupported; + } + + public List getGrantTypesSupported() { + return grantTypesSupported; + } + + public void setGrantTypesSupported(List grantTypesSupported) { + this.grantTypesSupported = grantTypesSupported; + } + + public List getAcrValuesSupported() { + return acrValuesSupported; + } + + public void setAcrValuesSupported(List acrValuesSupported) { + this.acrValuesSupported = acrValuesSupported; + } + + public List getSubjectTypesSupported() { + return subjectTypesSupported; + } + + public void setSubjectTypesSupported(List subjectTypesSupported) { + this.subjectTypesSupported = subjectTypesSupported; + } + + public List getUserinfoSigningAlgValuesSupported() { + return userinfoSigningAlgValuesSupported; + } + + public void setUserinfoSigningAlgValuesSupported(List userinfoSigningAlgValuesSupported) { + this.userinfoSigningAlgValuesSupported = userinfoSigningAlgValuesSupported; + } + + public List getUserinfoEncryptionAlgValuesSupported() { + return userinfoEncryptionAlgValuesSupported; + } + + public void setUserinfoEncryptionAlgValuesSupported(List userinfoEncryptionAlgValuesSupported) { + this.userinfoEncryptionAlgValuesSupported = userinfoEncryptionAlgValuesSupported; + } + + public List getUserinfoEncryptionEncValuesSupported() { + return userinfoEncryptionEncValuesSupported; + } + + public void setUserinfoEncryptionEncValuesSupported(List userinfoEncryptionEncValuesSupported) { + this.userinfoEncryptionEncValuesSupported = userinfoEncryptionEncValuesSupported; + } + + public List getIdTokenSigningAlgValuesSupported() { + return idTokenSigningAlgValuesSupported; + } + + public void setIdTokenSigningAlgValuesSupported(List idTokenSigningAlgValuesSupported) { + this.idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported; + } + + public List getIdTokenEncryptionAlgValuesSupported() { + return idTokenEncryptionAlgValuesSupported; + } + + public void setIdTokenEncryptionAlgValuesSupported(List idTokenEncryptionAlgValuesSupported) { + this.idTokenEncryptionAlgValuesSupported = idTokenEncryptionAlgValuesSupported; + } + + public List getIdTokenEncryptionEncValuesSupported() { + return idTokenEncryptionEncValuesSupported; + } + + public void setIdTokenEncryptionEncValuesSupported(List idTokenEncryptionEncValuesSupported) { + this.idTokenEncryptionEncValuesSupported = idTokenEncryptionEncValuesSupported; + } + + public List getRequestObjectSigningAlgValuesSupported() { + return requestObjectSigningAlgValuesSupported; + } + + public void setRequestObjectSigningAlgValuesSupported(List requestObjectSigningAlgValuesSupported) { + this.requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported; + } + + public List getRequestObjectEncryptionAlgValuesSupported() { + return requestObjectEncryptionAlgValuesSupported; + } + + public void setRequestObjectEncryptionAlgValuesSupported(List requestObjectEncryptionAlgValuesSupported) { + this.requestObjectEncryptionAlgValuesSupported = requestObjectEncryptionAlgValuesSupported; + } + + public List getRequestObjectEncryptionEncValuesSupported() { + return requestObjectEncryptionEncValuesSupported; + } + + public void setRequestObjectEncryptionEncValuesSupported(List requestObjectEncryptionEncValuesSupported) { + this.requestObjectEncryptionEncValuesSupported = requestObjectEncryptionEncValuesSupported; + } + + public List getTokenEndpointAuthMethodsSupported() { + return tokenEndpointAuthMethodsSupported; + } + + public void setTokenEndpointAuthMethodsSupported(List tokenEndpointAuthMethodsSupported) { + this.tokenEndpointAuthMethodsSupported = tokenEndpointAuthMethodsSupported; + } + + public List getTokenEndpointAuthSigningAlgValuesSupported() { + return tokenEndpointAuthSigningAlgValuesSupported; + } + + public void setTokenEndpointAuthSigningAlgValuesSupported(List tokenEndpointAuthSigningAlgValuesSupported) { + this.tokenEndpointAuthSigningAlgValuesSupported = tokenEndpointAuthSigningAlgValuesSupported; + } + + public List getDisplayValuesSupported() { + return displayValuesSupported; + } + + public void setDisplayValuesSupported(List displayValuesSupported) { + this.displayValuesSupported = displayValuesSupported; + } + + public List getClaimTypesSupported() { + return claimTypesSupported; + } + + public void setClaimTypesSupported(List claimTypesSupported) { + this.claimTypesSupported = claimTypesSupported; + } + + public List getClaimsSupported() { + return claimsSupported; + } + + public void setClaimsSupported(List claimsSupported) { + this.claimsSupported = claimsSupported; + } + + public String getServiceDocumentation() { + return serviceDocumentation; + } + + public void setServiceDocumentation(String serviceDocumentation) { + this.serviceDocumentation = serviceDocumentation; + } + + public List getClaimsLocalesSupported() { + return claimsLocalesSupported; + } + + public void setClaimsLocalesSupported(List claimsLocalesSupported) { + this.claimsLocalesSupported = claimsLocalesSupported; + } + + public List getUiLocalesSupported() { + return uiLocalesSupported; + } + + public void setUiLocalesSupported(List uiLocalesSupported) { + this.uiLocalesSupported = uiLocalesSupported; + } + + public Boolean getClaimsParameterSupported() { + return claimsParameterSupported; + } + + public void setClaimsParameterSupported(Boolean claimsParameterSupported) { + this.claimsParameterSupported = claimsParameterSupported; + } + + public Boolean getRequestParameterSupported() { + return requestParameterSupported; + } + + public void setRequestParameterSupported(Boolean requestParameterSupported) { + this.requestParameterSupported = requestParameterSupported; + } + + public Boolean getRequestUriParameterSupported() { + return requestUriParameterSupported; + } + + public void setRequestUriParameterSupported(Boolean requestUriParameterSupported) { + this.requestUriParameterSupported = requestUriParameterSupported; + } + + public Boolean getRequireRequestUriRegistration() { + return requireRequestUriRegistration; + } + + public void setRequireRequestUriRegistration(Boolean requireRequestUriRegistration) { + this.requireRequestUriRegistration = requireRequestUriRegistration; + } + + public String getOpPolicyUri() { + return opPolicyUri; + } + + public void setOpPolicyUri(String opPolicyUri) { + this.opPolicyUri = opPolicyUri; + } + + public String getOpTosUri() { + return opTosUri; + } + + public void setOpTosUri(String opTosUri) { + this.opTosUri = opTosUri; + } + + public String getRevocationEndpointUri() { + return revocationEndpointUri; + } + + public void setRevocationEndpointUri(String revocationEndpointUri) { + this.revocationEndpointUri = revocationEndpointUri; + } + + public UserInfoTokenMethod getUserInfoTokenMethod() { + return userInfoTokenMethod; + } + + public void setUserInfoTokenMethod(UserInfoTokenMethod userInfoTokenMethod) { + this.userInfoTokenMethod = userInfoTokenMethod; + } + + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/service/OidcAccountProvisioningService.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/provisioning/OidcAccountProvisioningService.java similarity index 97% rename from iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/service/OidcAccountProvisioningService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/provisioning/OidcAccountProvisioningService.java index 00e7f5eb8f..9eb73a862c 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/service/OidcAccountProvisioningService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/provisioning/OidcAccountProvisioningService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.authn.oidc.service; +package it.infn.mw.iam.authn.oidc.provisioning; import static java.lang.String.format; @@ -21,10 +21,10 @@ import java.util.Set; import java.util.UUID; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.stereotype.Service; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; import it.infn.mw.iam.core.user.IamAccountService; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamOidcId; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/userinfo/UserInfoFetcher.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/userinfo/UserInfoFetcher.java new file mode 100644 index 0000000000..b824db7546 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/userinfo/UserInfoFetcher.java @@ -0,0 +1,141 @@ +/** + * 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.authn.oidc.userinfo; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.http.client.HttpClient; +import org.apache.http.client.utils.URIBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import it.infn.mw.iam.authn.oidc.model.PendingOIDCAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration.UserInfoTokenMethod; +import it.infn.mw.iam.persistence.model.IamUserInfo; + +public class UserInfoFetcher { + + private static final Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class); + + private LoadingCache cache; + + public UserInfoFetcher(HttpClient httpClient) { + cache = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .maximumSize(100) + .build(new UserInfoLoader(httpClient)); + } + + public IamUserInfo loadUserInfo(final PendingOIDCAuthenticationToken token) { + try { + return cache.get(token); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Couldn't load User Info from token: " + e.getMessage()); + return null; + } + } + + private class UserInfoLoader extends CacheLoader { + private HttpComponentsClientHttpRequestFactory factory; + + UserInfoLoader(HttpClient httpClient) { + this.factory = new HttpComponentsClientHttpRequestFactory(httpClient); + } + + @Override + public IamUserInfo load(final PendingOIDCAuthenticationToken token) throws URISyntaxException { + + ServerConfiguration serverConfiguration = token.getServerConfiguration(); + + if (serverConfiguration == null) { + logger.warn("No server configuration found."); + return null; + } + + if (Strings.isNullOrEmpty(serverConfiguration.getUserInfoUri())) { + logger.warn("No userinfo endpoint, not fetching."); + return null; + } + + String userInfoString = null; + + if (serverConfiguration.getUserInfoTokenMethod() == null + || serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.HEADER)) { + + RestTemplate restTemplate = new RestTemplate(factory) { + @Override + protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { + ClientHttpRequest httpRequest = super.createRequest(url, method); + httpRequest.getHeaders() + .add("Authorization", String.format("Bearer %s", token.getAccessTokenValue())); + return httpRequest; + } + }; + + userInfoString = + restTemplate.getForObject(serverConfiguration.getUserInfoUri(), String.class); + + } else if (serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.FORM)) { + MultiValueMap form = new LinkedMultiValueMap<>(); + form.add("access_token", token.getAccessTokenValue()); + + RestTemplate restTemplate = new RestTemplate(factory); + userInfoString = + restTemplate.postForObject(serverConfiguration.getUserInfoUri(), form, String.class); + } else if (serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.QUERY)) { + URIBuilder builder = new URIBuilder(serverConfiguration.getUserInfoUri()); + builder.setParameter("access_token", token.getAccessTokenValue()); + + RestTemplate restTemplate = new RestTemplate(factory); + userInfoString = restTemplate.getForObject(builder.toString(), String.class); + } + + if (!Strings.isNullOrEmpty(userInfoString)) { + + JsonObject userInfoJson = JsonParser.parseString(userInfoString).getAsJsonObject(); + IamUserInfo userInfo = fromJson(userInfoJson); + return userInfo; + } + + throw new IllegalArgumentException("Unable to load user info"); + } + } + + protected IamUserInfo fromJson(JsonObject userInfoJson) { + + return IamUserInfo.fromJson(userInfoJson); + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/validator/DefaultOidcTokenValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/validator/DefaultOidcTokenValidator.java index d2cacb44f9..1306651086 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/validator/DefaultOidcTokenValidator.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/oidc/validator/DefaultOidcTokenValidator.java @@ -17,10 +17,8 @@ import java.util.Optional; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.nimbusds.jwt.JWT; @@ -30,21 +28,19 @@ import it.infn.mw.iam.authn.common.ValidatorResolver; import it.infn.mw.iam.authn.common.ValidatorResult; import it.infn.mw.iam.authn.common.config.AuthenticationValidator; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; @Service -public class DefaultOidcTokenValidator - implements AuthenticationValidator { +public class DefaultOidcTokenValidator implements AuthenticationValidator { public static final Logger LOG = LoggerFactory.getLogger(DefaultOidcTokenValidator.class); private final ValidatorResolver resolver; - @Autowired public DefaultOidcTokenValidator(ValidatorResolver validatorResolver) { resolver = validatorResolver; } - @Override public void validateAuthentication(OIDCAuthenticationToken token) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/authn/util/AuthenticationUtils.java b/iam-login-service/src/main/java/it/infn/mw/iam/authn/util/AuthenticationUtils.java index 9cf7943024..6ba4a5dbb9 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/authn/util/AuthenticationUtils.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/authn/util/AuthenticationUtils.java @@ -19,7 +19,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.mitre.oauth2.model.SavedUserAuthentication; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -28,6 +27,7 @@ import it.infn.mw.iam.authn.oidc.OidcExternalAuthenticationToken; import it.infn.mw.iam.authn.saml.SamlExternalAuthenticationToken; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; public class AuthenticationUtils { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/AssertionConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/AssertionConfig.java index 5b4860d6cb..a487e6ac4f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/AssertionConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/AssertionConfig.java @@ -18,39 +18,46 @@ import java.util.LinkedHashMap; import java.util.Map; -import org.mitre.jwt.assertion.AssertionValidator; -import org.mitre.jwt.assertion.impl.NullAssertionValidator; -import org.mitre.jwt.assertion.impl.WhitelistedIssuerAssertionValidator; -import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory; -import org.mitre.oauth2.assertion.impl.DirectCopyRequestFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.assertion.AssertionOAuth2RequestFactory; +import it.infn.mw.iam.core.jwt.assertion.AssertionValidator; +import it.infn.mw.iam.core.jwt.assertion.DirectCopyRequestFactory; +import it.infn.mw.iam.core.jwt.assertion.SelfAssertionValidator; +import it.infn.mw.iam.core.jwt.assertion.WhitelistedIssuerAssertionValidator; @Configuration public class AssertionConfig { @Bean - @Qualifier("jwtAssertionValidator") - public AssertionValidator jwtAssertionValidator() { - return new NullAssertionValidator(); - } - - @Bean - @Qualifier("jwtAssertionTokenFactory") - public AssertionOAuth2RequestFactory jwtAssertionTokenFactory() { + AssertionOAuth2RequestFactory jwtAssertionTokenFactory() { return new DirectCopyRequestFactory(); } @Bean @Qualifier("clientAssertionValidator") - public AssertionValidator clientAssertionValidator() { + AssertionValidator clientAssertionValidator(JwkSetCacheService jwkSetCacheService) { Map whitelist = new LinkedHashMap<>(); whitelist.put("http://artemesia.local", "http://localhost:8080/jwk"); - WhitelistedIssuerAssertionValidator validator = new WhitelistedIssuerAssertionValidator(); + WhitelistedIssuerAssertionValidator validator = + new WhitelistedIssuerAssertionValidator(jwkSetCacheService); validator.setWhitelist(whitelist); return validator; } + + @Bean + @Primary + @Qualifier("selfAssertionValidator") + AssertionValidator selfAssertionValidator(IamProperties properties, + JwtSigningAndValidationService jwtService) { + + return new SelfAssertionValidator(properties, jwtService); + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/AuthorizationServerConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/AuthorizationServerConfig.java index e248635c36..b641901c7a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/AuthorizationServerConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/AuthorizationServerConfig.java @@ -15,51 +15,22 @@ */ package it.infn.mw.iam.config; -import java.util.Arrays; -import java.util.Collections; - -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.DeviceCodeService; -import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.oauth2.token.ChainedTokenGranter; -import org.mitre.oauth2.token.JWTAssertionTokenGranter; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.CompositeTokenGranter; import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.OAuth2RequestValidator; import org.springframework.security.oauth2.provider.TokenGranter; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; -import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; -import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; - -import it.infn.mw.iam.api.account.AccountUtils; -import it.infn.mw.iam.core.oauth.exchange.TokenExchangePdp; -import it.infn.mw.iam.core.oauth.granters.IamAuthorizationCodeTokenGranter; -import it.infn.mw.iam.core.oauth.granters.IamClientCredentialsTokenGranter; -import it.infn.mw.iam.core.oauth.granters.IamDeviceCodeTokenGranter; -import it.infn.mw.iam.core.oauth.granters.IamImplicitTokenGranter; -import it.infn.mw.iam.core.oauth.granters.IamRefreshTokenGranter; -import it.infn.mw.iam.core.oauth.granters.IamResourceOwnerPasswordTokenGranter; -import it.infn.mw.iam.core.oauth.granters.TokenExchangeTokenGranter; -import it.infn.mw.iam.core.util.IamAuthenticationEventPublisher; -import it.infn.mw.iam.service.aup.AUPSignatureCheckService; + +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.core.client.IamClientDetailsService; @SuppressWarnings("deprecation") @Configuration @@ -67,136 +38,52 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired - @Qualifier("iamUserDetailsService") - private UserDetailsService iamUserDetailsService; - - @Autowired - private OAuth2TokenEntityService tokenServices; - - @Autowired - @Qualifier("iamClientDetailsEntityService") - private ClientDetailsEntityService clientDetailsService; - - @Autowired - private OAuth2RequestFactory requestFactory; + OAuth2TokenEntityService tokenServices; @Autowired - private AuthorizationCodeServices authorizationCodeServices; + IamClientDetailsService clientDetailsService; @Autowired - private OAuth2RequestValidator requestValidator; + OAuth2RequestFactory requestFactory; @Autowired - private UserApprovalHandler iamUserApprovalHandler; + AuthorizationCodeServices authorizationCodeServices; @Autowired - private PasswordEncoder passwordEncoder; + OAuth2RequestValidator requestValidator; @Autowired - private DeviceCodeService deviceCodeService; + UserApprovalHandler iamUserApprovalHandler; @Autowired - private AccountUtils accountUtils; + TokenGranter tokenGranter; @Autowired - private AUPSignatureCheckService signatureCheckService; - - @Autowired - private TokenExchangePdp tokenExchangePdp; - - @Bean - WebResponseExceptionTranslator webResponseExceptionTranslator() { - - return new DefaultWebResponseExceptionTranslator(); - } - - @Bean(name = "iamAuthenticationEventPublisher") - AuthenticationEventPublisher iamAuthenticationEventPublisher() { - return new IamAuthenticationEventPublisher(); - } - - @Bean(name = "authenticationManager") - AuthenticationManager authenticationManager() { - - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(iamUserDetailsService); - provider.setPasswordEncoder(passwordEncoder); - - ProviderManager pm = - new ProviderManager(Collections.singletonList(provider)); - - pm.setAuthenticationEventPublisher(iamAuthenticationEventPublisher()); - return pm; - - } - - @Bean - public TokenGranter tokenGranter() { - - AuthenticationManager authenticationManager = authenticationManager(); - - IamResourceOwnerPasswordTokenGranter resourceOwnerPasswordCredentialGranter = - new IamResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, - clientDetailsService, requestFactory); - - resourceOwnerPasswordCredentialGranter.setAccountUtils(accountUtils); - resourceOwnerPasswordCredentialGranter.setSignatureCheckService(signatureCheckService); - - IamRefreshTokenGranter refreshTokenGranter = - new IamRefreshTokenGranter(tokenServices, clientDetailsService, requestFactory); - refreshTokenGranter.setAccountUtils(accountUtils); - refreshTokenGranter.setSignatureCheckService(signatureCheckService); - - TokenExchangeTokenGranter tokenExchangeGranter = - new TokenExchangeTokenGranter(tokenServices, clientDetailsService, requestFactory); - - tokenExchangeGranter.setAccountUtils(accountUtils); - tokenExchangeGranter.setSignatureCheckService(signatureCheckService); - tokenExchangeGranter.setExchangePdp(tokenExchangePdp); - - return new CompositeTokenGranter(Arrays.asList( - new IamAuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, - clientDetailsService, requestFactory), - new IamImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory), - refreshTokenGranter, - new IamClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory), - resourceOwnerPasswordCredentialGranter, - new JWTAssertionTokenGranter(tokenServices, clientDetailsService, requestFactory), - new ChainedTokenGranter(tokenServices, clientDetailsService, requestFactory), - tokenExchangeGranter, new IamDeviceCodeTokenGranter(tokenServices, clientDetailsService, - requestFactory, deviceCodeService))); - } + AuthenticationManager authenticationManager; @Override public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception { - // @formatter:off - endpoints - .requestValidator(requestValidator) + endpoints.requestValidator(requestValidator) .pathMapping("/oauth/token", "/token") .pathMapping("/oauth/authorize", "/authorize") .tokenServices(tokenServices) .userApprovalHandler(iamUserApprovalHandler) .requestFactory(requestFactory) - .tokenGranter(tokenGranter()) - .authorizationCodeServices(authorizationCodeServices); - // @formatter:on + .tokenGranter(tokenGranter) + .authorizationCodeServices(authorizationCodeServices) + .authenticationManager(authenticationManager); } @Override public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); - } @Override public void configure(final AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); - - } - - } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/IamConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/IamConfig.java index 80b127a34f..6d4d4c4684 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/IamConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/IamConfig.java @@ -17,13 +17,15 @@ import java.time.Clock; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.h2.server.web.WebServlet; -import org.mitre.oauth2.repository.SystemScopeRepository; -import org.mitre.oauth2.service.impl.DefaultOAuth2AuthorizationCodeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -32,18 +34,56 @@ import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.core.Ordered; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.CompositeTokenGranter; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenGranter; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.session.web.http.DefaultCookieSerializer; import com.google.common.collect.Maps; import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.scim.converter.SshKeyConverter; +import it.infn.mw.iam.authn.oidc.DefaultRestTemplateFactory; +import it.infn.mw.iam.authn.oidc.RestTemplateFactory; +import it.infn.mw.iam.authn.oidc.configuration.DynamicServerConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.core.jwt.IamJwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.assertion.AssertionOAuth2RequestFactory; +import it.infn.mw.iam.core.jwt.assertion.AssertionValidator; import it.infn.mw.iam.core.oauth.attributes.AttributeMapHelper; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; +import it.infn.mw.iam.core.oauth.exchange.TokenExchangePdp; +import it.infn.mw.iam.core.oauth.granters.ChainedTokenGranter; +import it.infn.mw.iam.core.oauth.granters.IamAuthorizationCodeTokenGranter; +import it.infn.mw.iam.core.oauth.granters.IamClientCredentialsTokenGranter; +import it.infn.mw.iam.core.oauth.granters.IamDeviceCodeTokenGranter; +import it.infn.mw.iam.core.oauth.granters.IamImplicitTokenGranter; +import it.infn.mw.iam.core.oauth.granters.IamRefreshTokenGranter; +import it.infn.mw.iam.core.oauth.granters.IamResourceOwnerPasswordTokenGranter; +import it.infn.mw.iam.core.oauth.granters.TokenExchangeTokenGranter; import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; import it.infn.mw.iam.core.oauth.profile.ScopeAwareProfileResolver; @@ -80,7 +120,9 @@ import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatchersProperties; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatchersPropertiesParser; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; +import it.infn.mw.iam.core.token.JwtAssertionTokenGranter; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.core.util.IamAuthenticationEventPublisher; import it.infn.mw.iam.core.web.aup.EnforceAupFilter; import it.infn.mw.iam.notification.NotificationProperties; import it.infn.mw.iam.notification.service.resolver.AddressResolutionService; @@ -92,6 +134,7 @@ import it.infn.mw.iam.notification.service.resolver.NotifyGmStrategy; import it.infn.mw.iam.notification.service.resolver.NotifyGmsAndAdminsStrategy; import it.infn.mw.iam.persistence.repository.IamAupRepository; +import it.infn.mw.iam.persistence.repository.IamSystemScopeRepository; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; import it.infn.mw.iam.registration.validation.UsernameValidator; import it.infn.mw.iam.service.aup.AUPSignatureCheckService; @@ -99,6 +142,7 @@ @SuppressWarnings("deprecation") @Configuration public class IamConfig { + public static final Logger LOG = LoggerFactory.getLogger(IamConfig.class); @Value("${iam.organisation.name}") @@ -278,10 +322,11 @@ Clock defaultClock() { return Clock.systemDefaultZone(); } - @Bean - AuthorizationCodeServices authorizationCodeServices() { - return new DefaultOAuth2AuthorizationCodeService(); - } + // @Bean + // AuthorizationCodeServices authorizationCodeServices(IamAuthorizationCodeRepository repository, + // AuthenticationHolderEntityService authenticationHolderService) { + // return new IamAuthorizationCodeService(repository, authenticationHolderService); + // } @Bean PasswordEncoder passwordEncoder() { @@ -299,14 +344,14 @@ FilterRegistrationBean aupSignatureCheckFilter(AUPSignatureChe @Bean ScopeMatcherRegistry customScopeMatchersRegistry(ScopeMatchersProperties properties, - SystemScopeRepository scopeRepo) { + IamSystemScopeRepository scopeRepo) { ScopeMatchersPropertiesParser parser = new ScopeMatchersPropertiesParser(); return new DefaultScopeMatcherRegistry(parser.parseScopeMatchersProperties(properties), scopeRepo); } @Bean - @Profile("dev") + @Profile({"dev", "h2-console"}) ServletRegistrationBean h2Console() { WebServlet h2Servlet = new WebServlet(); return new ServletRegistrationBean<>(h2Servlet, "/h2-console/*"); @@ -329,4 +374,134 @@ DefaultCookieSerializer defaultCookieSerializer() { return cs; } + @Bean + JwkSetCacheService defaultCacheService(RestTemplateFactory rtf) { + + return new IamJwkSetCacheService(rtf, 100, 1, TimeUnit.HOURS); + } + + @Bean + WebResponseExceptionTranslator webResponseExceptionTranslator() { + + return new DefaultWebResponseExceptionTranslator(); + } + + @Bean(name = "iamAuthenticationEventPublisher") + AuthenticationEventPublisher iamAuthenticationEventPublisher() { + return new IamAuthenticationEventPublisher(); + } + + @Bean(name = "authenticationManager") + @Primary + AuthenticationManager authenticationManager(UserDetailsService iamUserDetailsService, + PasswordEncoder passwordEncoder) { + + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(iamUserDetailsService); + provider.setPasswordEncoder(passwordEncoder); + + ProviderManager pm = + new ProviderManager(Collections.singletonList(provider)); + + pm.setAuthenticationEventPublisher(iamAuthenticationEventPublisher()); + return pm; + } + + @Bean + TokenGranter tokenGranter( + @Qualifier("authenticationManager") AuthenticationManager authenticationManager, + UserDetailsService iamUserDetailsService, PasswordEncoder passwordEncoder, + OAuth2TokenEntityService tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, AccountUtils accountUtils, + AUPSignatureCheckService signatureCheckService, TokenExchangePdp tokenExchangePdp, + AuthorizationCodeServices authorizationCodeServices, AssertionValidator assertionValidator, + AssertionOAuth2RequestFactory assertionFactory, DeviceCodeService deviceCodeService) { + + IamResourceOwnerPasswordTokenGranter resourceOwnerPasswordCredentialGranter = + new IamResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, + clientDetailsService, requestFactory, signatureCheckService, accountUtils); + + IamRefreshTokenGranter refreshTokenGranter = new IamRefreshTokenGranter(tokenServices, + clientDetailsService, requestFactory, signatureCheckService, accountUtils); + + TokenExchangeTokenGranter tokenExchangeGranter = + new TokenExchangeTokenGranter(tokenServices, clientDetailsService, requestFactory); + + tokenExchangeGranter.setAccountUtils(accountUtils); + tokenExchangeGranter.setSignatureCheckService(signatureCheckService); + tokenExchangeGranter.setExchangePdp(tokenExchangePdp); + + return new CompositeTokenGranter(Arrays.asList( + new IamAuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, + clientDetailsService, requestFactory), + new IamImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory), + refreshTokenGranter, + new IamClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory), + resourceOwnerPasswordCredentialGranter, + new JwtAssertionTokenGranter(tokenServices, clientDetailsService, requestFactory, + assertionValidator, assertionFactory), + new ChainedTokenGranter(tokenServices, clientDetailsService, requestFactory), + tokenExchangeGranter, new IamDeviceCodeTokenGranter(tokenServices, clientDetailsService, + requestFactory, deviceCodeService))); + } + + @Bean + FilterRegistrationBean disabledAutomaticFilterRegistration( + OAuth2AuthenticationProcessingFilter f) { + + FilterRegistrationBean b = + new FilterRegistrationBean<>(f); + b.setEnabled(false); + return b; + } + + @Bean + OAuth2AuthenticationProcessingFilter oauthResourceServerFilter( + OAuth2AuthenticationEntryPoint authenticationEntryPoint, + OAuth2TokenEntityService tokenService) { + + OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager(); + manager.setTokenServices(tokenService); + + OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter(); + filter.setAuthenticationEntryPoint(authenticationEntryPoint); + filter.setAuthenticationManager(manager); + filter.setStateless(false); + return filter; + } + + @Bean + @Profile("!canl") + RestTemplateFactory restTemplateFactory() { + + return new DefaultRestTemplateFactory(new HttpComponentsClientHttpRequestFactory()); + } + + @Bean + @Profile("canl") + RestTemplateFactory canlRestTemplateFactory( + @Qualifier("canlRequestFactory") ClientHttpRequestFactory rf) { + + return new DefaultRestTemplateFactory(rf); + } + + @Bean + @Profile("!canl") + ServerConfigurationService dynamicServerConfiguration() { + + return new DynamicServerConfigurationService(); + } + + @Bean + @Profile("canl") + ServerConfigurationService canlDynamicServerConfiguration( + @Qualifier("canlHttpClient") HttpClient client) { + + return new DynamicServerConfigurationService(client); + } + +// @Bean +// HttpClient httpClient() { +// return HttpClientBuilder.create().useSystemProperties().build(); +// } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/IamProperties.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/IamProperties.java index 9e5066c019..324c5fe764 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/IamProperties.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/IamProperties.java @@ -599,6 +599,18 @@ public void setTrackLastUsed(boolean trackLastUsed) { } } + public static class GarbageCollectorProperties { + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + public static class DefaultGroup { private String name; private String enrollment = "INSERT"; @@ -727,6 +739,10 @@ public void setUrnSubnamespaces(String urnSubnamespaces) { private AarcProfile aarcProfile = new AarcProfile(); + private List languageNamespaces = Lists.newArrayList("messages"); + + private GarbageCollectorProperties garbageCollector = new GarbageCollectorProperties(); + public String getBaseUrl() { return baseUrl; } @@ -977,4 +993,9 @@ public void setAarcProfile(AarcProfile aarcProfile) { this.aarcProfile = aarcProfile; } + public List getLanguageNamespaces() { + + return languageNamespaces; + } + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/IamTotpMfaConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/IamTotpMfaConfig.java index f760a760ac..5a1f46831d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/IamTotpMfaConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/IamTotpMfaConfig.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -40,16 +41,17 @@ import dev.samstevens.totp.time.SystemTimeProvider; import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.account.multi_factor_authentication.IamTotpMfaService; -import it.infn.mw.iam.service.aup.AUPSignatureCheckService; import it.infn.mw.iam.authn.multi_factor_authentication.MultiFactorTotpCheckProvider; import it.infn.mw.iam.authn.multi_factor_authentication.MultiFactorVerificationFilter; import it.infn.mw.iam.authn.multi_factor_authentication.MultiFactorVerificationSuccessHandler; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.service.aup.AUPSignatureCheckService; /** * Beans for handling TOTP MFA functionality */ @Configuration +@Profile("mfa") public class IamTotpMfaConfig { @Value("${iam.baseUrl}") @@ -100,7 +102,7 @@ CodeVerifier codeVerifier() { return new DefaultCodeVerifier(new DefaultCodeGenerator(), new SystemTimeProvider()); } - @Bean(name = "MultiFactorVerificationFilter") + @Bean MultiFactorVerificationFilter multiFactorVerificationFilter( @Qualifier("MultiFactorVerificationAuthenticationManager") AuthenticationManager authenticationManager) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/JWTCriptoConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/JWTCriptoConfig.java index 0072ac7c11..4aca87dde4 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/JWTCriptoConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/JWTCriptoConfig.java @@ -15,9 +15,6 @@ */ package it.infn.mw.iam.config; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -26,8 +23,11 @@ import org.springframework.core.io.ResourceLoader; import it.infn.mw.iam.config.error.IAMJWTKeystoreError; -import it.infn.mw.iam.core.jwk.IamJWTEncryptionService; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.core.jwt.IamJwtEncryptionAndDecryptionService; +import it.infn.mw.iam.core.jwt.IamJwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; +import it.infn.mw.iam.core.jwt.JwtEncryptionAndDecryptionService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; import it.infn.mw.iam.util.JWKKeystoreLoader; @Configuration @@ -42,22 +42,22 @@ public class JWTCriptoConfig { ResourceLoader resourceLoader; @Bean - public JWKKeystoreLoader loader() { + JWKKeystoreLoader loader() { return new JWKKeystoreLoader(resourceLoader); } @Bean(name = "defaultKeyStore") - public JWKSetKeyStore defaultKeyStore(JWKKeystoreLoader loader) { + JwkSetKeyStore defaultKeyStore(JWKKeystoreLoader loader) { LOG.info("Loading JWT keystore from: {}", iamProperties.getJwk().getKeystoreLocation()); return loader.loadKeystoreFromLocation(iamProperties.getJwk().getKeystoreLocation()); } @Bean(name = "defaultsignerService") - public JWTSigningAndValidationService defaultSignerService(JWKSetKeyStore keystore) { + JwtSigningAndValidationService defaultSignerService(JwkSetKeyStore keystore) { try { - IamJWTSigningService signerService = - new IamJWTSigningService(iamProperties.getJwk(), keystore); + JwtSigningAndValidationService signerService = + new IamJwtSigningAndValidationService(iamProperties.getJwk(), keystore); LOG.info("Default JWK key id: {}", iamProperties.getJwk().getDefaultKeyId()); LOG.info("Default JWS algorithm: {}", iamProperties.getJwk().getDefaultJwsAlgorithm()); @@ -69,13 +69,13 @@ public JWTSigningAndValidationService defaultSignerService(JWKSetKeyStore keysto } @Bean(name = "defaultEncryptionService") - public JWTEncryptionAndDecryptionService defaultEncryptionService( - JWKSetKeyStore keystore) { + JwtEncryptionAndDecryptionService defaultEncryptionService( + JwkSetKeyStore keystore) { try { - IamJWTEncryptionService encryptionService = - new IamJWTEncryptionService(iamProperties, keystore); + JwtEncryptionAndDecryptionService encryptionService = + new IamJwtEncryptionAndDecryptionService(iamProperties, keystore); LOG.info("Default JWE key encrypt key id: {}", iamProperties.getJwk().getDefaultJweEncryptKeyId()); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreConfigurationPropertiesBean.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreConfigurationPropertiesBean.java new file mode 100644 index 0000000000..549e850d1f --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreConfigurationPropertiesBean.java @@ -0,0 +1,120 @@ +/** + * 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.config; + +import java.util.List; +import java.util.Locale; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; + +public class MitreConfigurationPropertiesBean { + + private String issuer; + + private String topbarTitle; + + private String shortTopbarTitle; + + private String logoImageUrl; + + private Long regTokenLifeTime; + + private Long rqpTokenLifeTime; + + private boolean forceHttps = false; + + private Locale locale = Locale.ENGLISH; + + private List languageNamespaces = Lists.newArrayList("messages"); + + private boolean dualClient = false; + + private boolean heartMode = false; + + private boolean allowCompleteDeviceCodeUri = false; + + public MitreConfigurationPropertiesBean(IamProperties properties) { + + this.issuer = properties.getIssuer().endsWith("/") ? properties.getIssuer() : properties.getIssuer() + "/"; + this.topbarTitle = properties.getTopbarTitle(); + this.logoImageUrl = properties.getLogo().getUrl(); + this.regTokenLifeTime = properties.getToken().getLifetime() <= 0L ? null : properties.getToken().getLifetime(); + this.forceHttps = false; + this.locale = Locale.ENGLISH; + this.allowCompleteDeviceCodeUri = properties.getDeviceCode().getAllowCompleteVerificationUri(); + } + + public String getIssuer() { + return issuer; + } + + public String getTopbarTitle() { + return topbarTitle; + } + + public String getShortTopbarTitle() { + return shortTopbarTitle == null ? topbarTitle : shortTopbarTitle; + } + + public String getLogoImageUrl() { + return logoImageUrl; + } + + public Long getRegTokenLifeTime() { + return regTokenLifeTime; + } + + public Long getRqpTokenLifeTime() { + return rqpTokenLifeTime; + } + + public boolean isForceHttps() { + return forceHttps; + } + + public Locale getLocale() { + return locale; + } + + public List getLanguageNamespaces() { + return languageNamespaces; + } + + public boolean isDualClient() { + if (isHeartMode()) { + return false; + } + return dualClient; + } + + public String getLanguageNamespacesString() { + return new Gson().toJson(getLanguageNamespaces()); + } + + public String getDefaultLanguageNamespace() { + return getLanguageNamespaces().get(0); + } + + public boolean isHeartMode() { + return heartMode; + } + + public boolean isAllowCompleteDeviceCodeUri() { + return allowCompleteDeviceCodeUri; + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreRepositoryConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreRepositoryConfig.java deleted file mode 100644 index 89c803d72c..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreRepositoryConfig.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * 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.config; - -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.repository.AuthorizationCodeRepository; -import org.mitre.oauth2.repository.OAuth2ClientRepository; -import org.mitre.oauth2.repository.OAuth2TokenRepository; -import org.mitre.oauth2.repository.SystemScopeRepository; -import org.mitre.oauth2.repository.impl.DeviceCodeRepository; -import org.mitre.oauth2.repository.impl.JpaAuthenticationHolderRepository; -import org.mitre.oauth2.repository.impl.JpaAuthorizationCodeRepository; -import org.mitre.oauth2.repository.impl.JpaDeviceCodeRepository; -import org.mitre.oauth2.repository.impl.JpaOAuth2TokenRepository; -import org.mitre.oauth2.repository.impl.JpaSystemScopeRepository; -import org.mitre.openid.connect.repository.ApprovedSiteRepository; -import org.mitre.openid.connect.repository.BlacklistedSiteRepository; -import org.mitre.openid.connect.repository.PairwiseIdentifierRepository; -import org.mitre.openid.connect.repository.UserInfoRepository; -import org.mitre.openid.connect.repository.WhitelistedSiteRepository; -import org.mitre.openid.connect.repository.impl.JpaApprovedSiteRepository; -import org.mitre.openid.connect.repository.impl.JpaBlacklistedSiteRepository; -import org.mitre.openid.connect.repository.impl.JpaPairwiseIdentifierRepository; -import org.mitre.openid.connect.repository.impl.JpaWhitelistedSiteRepository; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_3; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import it.infn.mw.iam.persistence.repository.IamUserinfoRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamOAuth2ClientRepositoryAdapter; - -@Configuration -public class MitreRepositoryConfig { - - @Bean - AuthenticationHolderRepository authenticationHolderRepository() { - - return new JpaAuthenticationHolderRepository(); - } - - @Bean - AuthorizationCodeRepository authorizationCodeRepository() { - - return new JpaAuthorizationCodeRepository(); - } - - @Bean - PairwiseIdentifierRepository defaultPairwiseIdentifierRepository() { - - return new JpaPairwiseIdentifierRepository(); - } - - @Bean - UserInfoRepository defaultUserInfoRepository() { - - return new IamUserinfoRepository(); - } - - @Bean - OAuth2ClientRepository defaultOAuth2ClientRepository(IamClientRepository clientRepo, - IamAccountClientRepository accountClientRepo) { - return new IamOAuth2ClientRepositoryAdapter(clientRepo, accountClientRepo); - } - - @Bean - OAuth2TokenRepository defaultOAuth2TokenRepository() { - - return new JpaOAuth2TokenRepository(); - } - - @Bean - ApprovedSiteRepository defaultApprovedSiteRepository() { - - return new JpaApprovedSiteRepository(); - } - - @Bean - WhitelistedSiteRepository defaultWhitelistedSiteRepository() { - - return new JpaWhitelistedSiteRepository(); - } - - @Bean - BlacklistedSiteRepository defaultBlacklistedSiteRepository() { - - return new JpaBlacklistedSiteRepository(); - } - - @Bean - SystemScopeRepository defaultSystemScopeRepository() { - - return new JpaSystemScopeRepository(); - } - - @Bean - DeviceCodeRepository deviceCodeRepository() { - return new JpaDeviceCodeRepository(); - } - - @Bean - MITREidDataService_1_3 dataService13() { - return new MITREidDataService_1_3(); - } -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreServicesConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreServicesConfig.java index 50f22d42eb..3986687f69 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreServicesConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/MitreServicesConfig.java @@ -15,79 +15,29 @@ */ package it.infn.mw.iam.config; -import java.util.Locale; import java.util.Set; -import java.util.concurrent.TimeUnit; -import org.mitre.jwt.assertion.AssertionValidator; -import org.mitre.jwt.assertion.impl.SelfAssertionValidator; -import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService; -import org.mitre.oauth2.repository.AuthorizationCodeRepository; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.DeviceCodeService; -import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.oauth2.service.impl.BlacklistAwareRedirectResolver; -import org.mitre.oauth2.service.impl.DefaultDeviceCodeService; -import org.mitre.oauth2.service.impl.DefaultOAuth2ClientDetailsEntityService; -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; -import org.mitre.openid.connect.service.DynamicClientValidationService; -import org.mitre.openid.connect.service.LoginHintExtracter; -import org.mitre.openid.connect.service.OIDCTokenService; -import org.mitre.openid.connect.service.PairwiseIdentiferService; -import org.mitre.openid.connect.service.StatsService; -import org.mitre.openid.connect.service.UserInfoService; -import org.mitre.openid.connect.service.WhitelistedSiteService; -import org.mitre.openid.connect.service.impl.DefaultApprovedSiteService; -import org.mitre.openid.connect.service.impl.DefaultBlacklistedSiteService; -import org.mitre.openid.connect.service.impl.DefaultOIDCTokenService; -import org.mitre.openid.connect.service.impl.DefaultStatsService; -import org.mitre.openid.connect.service.impl.DefaultUserInfoService; -import org.mitre.openid.connect.service.impl.DefaultWhitelistedSiteService; -import org.mitre.openid.connect.service.impl.DummyResourceSetService; -import org.mitre.openid.connect.service.impl.InMemoryClientLogoLoadingService; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_0; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_1; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_2; -import org.mitre.openid.connect.service.impl.MatchLoginHintsAgainstUsers; -import org.mitre.openid.connect.service.impl.UUIDPairwiseIdentiferService; -import org.mitre.openid.connect.token.ConnectTokenEnhancer; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; -import org.mitre.openid.connect.web.ServerConfigInterceptor; -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; import org.springframework.security.oauth2.provider.OAuth2RequestValidator; -import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import com.google.common.collect.Sets; -import it.infn.mw.iam.authn.oidc.RestTemplateFactory; -import it.infn.mw.iam.core.client.ClientUserDetailsService; -import it.infn.mw.iam.core.client.IAMClientUserDetailsService; -import it.infn.mw.iam.core.jwk.IamJWKSetCacheService; +import it.infn.mw.iam.authn.oidc.AuthorizationRequestFilter; +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.core.client.IamClientDetailsService; import it.infn.mw.iam.core.oauth.IamOAuth2RequestFactory; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; -import it.infn.mw.iam.core.oauth.scope.IamSystemScopeService; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcherOAuthRequestValidator; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcherRegistry; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; -import it.infn.mw.iam.core.oidc.IamClientValidationService; -import it.infn.mw.iam.core.userinfo.IamUserInfoInterceptor; +import it.infn.mw.iam.persistence.repository.IamAuthorizationCodeRepository; @SuppressWarnings("deprecation") @Configuration @@ -106,35 +56,11 @@ public class MitreServicesConfig { private String topbarTitle; @Bean - ConfigurationPropertiesBean config(IamProperties properties) { + MitreConfigurationPropertiesBean config(IamProperties properties) { - ConfigurationPropertiesBean config = new ConfigurationPropertiesBean(); - - config.setLogoImageUrl(properties.getLogo().getUrl()); - config.setTopbarTitle(topbarTitle); - - if (!issuer.endsWith("/")) { - issuer = issuer + "/"; - } - - config.setIssuer(issuer); - - if (tokenLifeTime <= 0L) { - config.setRegTokenLifeTime(null); - } else { - config.setRegTokenLifeTime(tokenLifeTime); - } - - config.setForceHttps(false); - config.setLocale(Locale.ENGLISH); - - config - .setAllowCompleteDeviceCodeUri(properties.getDeviceCode().getAllowCompleteVerificationUri()); - - return config; + return new MitreConfigurationPropertiesBean(properties); } - @Bean UIConfiguration uiConfiguration() { @@ -146,13 +72,6 @@ UIConfiguration uiConfiguration() { UIConfiguration config = new UIConfiguration(); config.setJsFiles(jsFiles); return config; - - } - - @Bean - RedirectResolver blacklistAwareRedirectResolver() { - - return new BlacklistAwareRedirectResolver(); } @Bean @@ -162,52 +81,24 @@ OAuth2RequestValidator requestValidator(ScopeMatcherRegistry registry) { } @Bean - OAuth2RequestFactory requestFactory(ScopeFilter scopeFilter, JWTProfileResolver profileResolver, - DeviceCodeService deviceCodeService, AuthorizationCodeRepository authzCodeRepository, + OAuth2RequestFactory requestFactory(IamClientDetailsService clientDetailsService, + ScopeFilter scopeFilter, JWTProfileResolver profileResolver, + DeviceCodeService deviceCodeService, IamAuthorizationCodeRepository authzCodeRepository, OAuth2TokenEntityService tokenServices) { - return new IamOAuth2RequestFactory(clientDetailsEntityService(), scopeFilter, profileResolver, + return new IamOAuth2RequestFactory(clientDetailsService, scopeFilter, profileResolver, deviceCodeService, authzCodeRepository, tokenServices); } - @Bean - @Qualifier("iamClientDetailsEntityService") - ClientDetailsEntityService clientDetailsEntityService() { - return new DefaultOAuth2ClientDetailsEntityService(); - } - - @Bean(name = "mitreUserInfoInterceptor") - IamUserInfoInterceptor userInfoInterceptor(UserInfoService service) { - - return new IamUserInfoInterceptor(service); - } - - @Bean(name = "mitreServerConfigInterceptor") - ServerConfigInterceptor serverConfigInterceptor() { - - return new ServerConfigInterceptor(); - } - @Bean FilterRegistrationBean disabledMitreFilterRegistration( AuthorizationRequestFilter f) { FilterRegistrationBean b = new FilterRegistrationBean<>(f); b.setEnabled(false); + // what ??? return b; } - @Bean(name = "mitreAuthzRequestFilter") - AuthorizationRequestFilter authorizationRequestFilter() { - - return new AuthorizationRequestFilter(); - } - - @Bean - AuthenticationTimeStamper timestamper() { - - return new AuthenticationTimeStamper(); - } - @Bean Http403ForbiddenEntryPoint http403ForbiddenEntryPoint() { @@ -221,141 +112,4 @@ OAuth2AuthenticationEntryPoint oauth2AuthenticationEntryPoint() { entryPoint.setRealmName("openidconnect"); return entryPoint; } - - @Bean - TokenEnhancer defaultTokenEnhancer() { - - return new ConnectTokenEnhancer(); - } - - @Bean(name = "clientUserDetailsService") - ClientUserDetailsService defaultClientUserDetailsService( - ClientDetailsEntityService clientService) { - - return new IAMClientUserDetailsService(clientService); - } - - @Bean - DynamicClientValidationService clientValidationService(ScopeMatcherRegistry registry, - SystemScopeService scopeService, BlacklistedSiteService blacklistService, - ConfigurationPropertiesBean config, - @Qualifier("clientAssertionValidator") AssertionValidator validator, - ClientDetailsEntityService clientService) { - - return new IamClientValidationService(registry, scopeService, validator, blacklistService, - config, clientService); - } - - @Bean - MITREidDataService_1_0 mitreDataService10() { - - return new MITREidDataService_1_0(); - } - - @Bean - MITREidDataService_1_1 mitreDataService11() { - - return new MITREidDataService_1_1(); - } - - @Bean - MITREidDataService_1_2 mitreDataService12() { - - return new MITREidDataService_1_2(); - } - - @Bean - LoginHintExtracter defaultLoginHintExtracter() { - - return new MatchLoginHintsAgainstUsers(); - } - - @Bean - ClientLogoLoadingService defaultClientLogoLoadingService() { - - return new InMemoryClientLogoLoadingService(); - } - - - - @Bean - SymmetricKeyJWTValidatorCacheService defaultSimmetricKeyJWTValidatorCacheService() { - - return new SymmetricKeyJWTValidatorCacheService(); - } - - @Bean - JWKSetCacheService defaultCacheService(RestTemplateFactory rtf) { - - return new IamJWKSetCacheService(rtf, 100, 1, TimeUnit.HOURS); - } - - @Bean - OIDCTokenService defaultOIDCTokenService() { - - return new DefaultOIDCTokenService(); - } - - @Bean - PairwiseIdentiferService defaultPairwiseIdentifierService() { - - return new UUIDPairwiseIdentiferService(); - } - - @Bean - UserInfoService defaultUserInfoService() { - - return new DefaultUserInfoService(); - } - - @Bean - ApprovedSiteService defaultApprovedSiteService() { - - return new DefaultApprovedSiteService(); - } - - @Bean - StatsService defaultStatsService() { - - return new DefaultStatsService(); - } - - @Bean - WhitelistedSiteService defaultWhitelistedSiteService() { - - return new DefaultWhitelistedSiteService(); - } - - @Bean - BlacklistedSiteService defaultBlacklistedSiteService() { - - return new DefaultBlacklistedSiteService(); - } - - @Bean - SystemScopeService defaultSystemScopeService(ScopeMatcherRegistry registry) { - return new IamSystemScopeService(registry); - } - - @Bean - ResourceSetService defaultResourceSetService() { - - return new DummyResourceSetService(); - } - - @Bean - ClientKeyCacheService defaultClientKeyCacheService() { - - return new ClientKeyCacheService(); - } - - @Bean - DeviceCodeService defaultDeviceCodeService() { - return new DefaultDeviceCodeService(); - } - - @Bean - SelfAssertionValidator selfAssertionValidator() { - return new SelfAssertionValidator(); - } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/MvcConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/MvcConfig.java index d8557f9d40..a39758caaf 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/MvcConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/MvcConfig.java @@ -21,11 +21,9 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import org.mitre.openid.connect.web.ServerConfigInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; @@ -48,6 +46,7 @@ import it.infn.mw.iam.core.userinfo.IamUserInfoInterceptor; import it.infn.mw.iam.core.util.PoliteJsonMessageSource; +import it.infn.mw.iam.core.web.ServerConfigInterceptor; import it.infn.mw.iam.core.web.util.IamViewInfoInterceptor; @Configuration @@ -56,11 +55,9 @@ public class MvcConfig implements WebMvcConfigurer { public static final Logger LOG = LoggerFactory.getLogger(MvcConfig.class); @Autowired - @Qualifier("mitreUserInfoInterceptor") IamUserInfoInterceptor userInfoInterceptor; @Autowired - @Qualifier("mitreServerConfigInterceptor") ServerConfigInterceptor serverConfigInterceptor; @Autowired @@ -147,7 +144,7 @@ private BeanNameViewResolver beanNameViewResolver() { } @Bean - public LocaleResolver localeResolver() { + LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); slr.setDefaultLocale(Locale.US); @@ -155,7 +152,7 @@ public LocaleResolver localeResolver() { } @Bean - public MessageSource messageSource() { + MessageSource messageSource() { DefaultResourceLoader loader = new DefaultResourceLoader(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/ResourceServerConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/ResourceServerConfig.java index d4aa7e4e5e..92b1bfae08 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/ResourceServerConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/ResourceServerConfig.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.config; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -25,38 +24,40 @@ import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import it.infn.mw.iam.core.OAuth2TokenEntityService; + @SuppressWarnings("deprecation") @Configuration @Order(9) // This is important! Do not remove public class ResourceServerConfig { - - @Autowired - private OAuth2AuthenticationEntryPoint authenticationEntryPoint; - - @Autowired - private OAuth2TokenEntityService tokenService; - - @Bean - public FilterRegistrationBean disabledAutomaticFilterRegistration( - final OAuth2AuthenticationProcessingFilter f) { - - FilterRegistrationBean b = - new FilterRegistrationBean<>(f); - b.setEnabled(false); - return b; - } - - @Bean(name = "resourceServerFilter") - public OAuth2AuthenticationProcessingFilter oauthResourceServerFilter() { - OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager(); - manager.setTokenServices(tokenService); +// @Autowired +// private OAuth2AuthenticationEntryPoint authenticationEntryPoint; +// +// @Autowired +// private OAuth2TokenEntityService tokenService; +// +// @Bean +// FilterRegistrationBean disabledAutomaticFilterRegistration( +// final OAuth2AuthenticationProcessingFilter f) { +// +// FilterRegistrationBean b = +// new FilterRegistrationBean<>(f); +// b.setEnabled(false); +// return b; +// } +// +// @Bean(name = "resourceServerFilter") +// OAuth2AuthenticationProcessingFilter oauthResourceServerFilter() { +// +// OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager(); +// manager.setTokenServices(tokenService); +// +// OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter(); +// filter.setAuthenticationEntryPoint(authenticationEntryPoint); +// filter.setAuthenticationManager(manager); +// filter.setStateless(false); +// return filter; +// } - OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter(); - filter.setAuthenticationEntryPoint(authenticationEntryPoint); - filter.setAuthenticationManager(manager); - filter.setStateless(false); - return filter; - } - } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java index 5b8f25fd68..1f6b102c9b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java @@ -15,36 +15,33 @@ */ package it.infn.mw.iam.config; -import java.util.Date; -import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.FixedRateTask; import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import it.infn.mw.iam.api.aup.AupService; import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.config.lifecycle.LifecycleProperties; +import it.infn.mw.iam.config.oidc.OpenidFederationProperties; import it.infn.mw.iam.core.gc.GarbageCollector; import it.infn.mw.iam.core.lifecycle.ExpiredAccountsHandler; import it.infn.mw.iam.core.web.aup.AupReminderTask; import it.infn.mw.iam.core.web.wellknown.IamWellKnownInfoProvider; import it.infn.mw.iam.notification.NotificationDeliveryTask; +import it.infn.mw.iam.notification.NotificationProperties; import it.infn.mw.iam.notification.service.NotificationStoreService; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; @Configuration @EnableScheduling -@Profile({"prod", "dev"}) public class TaskConfig implements SchedulingConfigurer { public static final Logger LOG = LoggerFactory.getLogger(TaskConfig.class); @@ -57,35 +54,40 @@ public class TaskConfig implements SchedulingConfigurer { public static final long ONE_HOUR_MSEC = 60 * ONE_MINUTE_MSEC; public static final long ONE_DAY_MSEC = 24 * ONE_HOUR_MSEC; + private final TaskProperties taskProperties; + private final OpenidFederationProperties oidFedProperties; + private final NotificationProperties notificationProperties; + private final AupService aupService; + private NotificationStoreService notificationStoreService; private NotificationDeliveryTask deliveryTask; private LifecycleProperties lifecycleProperties; private ExpiredAccountsHandler expiredAccountsHandler; private AupReminderTask aupReminderTask; private ExecutorService taskScheduler; - private IamClientRepository clientRepo; private ClientService clientService; private GarbageCollector garbageCollector; - @Value("${notification.disable}") - boolean notificationDisabled; - - @Value("${notification.taskDelay}") - long notificationTaskPeriodMsec; - - public TaskConfig(NotificationStoreService notificationStoreService, - NotificationDeliveryTask deliveryTask, LifecycleProperties lifecycleProperties, - ExpiredAccountsHandler expiredAccountsHandler, AupReminderTask aupReminderTask, - ExecutorService taskScheduler, IamClientRepository clientRepo, ClientService clientService, + public TaskConfig(TaskProperties taskProperties, + OpenidFederationProperties oidFedProperties, + NotificationProperties notificationProperties, + AupService aupService, + NotificationStoreService notificationStoreService, NotificationDeliveryTask deliveryTask, + LifecycleProperties lifecycleProperties, ExpiredAccountsHandler expiredAccountsHandler, + AupReminderTask aupReminderTask, ExecutorService taskScheduler, + ClientService clientService, GarbageCollector garbageCollector) { + this.taskProperties = taskProperties; + this.oidFedProperties = oidFedProperties; + this.notificationProperties = notificationProperties; + this.aupService = aupService; this.notificationStoreService = notificationStoreService; this.deliveryTask = deliveryTask; this.lifecycleProperties = lifecycleProperties; this.expiredAccountsHandler = expiredAccountsHandler; this.aupReminderTask = aupReminderTask; this.taskScheduler = taskScheduler; - this.clientRepo = clientRepo; this.clientService = clientService; this.garbageCollector = garbageCollector; } @@ -97,61 +99,61 @@ public void logWellKnownCacheEviction() { LOG.debug("well-known config cache evicted"); } - @Scheduled(fixedDelayString = "${task.tokenCleanupPeriodMsec}", initialDelay = TEN_MINUTES_MSEC) - public void clearExpiredTokens() { - - garbageCollector.clearExpiredAccessTokens(100); - garbageCollector.clearExpiredRefreshTokens(100); - garbageCollector.clearOrphanedAuthenticationHolder(100); - } - - @Scheduled(fixedDelayString = "${task.approvalCleanupPeriodMsec}", - initialDelay = TEN_MINUTES_MSEC) - public void clearExpiredSites() { - - garbageCollector.clearExpiredApprovedSites(100); - } - @Scheduled(fixedDelay = THIRTY_SECONDS_MSEC, initialDelay = TEN_MINUTES_MSEC) public void clearExpiredNotifications() { notificationStoreService.clearExpiredNotifications(); } - @Scheduled(fixedDelayString = "${task.deviceCodeCleanupPeriodMsec}", - initialDelay = TEN_MINUTES_MSEC) - public void clearExpiredDeviceCodes() { - - garbageCollector.clearExpiredDeviceCodes(100); + @Override + public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(taskScheduler); + schedulePendingNotificationsDelivery(taskRegistrar); + scheduledExpiredAccountsTask(taskRegistrar); + scheduleGarbageCollectorTasks(taskRegistrar); + scheduleExpiredClientsTask(taskRegistrar); + scheduleAupRemindersTask(taskRegistrar); } - @Scheduled(fixedRateString = "${task.aupReminder:14400}", timeUnit = TimeUnit.SECONDS, - initialDelay = ONE_MINUTE_MSEC) - public void scheduledAupRemindersTask() { + private void scheduleAupRemindersTask(ScheduledTaskRegistrar taskRegistrar) { - aupReminderTask.sendAupReminders(); - } + Runnable aupRemindersTask = () -> { + aupReminderTask.sendAupReminders(); + }; - @Scheduled(fixedDelay = ONE_DAY_MSEC, initialDelay = TEN_MINUTES_MSEC) - public void disableExpiredClients() { - List clients = clientRepo.findActiveClientsExpiredBefore(new Date()); - for (ClientDetailsEntity client : clients) { - clientService.updateClientStatus(client, false, "expired_client_task"); + if (aupService.findAup().isEmpty()) { + LOG.info("Period AUP reminders delivery task will NOT be scheduled, since " + + "an AUP is not defined"); + return; } + if (taskProperties.getAupReminder() < 0) { + LOG.info("Period AUP reminders delivery task will NOT be scheduled, since " + + "task.aupReminders is a negative number: {}", taskProperties.getAupReminder()); + return; + } + LOG.info("Scheduling AUP reminders delivery task to run every {} sec", + TimeUnit.MILLISECONDS.toSeconds(taskProperties.getAupReminder())); + + taskRegistrar.addFixedRateTask( + new FixedRateTask(aupRemindersTask, taskProperties.getAupReminder(), ONE_MINUTE_MSEC)); } public void schedulePendingNotificationsDelivery(final ScheduledTaskRegistrar taskRegistrar) { - if (notificationTaskPeriodMsec < 0) { + if (notificationProperties.getDisable()) { + LOG.info("Period notification delivery task is disabled"); + return; + } + if (notificationProperties.getTaskDelay() < 0) { LOG.info("Period notification delivery task will NOT be scheduled, since " - + "notificationTaskPeriodMsec is a negative number: {}", notificationTaskPeriodMsec); + + "notificationTaskPeriodMsec is a negative number: {}", notificationProperties.getTaskDelay()); return; } LOG.info("Scheduling pending notification delivery task to run every {} sec", - TimeUnit.MILLISECONDS.toSeconds(notificationTaskPeriodMsec)); + TimeUnit.MILLISECONDS.toSeconds(notificationProperties.getTaskDelay())); - taskRegistrar.addFixedRateTask(deliveryTask, notificationTaskPeriodMsec); + taskRegistrar.addFixedRateTask(deliveryTask, notificationProperties.getTaskDelay()); } public void scheduledExpiredAccountsTask(final ScheduledTaskRegistrar taskRegistrar) { @@ -165,10 +167,71 @@ public void scheduledExpiredAccountsTask(final ScheduledTaskRegistrar taskRegist } } - @Override - public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) { - taskRegistrar.setScheduler(taskScheduler); - schedulePendingNotificationsDelivery(taskRegistrar); - scheduledExpiredAccountsTask(taskRegistrar); + private void scheduleExpiredClientsTask(ScheduledTaskRegistrar taskRegistrar) { + + Runnable expiredClientsTask = () -> { + clientService.disableExpiredClients(); + }; + + if (oidFedProperties.isEnabled()) { + LOG.info("Scheduling disable expired clients task to run every {} sec", + TimeUnit.MILLISECONDS.toSeconds(ONE_DAY_MSEC)); + taskRegistrar + .addFixedRateTask(new FixedRateTask(expiredClientsTask, ONE_DAY_MSEC, TEN_MINUTES_MSEC)); + } + } + + private void scheduleGarbageCollectorTasks(ScheduledTaskRegistrar taskRegistrar) { + + Runnable expiredTokensTask = () -> { + garbageCollector.clearExpiredAccessTokens(100); + garbageCollector.clearExpiredRefreshTokens(100); + garbageCollector.clearOrphanedAuthenticationHolder(100); + }; + Runnable expiredApprovedSitesTask = () -> { + garbageCollector.clearExpiredApprovedSites(100); + }; + Runnable expiredDeviceCodesTask = () -> { + garbageCollector.clearExpiredDeviceCodes(100); + }; + + // Expired Tokens Task + if (taskProperties.getTokenCleanupPeriodMsec() < 0) { + LOG.info( + "Period expired token cleanup task will NOT be scheduled, since " + + "task.tokenCleanupPeriodMsec is a negative number: {}", + taskProperties.getTokenCleanupPeriodMsec()); + } else { + LOG.info("Scheduling expired token cleanup task to run every {} sec", + TimeUnit.MILLISECONDS.toSeconds(taskProperties.getTokenCleanupPeriodMsec())); + taskRegistrar.addFixedRateTask(new FixedRateTask(expiredTokensTask, + taskProperties.getTokenCleanupPeriodMsec(), TEN_MINUTES_MSEC)); + } + + // Expired Approved Sites Task + if (taskProperties.getApprovalCleanupPeriodMsec() < 0) { + LOG.info( + "Period approved sites cleanup task will NOT be scheduled, since " + + "task.approvalCleanupPeriodMsec is a negative number: {}", + taskProperties.getTokenCleanupPeriodMsec()); + } else { + LOG.info("Scheduling approved sites cleanup task to run every {} sec", + TimeUnit.MILLISECONDS.toSeconds(taskProperties.getApprovalCleanupPeriodMsec())); + taskRegistrar.addFixedRateTask(new FixedRateTask(expiredApprovedSitesTask, + taskProperties.getApprovalCleanupPeriodMsec(), TEN_MINUTES_MSEC)); + } + + // Expired Device Codes Task + if (taskProperties.getDeviceCodeCleanupPeriodMsec() < 0) { + LOG.info( + "Period device codes cleanup task will NOT be scheduled, since " + + "task.deviceCodeCleanupPeriodMsec is a negative number: {}", + taskProperties.getTokenCleanupPeriodMsec()); + } else { + LOG.info("Scheduling device codes cleanup task to run every {} sec", + TimeUnit.MILLISECONDS.toSeconds(taskProperties.getDeviceCodeCleanupPeriodMsec())); + taskRegistrar.addFixedRateTask(new FixedRateTask(expiredDeviceCodesTask, + taskProperties.getDeviceCodeCleanupPeriodMsec(), TEN_MINUTES_MSEC)); + } } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskProperties.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskProperties.java new file mode 100644 index 0000000000..72e9fdf1ec --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskProperties.java @@ -0,0 +1,72 @@ +/** + * 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.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "task") +public class TaskProperties { + + private long aupReminder; + private long tokenCleanupPeriodMsec; + private long approvalCleanupPeriodMsec; + private long deviceCodeCleanupPeriodMsec; + private long wellKnownCacheCleanupPeriodSecs; + + public long getAupReminder() { + return aupReminder; + } + + public void setAupReminder(long aupReminder) { + this.aupReminder = aupReminder; + } + + public long getTokenCleanupPeriodMsec() { + return tokenCleanupPeriodMsec; + } + + public void setTokenCleanupPeriodMsec(long tokenCleanupPeriodMsec) { + this.tokenCleanupPeriodMsec = tokenCleanupPeriodMsec; + } + + public long getApprovalCleanupPeriodMsec() { + return approvalCleanupPeriodMsec; + } + + public void setApprovalCleanupPeriodMsec(long approvalCleanupPeriodMsec) { + this.approvalCleanupPeriodMsec = approvalCleanupPeriodMsec; + } + + public long getDeviceCodeCleanupPeriodMsec() { + return deviceCodeCleanupPeriodMsec; + } + + public void setDeviceCodeCleanupPeriodMsec(long deviceCodeCleanupPeriodMsec) { + this.deviceCodeCleanupPeriodMsec = deviceCodeCleanupPeriodMsec; + } + + public long getWellKnownCacheCleanupPeriodSecs() { + return wellKnownCacheCleanupPeriodSecs; + } + + public void setWellKnownCacheCleanupPeriodSecs(long wellKnownCacheCleanupPeriodSecs) { + this.wellKnownCacheCleanupPeriodSecs = wellKnownCacheCleanupPeriodSecs; + } + + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/UIConfiguration.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/UIConfiguration.java new file mode 100644 index 0000000000..fda582595b --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/UIConfiguration.java @@ -0,0 +1,33 @@ +/** + * 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.config; + +import java.util.Set; + +public class UIConfiguration { + + private Set jsFiles; + + public Set getJsFiles() { + return jsFiles; + } + + public void setJsFiles(Set jsFiles) { + this.jsFiles = jsFiles; + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OidcConfiguration.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OidcConfiguration.java index e67b2dfb37..dde6f898d8 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OidcConfiguration.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OidcConfiguration.java @@ -15,26 +15,11 @@ */ package it.infn.mw.iam.config.oidc; -import java.time.Clock; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; -import org.apache.http.client.HttpClient; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.oauth2.model.RegisteredClient; -import org.mitre.openid.connect.client.OIDCAuthenticationProvider; -import org.mitre.openid.connect.client.UserInfoFetcher; -import org.mitre.openid.connect.client.service.AuthRequestOptionsService; -import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder; -import org.mitre.openid.connect.client.service.ClientConfigurationService; -import org.mitre.openid.connect.client.service.IssuerService; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.client.service.impl.DynamicServerConfigurationService; -import org.mitre.openid.connect.client.service.impl.PlainAuthRequestUrlBuilder; -import org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService; -import org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -42,14 +27,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.core.env.Environment; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; import com.google.common.collect.Sets; @@ -58,21 +41,33 @@ import it.infn.mw.iam.authn.ExternalAuthenticationSuccessHandler; import it.infn.mw.iam.authn.InactiveAccountAuthenticationHander; import it.infn.mw.iam.authn.common.config.AuthenticationValidator; +import it.infn.mw.iam.authn.oidc.AuthRequestUrlBuilder; import it.infn.mw.iam.authn.oidc.DefaultOidcTokenRequestor; -import it.infn.mw.iam.authn.oidc.DefaultRestTemplateFactory; import it.infn.mw.iam.authn.oidc.OidcAuthenticationProvider; import it.infn.mw.iam.authn.oidc.OidcClientFilter; import it.infn.mw.iam.authn.oidc.OidcExceptionMessageHelper; import it.infn.mw.iam.authn.oidc.OidcTokenRequestor; +import it.infn.mw.iam.authn.oidc.PlainAuthRequestUrlBuilder; +import it.infn.mw.iam.authn.oidc.RegisteredClient; import it.infn.mw.iam.authn.oidc.RestTemplateFactory; -import it.infn.mw.iam.authn.oidc.service.NullClientConfigurationService; -import it.infn.mw.iam.authn.oidc.service.OidcAccountProvisioningService; +import it.infn.mw.iam.authn.oidc.configuration.ClientConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.NullClientConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.StaticClientConfigurationService; +import it.infn.mw.iam.authn.oidc.mapper.OidcAuthoritiesMapper; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; +import it.infn.mw.iam.authn.oidc.provisioning.OidcAccountProvisioningService; +import it.infn.mw.iam.authn.oidc.userinfo.UserInfoFetcher; import it.infn.mw.iam.authn.util.SessionTimeoutHelper; -import it.infn.mw.iam.core.IamThirdPartyIssuerService; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.SymmetricKeyJWTValidatorCacheService; +import it.infn.mw.iam.core.oidc.service.IamThirdPartyIssuerService; +import it.infn.mw.iam.core.oidc.service.IssuerService; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; @Configuration +@Profile("oidc") @EnableConfigurationProperties(IamOidcJITAccountProvisioningProperties.class) public class OidcConfiguration { @@ -82,61 +77,36 @@ public class OidcConfiguration { public static final String DEFINE_ME_PLEASE = "define_me_please"; @Bean - FilterRegistrationBean disabledAutomaticOidcFilterRegistration( - OidcClientFilter f) { - - FilterRegistrationBean b = new FilterRegistrationBean<>(f); - b.setEnabled(false); - return b; - } - - @Bean(name = "OIDCAuthenticationFilter") OidcClientFilter openIdConnectAuthenticationFilterCanl(OidcTokenRequestor tokenRequestor, @Qualifier("OIDCAuthenticationManager") AuthenticationManager oidcAuthenticationManager, - @Qualifier("OIDCExternalAuthenticationSuccessHandler") AuthenticationSuccessHandler successHandler, - @Qualifier("OIDCExternalAuthenticationFailureHandler") AuthenticationFailureHandler failureHandler, - IssuerService issuerService, ServerConfigurationService serverConfigurationService, - ClientConfigurationService clientConfigurationService, - AuthRequestUrlBuilder authRequestUrlBuilder, - AuthRequestOptionsService authRequestOptionsService, JWKSetCacheService validationServices) { - - OidcClientFilter filter = new OidcClientFilter(); - filter.setAuthenticationManager(oidcAuthenticationManager); - filter.setIssuerService(issuerService); - filter.setServerConfigurationService(serverConfigurationService); - filter.setClientConfigurationService(clientConfigurationService); - filter.setAuthRequestOptionsService(authRequestOptionsService); - filter.setAuthRequestUrlBuilder(authRequestUrlBuilder); - filter.setAuthenticationSuccessHandler(successHandler); - filter.setAuthenticationFailureHandler(failureHandler); - filter.setValidationServices(validationServices); - filter.setTokenRequestor(tokenRequestor); - - return filter; - } + AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler, + ServerConfigurationService serverConfigService, + ClientConfigurationService clientConfigService, + SymmetricKeyJWTValidatorCacheService symmetricKeyJwtValidatorCacheService, + JwkSetCacheService validationServices, IssuerService issuerService, + AuthRequestUrlBuilder authRequestUrlBuilder, Environment env) { - @Bean - @Profile("!canl") - RestTemplateFactory restTemplateFactory() { - - return new DefaultRestTemplateFactory(new HttpComponentsClientHttpRequestFactory()); + return new OidcClientFilter(oidcAuthenticationManager, serverConfigService, clientConfigService, + symmetricKeyJwtValidatorCacheService, validationServices, tokenRequestor, issuerService, + authRequestUrlBuilder, env); } @Bean - @Profile("canl") - RestTemplateFactory canlRestTemplateFactory( - @Qualifier("canlRequestFactory") ClientHttpRequestFactory rf) { + FilterRegistrationBean disabledAutomaticOidcFilterRegistration( + OidcClientFilter f) { - return new DefaultRestTemplateFactory(rf); + FilterRegistrationBean b = new FilterRegistrationBean<>(f); + b.setEnabled(false); + return b; } - @Bean(name = "OIDCExternalAuthenticationFailureHandler") + @Bean AuthenticationFailureHandler failureHandler() { return new ExternalAuthenticationFailureHandler(new OidcExceptionMessageHelper()); } - @Bean(name = "OIDCExternalAuthenticationSuccessHandler") + @Bean AuthenticationSuccessHandler successHandler(AuthenticationSuccessHandlerHelper helper) { return new ExternalAuthenticationSuccessHandler("/", helper); @@ -144,26 +114,22 @@ AuthenticationSuccessHandler successHandler(AuthenticationSuccessHandlerHelper h @Bean(name = "OIDCAuthenticationManager") AuthenticationManager authenticationManager( - OIDCAuthenticationProvider oidcAuthenticationProvider) { + OidcAuthenticationProvider oidcAuthenticationProvider) { return new ProviderManager(Arrays.asList(oidcAuthenticationProvider)); } @Bean - OIDCAuthenticationProvider openIdConnectAuthenticationProvider(Clock clock, - UserInfoFetcher userInfoFetcher, AuthenticationValidator validator, - SessionTimeoutHelper timeoutHelper, + OidcAuthenticationProvider openIdConnectAuthenticationProvider( + AuthenticationValidator authnValidator, + SessionTimeoutHelper sessionTimeoutHelper, IamAccountRepository accountRepo, InactiveAccountAuthenticationHander inactiveAccountHandler, - IamTotpMfaRepository totpMfaRepository, IamAccountRepository accountRepo, - IamOidcJITAccountProvisioningProperties jitProperties, - OidcAccountProvisioningService oidcProvisioningService) { + IamTotpMfaRepository totpMfaRepository, IamOidcJITAccountProvisioningProperties jitProperties, + OidcAccountProvisioningService oidcProvisioningService, UserInfoFetcher userInfoFetcher, + OidcAuthoritiesMapper authoritiesMapper) { - OidcAuthenticationProvider provider = - new OidcAuthenticationProvider(validator, timeoutHelper, accountRepo, - inactiveAccountHandler, totpMfaRepository, jitProperties, oidcProvisioningService); - - provider.setUserInfoFetcher(userInfoFetcher); - - return provider; + return new OidcAuthenticationProvider(authnValidator, sessionTimeoutHelper, accountRepo, + inactiveAccountHandler, totpMfaRepository, jitProperties, oidcProvisioningService, + userInfoFetcher, authoritiesMapper); } @Bean @@ -172,21 +138,6 @@ IssuerService oidcIssuerService() { return new IamThirdPartyIssuerService(); } - @Bean - @Profile("!canl") - ServerConfigurationService dynamicServerConfiguration() { - - return new DynamicServerConfigurationService(); - } - - @Bean - @Profile("canl") - ServerConfigurationService canlDynamicServerConfiguration( - @Qualifier("canlHttpClient") HttpClient client) { - - return new DynamicServerConfigurationService(client); - } - public boolean configuredProvider(OidcProvider provider) { return !Strings.isNullOrEmpty(provider.getClient().getClientId()); } @@ -210,17 +161,7 @@ ClientConfigurationService oidcClientConfiguration(OidcValidatedProviders provid return new NullClientConfigurationService(); } - - StaticClientConfigurationService config = new StaticClientConfigurationService(); - config.setClients(clients); - - return config; - } - - @Bean - AuthRequestOptionsService authOptions() { - - return new StaticAuthRequestOptionsService(); + return new StaticClientConfigurationService(clients); } @Bean @@ -231,11 +172,12 @@ AuthRequestUrlBuilder authRequestBuilder() { @Bean UserInfoFetcher userInfoFetcher() { - return new UserInfoFetcher(); + + return new UserInfoFetcher(HttpClientBuilder.create().useSystemProperties().build()); } @Bean - OidcTokenRequestor tokenRequestor(RestTemplateFactory restTemplateFactory, ObjectMapper mapper) { - return new DefaultOidcTokenRequestor(restTemplateFactory, mapper); + OidcTokenRequestor tokenRequestor(RestTemplateFactory restTemplateFactory) { + return new DefaultOidcTokenRequestor(restTemplateFactory); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OpenidFederationProperties.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OpenidFederationProperties.java index 815dbe5dd8..c2de9db633 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OpenidFederationProperties.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/oidc/OpenidFederationProperties.java @@ -24,8 +24,20 @@ @Configuration public class OpenidFederationProperties { + private boolean enabled; + private List trustAnchors; + private EntityConfigurationProperties entityConfiguration = new EntityConfigurationProperties(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public List getTrustAnchors() { return trustAnchors; } @@ -34,8 +46,6 @@ public void setTrustAnchors(List trustAnchors) { this.trustAnchors = trustAnchors; } - private EntityConfigurationProperties entityConfiguration = new EntityConfigurationProperties(); - public EntityConfigurationProperties getEntityConfiguration() { return entityConfiguration; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java index 37c8cb621d..a9a7624c0e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java @@ -20,8 +20,6 @@ import java.time.Clock; -import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; -import org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -32,6 +30,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; @@ -40,8 +39,10 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import it.infn.mw.iam.authn.jwt.JwtBearerClientAssertionTokenEndpointFilter; import it.infn.mw.iam.config.IamProperties; -import it.infn.mw.iam.core.client.ClientUserDetailsService; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.core.jwt.ClientKeyCacheService; import it.infn.mw.iam.core.oauth.assertion.IAMJWTBearerAuthenticationProvider; @SuppressWarnings("deprecation") @@ -56,7 +57,7 @@ public class IamTokenEndointSecurityConfig extends WebSecurityConfigurerAdapter @Autowired @Qualifier("clientUserDetailsService") - private ClientUserDetailsService userDetailsService; + private UserDetailsService userDetailsService; @Autowired private Clock clock; @@ -67,15 +68,17 @@ public class IamTokenEndointSecurityConfig extends WebSecurityConfigurerAdapter @Autowired private IamProperties iamProperties; + @Autowired + private IamClientDetailsService clientService; + @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(userDetailsService) - .passwordEncoder(NoOpPasswordEncoder.getInstance()); + auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance()); } @Bean - public ClientCredentialsTokenEndpointFilter ccFilter() throws Exception { + ClientCredentialsTokenEndpointFilter ccFilter() throws Exception { ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter(TOKEN_ENDPOINT); filter.setAllowOnlyPost(true); @@ -84,13 +87,13 @@ public ClientCredentialsTokenEndpointFilter ccFilter() throws Exception { } @Bean - public JWTBearerClientAssertionTokenEndpointFilter jwtBearerFilter() { + JwtBearerClientAssertionTokenEndpointFilter jwtBearerFilter() { - JWTBearerClientAssertionTokenEndpointFilter filter = - new JWTBearerClientAssertionTokenEndpointFilter(new AntPathRequestMatcher(TOKEN_ENDPOINT)); + JwtBearerClientAssertionTokenEndpointFilter filter = + new JwtBearerClientAssertionTokenEndpointFilter(new AntPathRequestMatcher(TOKEN_ENDPOINT)); - IAMJWTBearerAuthenticationProvider authProvider = new IAMJWTBearerAuthenticationProvider(clock, - iamProperties, userDetailsService.getClientDetailsService(), validators); + IAMJWTBearerAuthenticationProvider authProvider = + new IAMJWTBearerAuthenticationProvider(clock, iamProperties, clientService, validators); filter.setAuthenticationManager(new ProviderManager(singletonList(authProvider))); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamWebSecurityConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamWebSecurityConfig.java index ecfacff99a..8868269753 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamWebSecurityConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamWebSecurityConfig.java @@ -24,7 +24,6 @@ import javax.servlet.RequestDispatcher; -import org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -64,6 +63,7 @@ import it.infn.mw.iam.authn.CheckMultiFactorIsEnabledSuccessHandler; import it.infn.mw.iam.authn.ExternalAuthenticationHintService; import it.infn.mw.iam.authn.HintAwareAuthenticationEntryPoint; +import it.infn.mw.iam.authn.jwt.JwtBearerClientAssertionTokenEndpointFilter; import it.infn.mw.iam.authn.multi_factor_authentication.ExtendedAuthenticationFilter; import it.infn.mw.iam.authn.multi_factor_authentication.ExtendedHttpServletRequestFilter; import it.infn.mw.iam.authn.multi_factor_authentication.MultiFactorVerificationFilter; @@ -103,11 +103,9 @@ public static class UserLoginConfig extends WebSecurityConfigurerAdapter { private OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler; @Autowired - @Qualifier("mitreAuthzRequestFilter") private GenericFilterBean authorizationRequestFilter; @Autowired - @Qualifier("iamUserDetailsService") private UserDetailsService iamUserDetailsService; @Autowired @@ -219,12 +217,12 @@ protected void configure(final HttpSecurity http) throws Exception { } @Bean - public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler() { + OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler() { return new OAuth2WebSecurityExpressionHandler(); } @Bean - public AuthenticationSuccessHandlerHelper authenticationSuccessHandlerHelper() { + AuthenticationSuccessHandlerHelper authenticationSuccessHandlerHelper() { return new AuthenticationSuccessHandlerHelper(accountUtils, iamBaseUrl, aupSignatureCheckService, accountRepo); } @@ -257,7 +255,6 @@ public static class RegistrationConfig extends WebSecurityConfigurerAdapter { private UserLoginConfig userLoginConfig; private IamProperties iamProperties; - @Autowired public RegistrationConfig(UserLoginConfig userLoginConfig, IamProperties iamProperties) { this.userLoginConfig = userLoginConfig; this.iamProperties = iamProperties; @@ -329,13 +326,13 @@ protected void configure(HttpSecurity http) throws Exception { @Configuration @Order(105) + @Profile("oidc") public static class ExternalOidcLogin extends WebSecurityConfigurerAdapter { @Value("${iam.baseUrl}") private String iamBaseUrl; @Autowired - @Qualifier("OIDCAuthenticationManager") private AuthenticationManager oidcAuthManager; @Autowired @@ -344,16 +341,12 @@ public static class ExternalOidcLogin extends WebSecurityConfigurerAdapter { @Autowired private IamProperties iamProperties; + @Autowired + @Qualifier("OIDCAuthenticationFilter") private OidcClientFilter oidcFilter; - private UserLoginConfig userLoginConfig; @Autowired - public ExternalOidcLogin( - @Qualifier("OIDCAuthenticationFilter") OidcClientFilter oidcClientFilter, - UserLoginConfig userLoginConfig) { - this.oidcFilter = oidcClientFilter; - this.userLoginConfig = userLoginConfig; - } + private UserLoginConfig userLoginConfig; @Override public AuthenticationManager authenticationManagerBean() throws Exception { @@ -450,10 +443,10 @@ public void configure(final WebSecurity builder) throws Exception { */ @Configuration @Order(102) + @Profile("mfa") public static class MultiFactorConfigurationAdapter extends WebSecurityConfigurerAdapter { @Autowired - @Qualifier("MultiFactorVerificationFilter") private MultiFactorVerificationFilter multiFactorVerificationFilter; public AuthenticationEntryPoint mfaAuthenticationEntryPoint() { @@ -483,12 +476,12 @@ public static class IntrospectEndpointAuthorizationConfig extends WebSecurityCon private OAuth2AuthenticationEntryPoint authenticationEntryPoint; private UserDetailsService userDetailsService; - private JWTBearerClientAssertionTokenEndpointFilter bearerFilter; + private JwtBearerClientAssertionTokenEndpointFilter bearerFilter; public IntrospectEndpointAuthorizationConfig( OAuth2AuthenticationEntryPoint authenticationEntryPoint, @Qualifier("clientUserDetailsService") UserDetailsService userDetailsService, - JWTBearerClientAssertionTokenEndpointFilter bearerFilter) { + JwtBearerClientAssertionTokenEndpointFilter bearerFilter) { this.authenticationEntryPoint = authenticationEntryPoint; this.userDetailsService = userDetailsService; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java index 610f90b0af..057b949614 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java @@ -47,7 +47,6 @@ public class MitreSecurityConfig { public static class MitreApisEndpointAuthorizationConfig extends WebSecurityConfigurerAdapter { @Autowired - @Qualifier("resourceServerFilter") private OAuth2AuthenticationProcessingFilter resourceFilter; @Autowired diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/AuthenticationHolderService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/AuthenticationHolderService.java new file mode 100644 index 0000000000..bd98eed85f --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/AuthenticationHolderService.java @@ -0,0 +1,36 @@ +/** + * 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; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.oauth2.provider.OAuth2Authentication; + +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + +@SuppressWarnings("deprecation") +public interface AuthenticationHolderService { + + AuthenticationHolderEntity create(ClientDetailsEntity client, OAuth2Authentication authn); + + AuthenticationHolderEntity save(AuthenticationHolderEntity code); + + void remove(AuthenticationHolderEntity holder); + + Page getOrphanedAuthenticationHolders(Pageable page); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamAuthenticationHolderEntityService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/IamAuthenticationHolderService.java similarity index 63% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/IamAuthenticationHolderEntityService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/IamAuthenticationHolderService.java index 639f8923a2..9e564a98f0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamAuthenticationHolderEntityService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/IamAuthenticationHolderService.java @@ -15,40 +15,36 @@ */ package it.infn.mw.iam.core; -import java.util.List; import java.util.Map; -import org.mitre.data.PageCriteria; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.service.AuthenticationHolderEntityService; -import org.springframework.context.annotation.Primary; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.stereotype.Service; import it.infn.mw.iam.authn.AbstractExternalAuthenticationToken; import it.infn.mw.iam.authn.ExternalAuthenticationInfoBuilder; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamAuthenticationHolderRepository; @SuppressWarnings("deprecation") -@Service("authenticationHolderEntityService") -@Primary -public class IamAuthenticationHolderEntityService implements AuthenticationHolderEntityService { +@Service +public class IamAuthenticationHolderService implements AuthenticationHolderService { - - final AuthenticationHolderRepository repo; + final IamAuthenticationHolderRepository repo; final ExternalAuthenticationInfoBuilder mapBuilder; - public IamAuthenticationHolderEntityService(AuthenticationHolderRepository repo, + public IamAuthenticationHolderService(IamAuthenticationHolderRepository repo, ExternalAuthenticationInfoBuilder mapBuilder) { this.repo = repo; this.mapBuilder = mapBuilder; } @Override - public AuthenticationHolderEntity create(OAuth2Authentication authn) { + public AuthenticationHolderEntity create(ClientDetailsEntity client, OAuth2Authentication authn) { - AuthenticationHolderEntity holder = new AuthenticationHolderEntity(); - holder.setAuthentication(authn); + AuthenticationHolderEntity holder = new AuthenticationHolderEntity(client, authn); if (authn.getUserAuthentication() != null && authn.getUserAuthentication() instanceof AbstractExternalAuthenticationToken) { @@ -58,27 +54,27 @@ public AuthenticationHolderEntity create(OAuth2Authentication authn) { Map info = token.buildAuthnInfoMap(mapBuilder); holder.getUserAuth().getAdditionalInfo().putAll(info); - } return repo.save(holder); - } @Override public void remove(AuthenticationHolderEntity holder) { - repo.remove(holder); + repo.delete(holder); } @Override - public List getOrphanedAuthenticationHolders() { + public Page getOrphanedAuthenticationHolders(Pageable page) { - return repo.getOrphanedAuthenticationHolders(); + return repo.getOrphans(page); } @Override - public List getOrphanedAuthenticationHolders(PageCriteria page) { - return repo.getOrphanedAuthenticationHolders(page); + public AuthenticationHolderEntity save(AuthenticationHolderEntity code) { + + return repo.save(code); } + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamTokenService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/IamTokenService.java index 4e6862f02d..7cf638a3b5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamTokenService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/IamTokenService.java @@ -15,21 +15,21 @@ */ package it.infn.mw.iam.core; +import static it.infn.mw.iam.core.oauth.IamOAuth2ParameterNames.CODE_CHALLENGE; +import static it.infn.mw.iam.core.oauth.IamOAuth2ParameterNames.CODE_CHALLENGE_METHOD; +import static it.infn.mw.iam.core.oauth.IamOAuth2ParameterNames.CODE_VERIFIER; import static java.lang.String.valueOf; import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_VERIFIER; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.text.ParseException; import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -37,16 +37,6 @@ import java.util.Set; import java.util.UUID; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.model.PKCEAlgorithm; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.service.OIDCTokenService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; @@ -63,35 +53,59 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.google.common.hash.Hashing; +import com.nimbusds.jose.Algorithm; +import com.nimbusds.jose.JWEHeader; +import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.EncryptedJWT; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTClaimsSet.Builder; import com.nimbusds.jwt.PlainJWT; import com.nimbusds.jwt.SignedJWT; import it.infn.mw.iam.api.client.service.ClientService; +import it.infn.mw.iam.api.common.error.NoSuchAccountError; import it.infn.mw.iam.audit.events.tokens.AccessTokenIssuedEvent; import it.infn.mw.iam.audit.events.tokens.RefreshTokenIssuedEvent; import it.infn.mw.iam.authn.util.Authorities; import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.jwt.ClientKeyCacheService; +import it.infn.mw.iam.core.jwt.JwtEncryptionAndDecryptionService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.SymmetricKeyJWTValidatorCacheService; +import it.infn.mw.iam.core.oauth.IamOAuth2ParameterNames; import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamAuthenticationHolderRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; +import it.infn.mw.iam.util.IdTokenHashUtils; @SuppressWarnings("deprecation") @Service public class IamTokenService implements OAuth2TokenEntityService { + record CodeChallenge(String code, PKCEAlgorithm method, String verifier) { + } + public static final String EXPIRES_IN_KEY = "expires_in"; public static final String INVALID_PARAMETER = "Value of 'expires_in' parameter is not valid"; @@ -106,26 +120,27 @@ public class IamTokenService implements OAuth2TokenEntityService { private final IamAuthenticationHolderRepository authenticationHolderRepo; private final ClientService clientService; private final IamAccountRepository accountRepository; - private final JWTSigningAndValidationService jwtSigningService; + private final JwtSigningAndValidationService jwtSigningService; private final TokenRevocationService revocationService; - private final OIDCTokenService connectTokenService; private final SystemScopeService scopeService; private final JWTProfileResolver profileResolver; private final ApplicationEventPublisher eventPublisher; private final IamProperties iamProperties; private final ScopeFilter scopeFilter; private final Clock clock; + private final ClientKeyCacheService encrypters; + private final SymmetricKeyJWTValidatorCacheService symmetricCacheService; private final MessageDigest sha256Digest; public IamTokenService(Clock clock, IamOAuthAccessTokenRepository accessTokenRepo, IamOAuthRefreshTokenRepository refreshTokenRepo, IamAuthenticationHolderRepository authenticationHolderRepo, ClientService clientService, - IamAccountRepository accountRepository, JWTSigningAndValidationService jwtSigningService, - TokenRevocationService revocationService, OIDCTokenService connectTokenService, - SystemScopeService scopeService, JWTProfileResolver profileResolver, - ApplicationEventPublisher eventPublisher, IamProperties iamProperties, - ScopeFilter scopeFilter) throws NoSuchAlgorithmException { + IamAccountRepository accountRepository, JwtSigningAndValidationService jwtSigningService, + TokenRevocationService revocationService, SystemScopeService scopeService, + JWTProfileResolver profileResolver, ApplicationEventPublisher eventPublisher, + IamProperties iamProperties, ScopeFilter scopeFilter, ClientKeyCacheService encrypters, + SymmetricKeyJWTValidatorCacheService symmetricCacheService) throws NoSuchAlgorithmException { this.accessTokenRepo = accessTokenRepo; this.refreshTokenRepo = refreshTokenRepo; @@ -134,13 +149,14 @@ public IamTokenService(Clock clock, IamOAuthAccessTokenRepository accessTokenRep this.accountRepository = accountRepository; this.jwtSigningService = jwtSigningService; this.revocationService = revocationService; - this.connectTokenService = connectTokenService; this.scopeService = scopeService; this.profileResolver = profileResolver; this.eventPublisher = eventPublisher; this.iamProperties = iamProperties; this.scopeFilter = scopeFilter; this.clock = clock; + this.encrypters = encrypters; + this.symmetricCacheService = symmetricCacheService; this.sha256Digest = MessageDigest.getInstance("SHA-256"); } @@ -148,7 +164,7 @@ public IamTokenService(Clock clock, IamOAuthAccessTokenRepository accessTokenRep @Override public Set getAllAccessTokensForUser(String id) { - Set results = Sets.newLinkedHashSet(); + Set results = new LinkedHashSet<>(); results.addAll(accessTokenRepo.findAccessTokensForUser(id)); return results; } @@ -156,37 +172,38 @@ public Set getAllAccessTokensForUser(String id) { @Override public Set getAllRefreshTokensForUser(String id) { - Set results = Sets.newLinkedHashSet(); + Set results = new LinkedHashSet<>(); results.addAll(refreshTokenRepo.findRefreshTokensForUser(id)); return results; } @Override - public void revokeAccessToken(OAuth2AccessTokenEntity accessToken) { + public OAuth2Authentication loadAuthentication(String accessTokenValue) + throws AuthenticationException { - revocationService.revokeAccessToken(accessToken); + return readAccessToken(accessTokenValue).getAuthenticationHolder().getAuthentication(); } - @Override - public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) { + private ClientDetailsEntity getClient(OAuth2Request request) { - revocationService.revokeRefreshToken(refreshToken); - } + ClientDetailsEntity client = clientService.findClientByClientId(request.getClientId()) + .orElseThrow(() -> new InvalidClientException("Client not found: " + request.getClientId())); - @Override - public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity accessToken) { + if (!client.isActive()) { + throw new InvalidClientException("Client is suspended: " + client.getClientId()); + } - AuthenticationHolderEntity ah = - authenticationHolderRepo.save(accessToken.getAuthenticationHolder()); - accessToken.setAuthenticationHolder(ah); - return accessTokenRepo.saveAndFlush(accessToken); + return client; } - @Override - public OAuth2Authentication loadAuthentication(String accessTokenValue) - throws AuthenticationException { + private Optional getAccountIfPresent(OAuth2Authentication authentication) { - return readAccessToken(accessTokenValue).getAuthenticationHolder().getAuthentication(); + Optional account = Optional.empty(); + if (!authentication.isClientOnly()) { + String username = authentication.getName(); + account = accountRepository.findByUsername(username); + } + return account; } @Override @@ -208,61 +225,66 @@ public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue) { public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) { validate(authentication); - OAuth2Request request = authentication.getOAuth2Request(); + ClientDetailsEntity client = getClient(request); + Optional account = getAccountIfPresent(authentication); - ClientDetailsEntity client = clientService.findClientByClientId(request.getClientId()) - .orElseThrow(() -> new InvalidClientException("Client not found: " + request.getClientId())); - - if (!client.isActive()) { - throw new InvalidClientException("Client is suspended: " + request.getClientId()); + Optional codeChallenge = getCodeChallenge(request); + if (codeChallenge.isPresent()) { + handleCodeChallenge(codeChallenge.get()); } - Optional account = Optional.empty(); - if (!authentication.isClientOnly()) { - String username = authentication.getName(); - account = accountRepository.findByUsername(username); - } + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(client, authentication); - if (hasCodeChallenge(request)) { - handleCodeChallenge(request); - } + Set authorizedScopes = + computeScopes(authentication.getOAuth2Request().getScope(), account); + authHolder.setScope(authorizedScopes); - Instant iat = clock.instant(); - AuthenticationHolderEntity authHolder = createAuthenticationHolder(authentication); - OAuth2AccessTokenEntity accessToken = new OAuth2AccessTokenEntity(); - accessToken.setClient(client); - accessToken.setScope(computeScopes(request, authentication)); - accessToken.setExpiration(computeExpiration(request.getRequestParameters(), client, iat)); - accessToken.setAuthenticationHolder(authHolder); + OAuth2AccessTokenEntity accessToken = + createAccessToken(client, authHolder, authentication, account, authorizedScopes); if (client.isAllowRefresh() && isRefreshTokenRequested(request.getGrantType(), accessToken.getScope())) { - accessToken.setRefreshToken(createRefreshToken(client, authHolder)); + OAuth2RefreshTokenEntity refreshToken = createRefreshToken(client, authHolder, accessToken); + accessToken.setRefreshToken(refreshToken); + authHolder.addRefreshToken(refreshToken); } - JWTProfile profile = profileResolver.resolveProfile(client.getScope()); + if (iamProperties.getClient().isTrackLastUsed()) { + clientService.useClient(client); + } + + AuthenticationHolderEntity savedAuthHolder = save(authHolder); + return savedAuthHolder.getAccessTokens().stream().findFirst().get(); + } + + private OAuth2AccessTokenEntity createAccessToken(ClientDetailsEntity client, + AuthenticationHolderEntity authHolder, OAuth2Authentication authentication, + Optional account, Set scopes) { + + Instant iat = clock.instant(); + Date expiration = computeExpiration(authentication.getOAuth2Request(), client, iat); + + OAuth2AccessTokenEntity accessToken = new OAuth2AccessTokenEntity(client, authHolder); + accessToken.setScope(scopes); + accessToken.setExpiration(expiration); + JWTProfile profile = profileResolver.resolveProfile(client.getScope()); JWTClaimsSet atClaims = profile.getAccessTokenBuilder().buildAccessToken(accessToken, authentication, account, iat); - accessToken.setJwt(signClaims(atClaims)); - accessToken.hashMe(); + accessToken.setTokenValue(signClaims(atClaims).serialize()); - if (request.getScope().contains(SystemScopeService.OPENID_SCOPE) && account.isPresent()) { + if (scopes.contains(SystemScopeService.OPENID_SCOPE) && account.isPresent()) { - accessToken.setIdToken(connectTokenService.createIdToken(client, request, Date.from(iat), - account.get().getUuid(), accessToken)); - } - - if (iamProperties.getClient().isTrackLastUsed()) { - clientService.useClient(client); + JWT idToken = createIdToken(client, authentication.getOAuth2Request(), Date.from(iat), + account.get().getUuid(), accessToken); + accessToken.setIdToken(idToken); } + authHolder.addAccessToken(accessToken); - OAuth2AccessTokenEntity savedAccessToken = saveAccessToken(accessToken); - eventPublisher.publishEvent(new AccessTokenIssuedEvent(this, savedAccessToken)); - return savedAccessToken; + return accessToken; } private boolean isRefreshTokenRequested(String grantType, Set scopes) { @@ -271,18 +293,10 @@ private boolean isRefreshTokenRequested(String grantType, Set scopes) { && !grantType.equals("client_credentials"); } - private AuthenticationHolderEntity createAuthenticationHolder( - OAuth2Authentication authentication) { - - AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); - authHolder.setAuthentication(authentication); - return authHolder; - } - - private Date computeExpiration(Map requestParameters, ClientDetailsEntity client, + private Date computeExpiration(OAuth2Request request, ClientDetailsEntity client, Instant tokenIssueInstant) { - Optional expiresIn = getExpiresIn(requestParameters); + Optional expiresIn = getExpiresIn(request); int validityInSeconds = 3600; if (client.getAccessTokenValiditySeconds() != null && client.getAccessTokenValiditySeconds() > 0) { @@ -295,8 +309,12 @@ private Date computeExpiration(Map requestParameters, ClientDeta tokenIssueInstant.plus(Math.min(expiresIn.get(), validityInSeconds), ChronoUnit.SECONDS)); } - private Optional getExpiresIn(Map requestParameters) { + private Optional getExpiresIn(OAuth2Request request) { + Map requestParameters = request.getRequestParameters(); + if (request.isRefresh()) { + requestParameters = request.getRefreshTokenRequest().getRequestParameters(); + } try { if (requestParameters.containsKey(EXPIRES_IN_KEY)) { return Optional.of(Integer.valueOf(requestParameters.get(EXPIRES_IN_KEY))); @@ -318,32 +336,70 @@ private SignedJWT signClaims(JWTClaimsSet claims) { return signedJWT; } - private Set computeScopes(OAuth2Request request, OAuth2Authentication authentication) { - - Set filteredScopes = scopeFilter.filterScopes(request.getScope(), authentication); - Set scopes = scopeService.fromStrings(filteredScopes); - scopes = scopeService.removeReservedScopes(scopes); - return scopeService.toStrings(scopes); + // private Set getScopes(AuthenticationHolderEntity authHolder, + // Optional account) { + // + // Set authorizedScopes = authHolder.getAuthentication().getOAuth2Request().getScope(); + // Set filteredScopes = new HashSet<>(); + // if (account.isPresent()) { + // filteredScopes.addAll(scopeFilter.filterScopes(authorizedScopes, account.get())); + // } else { + // filteredScopes.addAll(authorizedScopes); + // } + // return scopeService + // .toStrings(scopeService.removeReservedScopes(scopeService.fromStrings(filteredScopes))); + // } + + private Set refreshScopes(OAuth2Authentication authn, Optional account) { + + Set authorizedScopes = authn.getOAuth2Request().getScope(); + Set requestedScopes = authn.getOAuth2Request().getRefreshTokenRequest().getScope(); + // Set authorizedScopes = + // oldAuthHolder.getAuthentication().getOAuth2Request().getScope(); + // Set requestedScopes = + // newAuthHolder.getAuthentication().getOAuth2Request().getScope(); + if (requestedScopes == null || requestedScopes.isEmpty()) { + requestedScopes = new HashSet<>(authorizedScopes); + } else { + /* Check for up-scoping */ + if (!scopeService.scopesMatch(authorizedScopes, requestedScopes)) { + String errorMsg = "Up-scoping is not allowed."; + LOG.error(errorMsg); + throw new InvalidScopeException(errorMsg); + } + } + if (account.isPresent()) { + return scopeFilter.filterScopes(requestedScopes, account.get()); + } + return requestedScopes; } - private void handleCodeChallenge(OAuth2Request request) { - String challenge = valueOf(request.getExtensions().get(CODE_CHALLENGE)); - PKCEAlgorithm alg = - PKCEAlgorithm.parse(valueOf(request.getExtensions().get(CODE_CHALLENGE_METHOD))); + private Set computeScopes(Set requestedScopes, Optional account) { + + Set filteredScopes = new HashSet<>(); + if (account.isPresent()) { + filteredScopes.addAll(scopeFilter.filterScopes(requestedScopes, account.get())); + } else { + filteredScopes.addAll(requestedScopes); + } + return scopeService + .toStrings(scopeService.removeReservedScopes(scopeService.fromStrings(filteredScopes))); + } - String verifier = request.getRequestParameters().get(CODE_VERIFIER); + private void handleCodeChallenge(CodeChallenge codeChallenge) { - if (PKCEAlgorithm.plain.equals(alg)) { - if (challenge.equals(verifier)) { + if (PKCEAlgorithm.plain.equals(codeChallenge.method)) { + if (codeChallenge.code.equals(codeChallenge.verifier)) { LOG.debug("Plain code verified"); return; } throw new InvalidRequestException(CODE_VERIFICATION_ERROR); } - if (PKCEAlgorithm.S256.equals(alg)) { - String hash = Base64URL.encode(sha256Digest.digest(verifier.getBytes(US_ASCII))).toString(); - if (challenge.equals(hash)) { + if (PKCEAlgorithm.S256.equals(codeChallenge.method)) { + String hash = Base64URL.encode(sha256Digest.digest(codeChallenge.verifier.getBytes(US_ASCII))) + .toString(); + if (codeChallenge.code.equals(hash)) { LOG.debug("Hashed code verified"); return; } @@ -352,9 +408,27 @@ private void handleCodeChallenge(OAuth2Request request) { throw new InvalidRequestException(UNSUPPORTED_CODE_CHALLENGE_METHOD_ERROR); } - private boolean hasCodeChallenge(OAuth2Request request) { + private Optional getCodeChallenge(OAuth2Request request) { - return request.getExtensions().containsKey(CODE_CHALLENGE); + if (request.getRequestParameters().containsKey(CODE_CHALLENGE) + && request.getRequestParameters().containsKey(CODE_CHALLENGE_METHOD) + && request.getRequestParameters().containsKey(CODE_VERIFIER)) { + + return Optional + .of(new CodeChallenge(valueOf(request.getRequestParameters().get(CODE_CHALLENGE)), + PKCEAlgorithm.parse(valueOf(request.getRequestParameters().get(CODE_CHALLENGE_METHOD))), + valueOf(request.getRequestParameters().get(CODE_VERIFIER)))); + } + if (request.getExtensions().containsKey(CODE_CHALLENGE) + && request.getExtensions().containsKey(CODE_CHALLENGE_METHOD) + && request.getExtensions().containsKey(CODE_VERIFIER)) { + + return Optional + .of(new CodeChallenge(valueOf(request.getRequestParameters().get(CODE_CHALLENGE)), + PKCEAlgorithm.parse(valueOf(request.getRequestParameters().get(CODE_CHALLENGE_METHOD))), + valueOf(request.getRequestParameters().get(CODE_VERIFIER)))); + } + return Optional.empty(); } private void validate(OAuth2Authentication authentication) { @@ -372,9 +446,8 @@ private void validate(OAuth2Authentication authentication) { } } - @Override public OAuth2RefreshTokenEntity createRefreshToken(ClientDetailsEntity client, - AuthenticationHolderEntity authHolder) { + AuthenticationHolderEntity authHolder, OAuth2AccessTokenEntity accessToken) { String jti = UUID.randomUUID().toString(); Date iat = new Date(); @@ -393,14 +466,9 @@ public OAuth2RefreshTokenEntity createRefreshToken(ClientDetailsEntity client, refreshClaims.serializeNullClaims(false); PlainJWT refreshJwt = new PlainJWT(refreshClaims.build()); - OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity(); + OAuth2RefreshTokenEntity refreshToken = + new OAuth2RefreshTokenEntity(client, authHolder, accessToken, refreshJwt); refreshToken.setExpiration(exp); - refreshToken.setJwt(refreshJwt); - refreshToken.setAuthenticationHolder(scopeFilter.filterScopes(authHolder)); - refreshToken.setClient(client); - - refreshToken = saveRefreshToken(refreshToken); - eventPublisher.publishEvent(new RefreshTokenIssuedEvent(this, refreshToken)); return refreshToken; } @@ -412,22 +480,6 @@ public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, if (Objects.isNull(refreshTokenValue) || refreshTokenValue.isBlank()) { throw new InvalidTokenException("Invalid refresh token: null or empty value"); } - OAuth2RefreshTokenEntity refreshToken = getRefreshToken(refreshTokenValue); - ClientDetailsEntity client = refreshToken.getClient(); - AuthenticationHolderEntity authHolder = refreshToken.getAuthenticationHolder(); - - OAuth2Request newOAuth2Request = - authHolder.getAuthentication().getOAuth2Request().refresh(authRequest); - OAuth2Authentication newOAuth2Authentication = - new OAuth2Authentication(newOAuth2Request, authHolder.getUserAuth()); - - JWTProfile profile = profileResolver.resolveProfile(client.getScope()); - - Optional account = Optional.empty(); - if (!newOAuth2Authentication.isClientOnly()) { - String username = newOAuth2Authentication.getName(); - account = accountRepository.findByUsername(username); - } ClientDetailsEntity requestingClient = clientService.findClientByClientId(authRequest.getClientId()) @@ -436,119 +488,82 @@ public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, /* client validation */ if (!requestingClient.isActive()) { - throw new InvalidClientException("Suspended client '" + client.getClientId() + "'"); + throw new InvalidClientException("Suspended client '" + requestingClient.getClientId() + "'"); } if (!requestingClient.isAllowRefresh()) { - throw new InvalidClientException( - "Client '" + client.getClientId() + "' does not allow refreshing access token!"); - } - if (!requestingClient.getClientId().equals(client.getClientId())) { - revocationService.revokeRefreshToken(refreshToken); - throw new InvalidClientException("Client does not own the presented refresh token"); + throw new InvalidClientException("Client '" + requestingClient.getClientId() + + "' does not allow refreshing access token!"); } + OAuth2RefreshTokenEntity refreshToken = getRefreshToken(refreshTokenValue); + /* refresh token validation */ if (refreshToken.isExpired()) { revocationService.revokeRefreshToken(refreshToken); throw new InvalidTokenException("Expired refresh token: " + refreshTokenValue); } - Instant tokenIssueInstant = clock.instant(); - OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); - - token.setScope( - computeRefreshedScopes(authRequest, refreshToken.getAuthenticationHolder(), account)); - - token.setClient(client); - token.setExpiration( - computeExpiration(authRequest.getRequestParameters(), client, tokenIssueInstant)); + ClientDetailsEntity client = refreshToken.getClient(); - if (client.isReuseRefreshToken()) { - // if the client re-uses refresh tokens, do that - token.setRefreshToken(refreshToken); - } else { - // otherwise, make a new refresh token - token.setRefreshToken(createRefreshToken(client, authHolder)); - // clean up the old refresh token + if (!requestingClient.getClientId().equals(client.getClientId())) { revocationService.revokeRefreshToken(refreshToken); + throw new InvalidClientException("Client does not own the presented refresh token"); } - token.setAuthenticationHolder(authHolder); - + AuthenticationHolderEntity authHolder = refreshToken.getAuthenticationHolder(); - JWTClaimsSet atClaims = profile.getAccessTokenBuilder() - .buildAccessToken(token, newOAuth2Authentication, account, tokenIssueInstant); + OAuth2Request newOAuth2Request = + authHolder.getAuthentication().getOAuth2Request().refresh(authRequest); + OAuth2Authentication newOAuth2Authentication = + new OAuth2Authentication(newOAuth2Request, authHolder.getUserAuth()); - token.setJwt(signClaims(atClaims)); - token.hashMe(); + Optional account = Optional.empty(); + if (!newOAuth2Authentication.isClientOnly()) { + String username = newOAuth2Authentication.getName(); + account = accountRepository.findByUsername(username); + } - if (newOAuth2Request.getScope().contains(SystemScopeService.OPENID_SCOPE) - && account.isPresent()) { + // Instant tokenIssueInstant = clock.instant(); - JWT idToken = connectTokenService.createIdToken(client, newOAuth2Request, - Date.from(tokenIssueInstant), account.get().getUuid(), token); + AuthenticationHolderEntity newAuthHolder = + new AuthenticationHolderEntity(requestingClient, newOAuth2Authentication); - token.setIdToken(idToken); - } + Set refreshedScopes = refreshScopes(newOAuth2Authentication, account); + newAuthHolder.setScope(refreshedScopes); - if (iamProperties.getClient().isTrackLastUsed()) { - clientService.useClient(token.getClient()); - } - token = saveAccessToken(token); + OAuth2AccessTokenEntity accessToken = createAccessToken(requestingClient, newAuthHolder, + newOAuth2Authentication, account, refreshedScopes); - eventPublisher.publishEvent(new AccessTokenIssuedEvent(this, token)); - return token; - } + if (client.isReuseRefreshToken()) { - private Set computeRefreshedScopes(TokenRequest authRequest, - AuthenticationHolderEntity authHolder, Optional account) { + refreshToken.addAccessToken(accessToken); + accessToken.setRefreshToken(refreshToken); - /* load reserved scopes from database */ - Set reservedScopes = scopeService.toStrings(scopeService.getReserved()); - /* retrieve authorized scopes from refresh token */ - Set authorizedScopes = - Sets.newHashSet(authHolder.getAuthentication().getOAuth2Request().getScope()); - authorizedScopes.removeAll(reservedScopes); - /* get current requested scopes, if present */ - Set requestedScopes = new HashSet<>(); - if (authRequest.getScope() != null) { - requestedScopes.addAll(authRequest.getScope()); - } - requestedScopes.removeAll(reservedScopes); - - /* compute scopes to be filtered */ - Set scopesToFilter = new HashSet<>(); - if (requestedScopes.isEmpty()) { - scopesToFilter.addAll(authorizedScopes); } else { - /* Check for up-scoping */ - if (!scopeService.scopesMatch(authorizedScopes, requestedScopes)) { - String errorMsg = "Up-scoping is not allowed."; - LOG.error(errorMsg); - throw new InvalidScopeException(errorMsg); - } - scopesToFilter.addAll(requestedScopes); + + OAuth2RefreshTokenEntity newRefreshToken = + createRefreshToken(client, authHolder, accessToken); + accessToken.setRefreshToken(newRefreshToken); + authHolder.getRefreshTokens().remove(refreshToken); + revocationService.revokeRefreshToken(refreshToken); + authenticationHolderRepo.save(authHolder); } - if (account.isPresent()) { - return scopeFilter.filterScopes(scopesToFilter, account.get()); + accessToken.setAuthenticationHolder(newAuthHolder); + + if (iamProperties.getClient().isTrackLastUsed()) { + clientService.useClient(newAuthHolder.getClient()); } - return scopeFilter.filterScopes(authHolder).getScope(); - } - public static String sha256(String tokenString) { - return Hashing.sha256().hashString(tokenString, StandardCharsets.UTF_8).toString(); + AuthenticationHolderEntity savedAuthHolder = save(newAuthHolder); + return savedAuthHolder.getAccessTokens().stream().findFirst().get(); } @Override public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue) { - try { - return refreshTokenRepo.findByTokenValue(PlainJWT.parse(refreshTokenValue)) - .orElseThrow(() -> new InvalidTokenException("Invalid refresh token: token not found")); - } catch (ParseException e) { - throw new InvalidTokenException("Invalid refresh token: " + e.getMessage()); - } + return refreshTokenRepo.findByTokenValue(refreshTokenValue) + .orElseThrow(() -> new InvalidTokenException("Invalid refresh token: token not found")); } @Override @@ -563,18 +578,13 @@ public List getRefreshTokensForClient(ClientDetailsEnt return refreshTokenRepo.findByClientId(client.getId()); } - @Override - public void clearExpiredTokens() { - // GarbageCollector will remove them - } - - @Override - public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken) { - - refreshToken.setAuthenticationHolder( - authenticationHolderRepo.save(refreshToken.getAuthenticationHolder())); - return refreshTokenRepo.save(refreshToken); - } + // public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken) { + // + // AuthenticationHolderEntity ah = + // authenticationHolderRepo.save(refreshToken.getAuthenticationHolder()); + // refreshToken.setAuthenticationHolder(ah); + // return refreshTokenRepo.saveAndFlush(refreshToken); + // } @Override public OAuth2AccessTokenEntity getAccessToken(OAuth2Authentication authentication) { @@ -595,9 +605,212 @@ public OAuth2RefreshTokenEntity getRefreshTokenById(Long id) { return refreshTokenRepo.findById(id).orElse(null); } + public static String sha256(String tokenString) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(tokenString.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + @Override - public OAuth2AccessTokenEntity getRegistrationAccessTokenForClient(ClientDetailsEntity client) { + public JWT createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, + String sub, OAuth2AccessTokenEntity accessToken) { + + JWSAlgorithm signingAlg = jwtSigningService.getDefaultSigningAlgorithm(); + + if (client.getIdTokenSignedResponseAlg() != null) { + signingAlg = client.getIdTokenSignedResponseAlg(); + } + + JWT idToken = null; + JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder(); + + idClaims.issuer(iamProperties.getIssuer()); + idClaims.subject(sub); + idClaims.audience(Lists.newArrayList(client.getClientId())); + idClaims.jwtID(UUID.randomUUID().toString()); + + idClaims.issueTime(issueTime); + handleAuthTimestamp(client, request, idClaims); + + if (client.getIdTokenValiditySeconds() != null) { + Date expiration = + new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); + idClaims.expirationTime(expiration); + } + + String nonce = (String) request.getExtensions().get(IamOAuth2ParameterNames.NONCE); + if (!Strings.isNullOrEmpty(nonce)) { + idClaims.claim("nonce", nonce); + } + + Set responseTypes = request.getResponseTypes(); + + if (responseTypes.contains("token")) { + Base64URL atHash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken); + idClaims.claim("at_hash", atHash); + } + + addCustomIdTokenClaims(idClaims, client, request, sub, accessToken); + + if (clientWantsEncryptedIdTokens(client)) { + + JwtEncryptionAndDecryptionService encrypter = + Optional.ofNullable(encrypters.getEncrypter(client)).orElseThrow(); - return accessTokenRepo.findRegistrationToken(client.getId()).orElse(null); + JWEHeader header = new JWEHeader.Builder(client.getIdTokenEncryptedResponseAlg(), + client.getIdTokenEncryptedResponseEnc()).build(); + idToken = new EncryptedJWT(header, idClaims.build()); + encrypter.encryptJwt((JWEObject) idToken); + + } else { + + JWSHeader header = + new JWSHeader.Builder(signingAlg).keyID(jwtSigningService.getDefaultSignerKeyId()) + .build(); + + if (JWSAlgorithm.Family.HMAC_SHA.contains(signingAlg)) { + idToken = new SignedJWT(header, idClaims.build()); + JwtSigningAndValidationService signer = + Optional.ofNullable(symmetricCacheService.getSymmetricValidator(client)).orElseThrow(); + signer.signJwt((SignedJWT) idToken); + } else { + idClaims.claim("kid", jwtSigningService.getDefaultSignerKeyId()); + idToken = new SignedJWT(header, idClaims.build()); + jwtSigningService.signJwt((SignedJWT) idToken); + } + } + + return idToken; + } + + private boolean clientWantsEncryptedIdTokens(ClientDetailsEntity client) { + + return client.getIdTokenEncryptedResponseAlg() != null + && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) + && client.getIdTokenEncryptedResponseEnc() != null + && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) + && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null); + } + + private void addCustomIdTokenClaims(Builder idClaims, ClientDetailsEntity client, + OAuth2Request request, String sub, OAuth2AccessTokenEntity accessToken) { + + IamAccount account = + accountRepository.findByUuid(sub).orElseThrow(() -> NoSuchAccountError.forUuid(sub)); + + JWTProfile profile = profileResolver.resolveProfile(client.getScope(), accessToken.getScope()); + + profile.getIDTokenCustomizer() + .customizeIdTokenClaims(idClaims, client, request, sub, accessToken, account); } + + + private void handleAuthTimestamp(ClientDetailsEntity client, OAuth2Request request, + JWTClaimsSet.Builder idClaims) { + + if (request.getExtensions().containsKey(IamOAuth2ParameterNames.MAX_AGE) + || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) { + + if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) { + Long authTimestamp = Long.parseLong( + (String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP)); + if (authTimestamp != null) { + idClaims.claim("auth_time", authTimestamp / 1000L); + } + } else { + LOG.debug("Unable to find authentication timestamp while creating ID token"); + } + } + } + + @Override + public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) { + + return buildRegistrationAccessToken(client, + Sets.newHashSet(SystemScopeService.REGISTRATION_TOKEN_SCOPE)); + } + + @Override + public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client) { + + return buildRegistrationAccessToken(client, + Sets.newHashSet(SystemScopeService.RESOURCE_TOKEN_SCOPE)); + } + + @Override + public OAuth2AccessTokenEntity rotateRegistrationAccessTokenForClient( + ClientDetailsEntity client) { + + accessTokenRepo.findRegistrationToken(client.getId()) + .ifPresent(revocationService::revokeAccessToken); + return createRegistrationAccessToken(client); + } + + @Override + public OAuth2AccessTokenEntity rotateResourceAccessTokenForClient(ClientDetailsEntity client) { + + accessTokenRepo.findResourceToken(client.getId()) + .ifPresent(revocationService::revokeAccessToken); + return createResourceAccessToken(client); + } + + private OAuth2AccessTokenEntity buildRegistrationAccessToken(ClientDetailsEntity client, + Set scope) { + + Map authorizationParameters = Maps.newHashMap(); + OAuth2Request clientAuth = new OAuth2Request(authorizationParameters, client.getClientId(), + Sets.newHashSet(Authorities.ROLE_CLIENT), true, scope, null, null, null, null); + + OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null); + + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(client, authentication); + + OAuth2AccessTokenEntity regToken = new OAuth2AccessTokenEntity(client, authHolder); + regToken.setScope(scope); + + JWTClaimsSet claims = + new JWTClaimsSet.Builder().audience(Lists.newArrayList(client.getClientId())) + .issuer(iamProperties.getIssuer()) + .issueTime(Date.from(clock.instant())) + .jwtID(UUID.randomUUID().toString()) + .build(); + + JWSAlgorithm signingAlg = jwtSigningService.getDefaultSigningAlgorithm(); + JWSHeader header = + new JWSHeader.Builder(signingAlg).keyID(jwtSigningService.getDefaultSignerKeyId()).build(); + SignedJWT signedToken = new SignedJWT(header, claims); + + jwtSigningService.signJwt(signedToken); + regToken.setTokenJwtValue(signedToken); + + authHolder.addAccessToken(regToken); + + return save(authHolder).getAccessTokens().stream().findFirst().get(); + } + + private AuthenticationHolderEntity save(AuthenticationHolderEntity authHolder) { + + AuthenticationHolderEntity savedAuthHolder = authenticationHolderRepo.saveAndFlush(authHolder); + eventPublisher.publishEvent(new AccessTokenIssuedEvent(this, + savedAuthHolder.getAccessTokens().stream().findFirst().get())); + if (!savedAuthHolder.getRefreshTokens().isEmpty()) { + eventPublisher.publishEvent(new RefreshTokenIssuedEvent(this, + savedAuthHolder.getRefreshTokens().stream().findFirst().get())); + } + return savedAuthHolder; + } + + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamUserDetailsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/IamUserDetailsService.java deleted file mode 100644 index 1debac9026..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamUserDetailsService.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import it.infn.mw.iam.persistence.model.IamAccount; -import it.infn.mw.iam.persistence.model.IamAuthority; -import it.infn.mw.iam.persistence.repository.IamAccountRepository; - -@Service("iamUserDetailsService") -public class IamUserDetailsService implements UserDetailsService { - - @Autowired - private IamAccountRepository repo; - - List convertAuthorities(final IamAccount a) { - - List authorities = new ArrayList<>(); - for (IamAuthority auth : a.getAuthorities()) { - authorities.add(new SimpleGrantedAuthority(auth.getAuthority())); - } - return authorities; - } - - @Override - public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { - - Optional account = repo.findByUsername(username); - - if (account.isPresent()) { - IamAccount a = account.get(); - - if (a.isActive()) { - - return new User(a.getUsername(), a.getPassword(), convertAuthorities(a)); - - } else { - throw new DisabledException("User '" + username + "' is not active."); - } - } - - throw new UsernameNotFoundException("User '" + username + "' not found."); - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/OAuth2TokenEntityService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/OAuth2TokenEntityService.java new file mode 100644 index 0000000000..51d63ca6d5 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/OAuth2TokenEntityService.java @@ -0,0 +1,75 @@ +/** + * 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; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; + +import com.nimbusds.jwt.JWT; + +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; + +@SuppressWarnings("deprecation") +public interface OAuth2TokenEntityService + extends AuthorizationServerTokenServices, ResourceServerTokenServices { + + @Override + public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue); + + @Override + public OAuth2AccessTokenEntity getAccessToken(OAuth2Authentication authentication); + + public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue); + +// public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken); + +// public void revokeAccessToken(OAuth2AccessTokenEntity accessToken); + + public List getAccessTokensForClient(ClientDetailsEntity client); + + public List getRefreshTokensForClient(ClientDetailsEntity client); + + public OAuth2AccessTokenEntity getAccessTokenById(Long id); + + public OAuth2RefreshTokenEntity getRefreshTokenById(Long id); + + public Set getAllAccessTokensForUser(String name); + + public Set getAllRefreshTokensForUser(String name); + +// public OAuth2AccessTokenEntity getRegistrationAccessTokenForClient(ClientDetailsEntity client); + + public JWT createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, + String sub, OAuth2AccessTokenEntity accessToken); + + public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client); + + public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client); + + public OAuth2AccessTokenEntity rotateRegistrationAccessTokenForClient(ClientDetailsEntity client); + + public OAuth2AccessTokenEntity rotateResourceAccessTokenForClient(ClientDetailsEntity client); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IamClientDetailsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IamClientDetailsService.java new file mode 100644 index 0000000000..9f942d60e4 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IamClientDetailsService.java @@ -0,0 +1,65 @@ +/** + * 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.client; + +import static java.lang.String.format; + +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.ClientRegistrationException; +import org.springframework.security.oauth2.provider.NoSuchClientException; +import org.springframework.stereotype.Service; + +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; + +@SuppressWarnings("deprecation") +@Service +public class IamClientDetailsService implements ClientDetailsService { + + public static final String CLIENT_NOT_FOUND_ERROR = "Client %s not found"; + public static final String CLIENT_DISABLED_ERROR = "Client %s is not active"; + + private IamClientRepository clientRepository; + + public IamClientDetailsService(IamClientRepository clientRepository) { + + this.clientRepository = clientRepository; + } + + /** + * Load a client by the client id. This method must not return null. + * + * @param clientId The client id. + * @return The client details (never null). + * @throws ClientRegistrationException If the client account is locked, expired, disabled, or + * invalid for any other reason. + */ + @Override + public ClientDetailsEntity loadClientByClientId(String clientId) + throws ClientRegistrationException { + + ClientDetailsEntity client = clientRepository.findByClientId(clientId) + .orElseThrow(() -> new NoSuchClientException(format(CLIENT_NOT_FOUND_ERROR, clientId))); + + if (!client.isActive()) { + throw new InvalidClientException(format(CLIENT_DISABLED_ERROR, clientId)); + } + + return client; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IAMClientUserDetailsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IamClientUserDetailsService.java similarity index 56% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/client/IAMClientUserDetailsService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/client/IamClientUserDetailsService.java index 548fc5efe8..68f522c26a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IAMClientUserDetailsService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/client/IamClientUserDetailsService.java @@ -15,30 +15,38 @@ */ package it.infn.mw.iam.core.client; +import static java.lang.String.format; + import java.util.Collection; -import java.util.Optional; import java.util.function.Supplier; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.stereotype.Service; import com.google.common.base.Strings; import com.google.common.collect.Sets; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; + @SuppressWarnings("deprecation") -public class IAMClientUserDetailsService implements ClientUserDetailsService { +@Service("clientUserDetailsService") +public class IamClientUserDetailsService implements UserDetailsService { + private static final GrantedAuthority ROLE_CLIENT = new SimpleGrantedAuthority("ROLE_CLIENT"); - private final ClientDetailsEntityService clientService; + public static final String CLIENT_DISABLED_ERROR = "Client %s is not active"; - public IAMClientUserDetailsService(ClientDetailsEntityService clientService) { - this.clientService = clientService; + private final IamClientRepository clientRepository; + + public IamClientUserDetailsService(IamClientRepository clientRepository) { + this.clientRepository = clientRepository; } private Supplier unknownClientError(String clientId) { @@ -48,32 +56,26 @@ private Supplier unknownClientError(String clientId) @Override public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException { - try { - ClientDetailsEntity client = Optional.ofNullable(clientService.loadClientByClientId(clientId)) + ClientDetailsEntity client = clientRepository.findByClientId(clientId) .orElseThrow(unknownClientError(clientId)); - final String password = Strings.nullToEmpty(client.getClientSecret()); - - final boolean accountEnabled = true; - final boolean accountNonExpired = true; - final boolean credentialsNonExpired = true; - final boolean accountNonLocked = true; + if (!client.isActive()) { + throw new InvalidClientException(format(CLIENT_DISABLED_ERROR, clientId)); + } - Collection authorities = Sets.newHashSet(client.getAuthorities()); - authorities.add(ROLE_CLIENT); + final String password = Strings.nullToEmpty(client.getClientSecret()); - return new User(clientId, password, accountEnabled, accountNonExpired, credentialsNonExpired, - accountNonLocked, authorities); + final boolean accountEnabled = true; + final boolean accountNonExpired = true; + final boolean credentialsNonExpired = true; + final boolean accountNonLocked = true; - } catch (InvalidClientException e) { - throw unknownClientError(clientId).get(); - } - } + Collection authorities = Sets.newHashSet(client.getAuthorities()); + authorities.add(ROLE_CLIENT); - @Override - public ClientDetailsEntityService getClientDetailsService() { + return new User(clientId, password, accountEnabled, accountNonExpired, credentialsNonExpired, + accountNonLocked, authorities); - return clientService; } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java index b3817b2c06..347c836835 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java @@ -24,8 +24,8 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.requests.GroupRequestUtils; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @SuppressWarnings("deprecation") @Component diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java index ddf4d4cbfd..5dec0dcb73 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -30,10 +29,11 @@ import it.infn.mw.iam.authn.AbstractExternalAuthenticationToken; import it.infn.mw.iam.core.IamGroupRequestStatus; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamGroupRequest; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @SuppressWarnings("deprecation") public class IamSecurityExpressionMethods { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java index fd42c29e04..a50595e3a4 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java @@ -24,8 +24,8 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.requests.GroupRequestUtils; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @SuppressWarnings("deprecation") @Component diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/gc/DefaultGarbageCollector.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/gc/DefaultGarbageCollector.java index ea91b18e16..2fdf79571c 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/gc/DefaultGarbageCollector.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/gc/DefaultGarbageCollector.java @@ -15,18 +15,8 @@ */ package it.infn.mw.iam.core.gc; -import java.util.Collection; - -import org.mitre.data.DefaultPageCriteria; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.AuthorizationCodeEntity; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.repository.AuthorizationCodeRepository; -import org.mitre.oauth2.repository.impl.DeviceCodeRepository; -import org.mitre.openid.connect.service.ApprovedSiteService; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; @@ -34,7 +24,15 @@ import org.springframework.transaction.annotation.Transactional; import it.infn.mw.iam.api.common.OffsetPageable; +import it.infn.mw.iam.core.AuthenticationHolderService; +import it.infn.mw.iam.core.oauth.approvedsite.ApprovedSiteService; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.AuthorizationCodeEntity; import it.infn.mw.iam.persistence.model.IamRevokedAccessToken; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.repository.IamAuthorizationCodeRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; import it.infn.mw.iam.persistence.repository.IamRevokedAccessTokenRepository; @@ -47,23 +45,23 @@ public class DefaultGarbageCollector implements GarbageCollector { private final ApprovedSiteService approvedSiteService; private final IamOAuthAccessTokenRepository accessTokenRepo; private final IamOAuthRefreshTokenRepository refreshTokenRepo; - private final DeviceCodeRepository deviceCodeRepo; - private final AuthenticationHolderRepository authenticationHolderRepository; + private final DeviceCodeService deviceCodeService; + private final AuthenticationHolderService authHolderService; private final IamRevokedAccessTokenRepository revokedAccessTokenRepo; - private final AuthorizationCodeRepository authzCodeRepo; + private final IamAuthorizationCodeRepository authzCodeRepo; public DefaultGarbageCollector(ApprovedSiteService approvedSiteService, IamOAuthAccessTokenRepository accessTokenRepo, - IamOAuthRefreshTokenRepository refreshTokenRepo, DeviceCodeRepository deviceCodeRepo, - AuthenticationHolderRepository authenticationHolderRepository, + IamOAuthRefreshTokenRepository refreshTokenRepo, DeviceCodeService deviceCodeService, + AuthenticationHolderService authHolderService, IamRevokedAccessTokenRepository revokedAccessTokenRepo, - AuthorizationCodeRepository authzCodeRepo) { + IamAuthorizationCodeRepository authzCodeRepo) { this.approvedSiteService = approvedSiteService; this.accessTokenRepo = accessTokenRepo; this.refreshTokenRepo = refreshTokenRepo; - this.deviceCodeRepo = deviceCodeRepo; - this.authenticationHolderRepository = authenticationHolderRepository; + this.deviceCodeService = deviceCodeService; + this.authHolderService = authHolderService; this.revokedAccessTokenRepo = revokedAccessTokenRepo; this.authzCodeRepo = authzCodeRepo; } @@ -78,18 +76,17 @@ public void clearExpiredApprovedSites(int count) { @Transactional(value = "defaultTransactionManager") public void clearExpiredAuthorizationCodes(int count) { - Collection expiredAuthzCodes = authzCodeRepo.getExpiredCodes(); + List expiredAuthzCodes = authzCodeRepo.findExpired(); LOG.debug("Found {} expired authorization codes", expiredAuthzCodes.size()); - expiredAuthzCodes.forEach(authzCodeRepo::remove); + expiredAuthzCodes.forEach(authzCodeRepo::delete); } @Override @Transactional(value = "defaultTransactionManager") public void clearExpiredDeviceCodes(int count) { - Collection expiredDeviceCodes = deviceCodeRepo.getExpiredCodes(); - expiredDeviceCodes.forEach(deviceCodeRepo::remove); - LOG.debug("Removed {} expired device codes", expiredDeviceCodes.size()); + int deleted = deviceCodeService.clearExpired(); + LOG.debug("Removed {} expired device codes", deleted); } @Override @@ -124,10 +121,11 @@ public void clearExpiredRefreshTokens(int count) { @Override public void clearOrphanedAuthenticationHolder(int count) { - Collection orphanedHolders = - authenticationHolderRepository.getOrphanedAuthenticationHolders(new DefaultPageCriteria()); - orphanedHolders.forEach(authenticationHolderRepository::remove); - LOG.debug("Removed {} orphaned authentication holders", orphanedHolders.size()); + Page orphanedHolders = + authHolderService.getOrphanedAuthenticationHolders(new OffsetPageable(0, 100)); + LOG.debug("Removing {} orphaned authentication holders ...", + orphanedHolders.getContent().size()); + orphanedHolders.getContent().forEach(authHolderService::remove); } -} \ No newline at end of file +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/ClientKeyCacheService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/ClientKeyCacheService.java new file mode 100644 index 0000000000..c5abc58aaf --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/ClientKeyCacheService.java @@ -0,0 +1,129 @@ +/** + * 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.jwt; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; + +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + +@Service +public class ClientKeyCacheService { + + private static Logger logger = LoggerFactory.getLogger(ClientKeyCacheService.class); + + private JwkSetCacheService jwksUriCache; + private SymmetricKeyJWTValidatorCacheService symmetricCache; + + private LoadingCache jwksValidators; + private LoadingCache jwksEncrypters; + + public ClientKeyCacheService(JwkSetCacheService jwksUriCache, + SymmetricKeyJWTValidatorCacheService symmetricCache) { + + this.jwksValidators = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .maximumSize(100) + .build(new JWKSetVerifierBuilder()); + this.jwksEncrypters = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .maximumSize(100) + .build(new JWKSetEncryptorBuilder()); + } + + public JwtSigningAndValidationService getValidator(ClientDetailsEntity client, JWSAlgorithm alg) { + + try { + if (alg.equals(JWSAlgorithm.RS256) || alg.equals(JWSAlgorithm.RS384) + || alg.equals(JWSAlgorithm.RS512) || alg.equals(JWSAlgorithm.ES256) + || alg.equals(JWSAlgorithm.ES384) || alg.equals(JWSAlgorithm.ES512) + || alg.equals(JWSAlgorithm.PS256) || alg.equals(JWSAlgorithm.PS384) + || alg.equals(JWSAlgorithm.PS512)) { + + // asymmetric key + if (client.getJwks() != null) { + return jwksValidators.get(client.getJwks()); + } else if (!Strings.isNullOrEmpty(client.getJwksUri())) { + return jwksUriCache.getValidator(client.getJwksUri()); + } else { + return null; + } + + } else if (alg.equals(JWSAlgorithm.HS256) || alg.equals(JWSAlgorithm.HS384) + || alg.equals(JWSAlgorithm.HS512)) { + + return symmetricCache.getSymmetricValidator(client); + } + + return null; + + } catch (UncheckedExecutionException | ExecutionException e) { + logger.error("Problem loading client validator", e); + return null; + } + } + + public JwtEncryptionAndDecryptionService getEncrypter(ClientDetailsEntity client) { + + try { + if (client.getJwks() != null) { + return jwksEncrypters.get(client.getJwks()); + } else if (!Strings.isNullOrEmpty(client.getJwksUri())) { + return jwksUriCache.getEncrypter(client.getJwksUri()); + } else { + return null; + } + } catch (UncheckedExecutionException | ExecutionException e) { + logger.error("Problem loading client encrypter", e); + return null; + } + + } + + + private class JWKSetEncryptorBuilder + extends CacheLoader { + + @Override + public JwtEncryptionAndDecryptionService load(JWKSet key) throws Exception { + return new IamJwtEncryptionAndDecryptionService(new JwkSetKeyStore(key)); + } + + } + + private class JWKSetVerifierBuilder extends CacheLoader { + + @Override + public JwtSigningAndValidationService load(JWKSet key) throws Exception { + return new IamJwtSigningAndValidationService(new JwkSetKeyStore(key)); + } + + } + + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWKSetCacheService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwkSetCacheService.java similarity index 64% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWKSetCacheService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwkSetCacheService.java index 88f5734b37..2d6e46e20a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWKSetCacheService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwkSetCacheService.java @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.core.jwk; +package it.infn.mw.iam.core.jwt; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.client.RestTemplate; @@ -34,32 +30,32 @@ import it.infn.mw.iam.authn.oidc.RestTemplateFactory; -public class IamJWKSetCacheService extends JWKSetCacheService { +public class IamJwkSetCacheService implements JwkSetCacheService { public static final String KEY_MATERIAL_ERROR_TEMPLATE = "Could not retrieve key material from {}"; - public static final Logger LOG = LoggerFactory.getLogger(IamJWKSetCacheService.class); + public static final Logger LOG = LoggerFactory.getLogger(IamJwkSetCacheService.class); - private LoadingCache validators; - private LoadingCache encrypters; + private LoadingCache validators; + private LoadingCache encrypters; - public IamJWKSetCacheService(RestTemplateFactory rtf, int maxCacheSize, int expirationTime, + public IamJwkSetCacheService(RestTemplateFactory rtf, int maxCacheSize, int expirationTime, TimeUnit timeUnit) { this.validators = CacheBuilder.newBuilder() .expireAfterWrite(expirationTime, timeUnit) .maximumSize(maxCacheSize) - .build(new JWKSetVerifierFetcher(rtf)); + .build(new JwkSetVerifierFetcher(rtf)); this.encrypters = CacheBuilder.newBuilder() .expireAfterWrite(expirationTime, timeUnit) .maximumSize(maxCacheSize) - .build(new JWKSetEncryptorFetcher(rtf)); + .build(new JwkSetEncryptorFetcher(rtf)); } @Override - public JWTSigningAndValidationService getValidator(String jwksUri) { + public JwtSigningAndValidationService getValidator(String jwksUri) { try { return validators.get(jwksUri); @@ -73,7 +69,7 @@ public JWTSigningAndValidationService getValidator(String jwksUri) { } @Override - public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) { + public JwtEncryptionAndDecryptionService getEncrypter(String jwksUri) { try { return encrypters.get(jwksUri); } catch (UncheckedExecutionException | ExecutionException e) { @@ -85,49 +81,49 @@ public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) { } } - public static class JWKSetEncryptorFetcher - extends CacheLoader { + public static class JwkSetEncryptorFetcher + extends CacheLoader { final RestTemplateFactory rtf; - public JWKSetEncryptorFetcher(RestTemplateFactory rtf) { + public JwkSetEncryptorFetcher(RestTemplateFactory rtf) { this.rtf = rtf; } @Override - public JWTEncryptionAndDecryptionService load(String key) throws Exception { + public JwtEncryptionAndDecryptionService load(String key) throws Exception { RestTemplate rt = rtf.newRestTemplate(); String jsonString = rt.getForObject(key, String.class); JWKSet jwkSet = JWKSet.parse(jsonString); - JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); + JwkSetKeyStore keyStore = new JwkSetKeyStore(jwkSet); - return new IamJWTEncryptionService(keyStore); + return new IamJwtEncryptionAndDecryptionService(keyStore); } } - public static class JWKSetVerifierFetcher - extends CacheLoader { + public static class JwkSetVerifierFetcher + extends CacheLoader { final RestTemplateFactory rtf; - public JWKSetVerifierFetcher(RestTemplateFactory rtf) { + public JwkSetVerifierFetcher(RestTemplateFactory rtf) { this.rtf = rtf; } @Override - public JWTSigningAndValidationService load(String key) throws Exception { + public JwtSigningAndValidationService load(String key) throws Exception { RestTemplate rt = rtf.newRestTemplate(); String jsonString = rt.getForObject(key, String.class); JWKSet jwkSet = JWKSet.parse(jsonString); - JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); + JwkSetKeyStore keyStore = new JwkSetKeyStore(jwkSet); - return new IamJWTSigningService(keyStore); + return new IamJwtSigningAndValidationService(keyStore); } } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWTEncryptionService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwtEncryptionAndDecryptionService.java similarity index 78% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWTEncryptionService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwtEncryptionAndDecryptionService.java index d052d055d4..37021aa2fe 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWTEncryptionService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwtEncryptionAndDecryptionService.java @@ -13,24 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.core.jwk; +package it.infn.mw.iam.core.jwt; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Maps.newHashMap; -import static com.google.common.collect.Sets.newHashSet; import static it.infn.mw.iam.core.jwk.JWKUtils.buildDecrypter; import static it.infn.mw.iam.core.jwk.JWKUtils.buildEncrypter; import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.util.Strings; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,9 +46,9 @@ import it.infn.mw.iam.core.error.StartupError; -public class IamJWTEncryptionService implements JWTEncryptionAndDecryptionService { +public class IamJwtEncryptionAndDecryptionService implements JwtEncryptionAndDecryptionService { - private static final Logger LOG = LoggerFactory.getLogger(IamJWTEncryptionService.class); + private static final Logger LOG = LoggerFactory.getLogger(IamJwtEncryptionAndDecryptionService.class); private static final String KEY_INIT_ERROR_MSG = "Error initializing keys"; @@ -59,31 +56,33 @@ public class IamJWTEncryptionService implements JWTEncryptionAndDecryptionServic private static final String DECRYPTION_ERROR_MSG = "JWT decryption error: {}"; private static final String NO_PRIVATE_KEY_MSG = "No private key found for key id {}"; - private final Map encrypters = newHashMap(); - private final Map decrypters = newHashMap(); + private final Map encrypters = new HashMap<>(); + private final Map decrypters = new HashMap<>(); - private final Map allPublicKeys = newHashMap(); - private final Set allAlgorithms = newHashSet(); - private final Set allEncryptionMethods = newHashSet(); + private final Map allPublicKeys = new HashMap<>(); + private final Set allAlgorithms = new HashSet<>(); + private final Set allEncryptionMethods = new HashSet<>(); private final String defaultJweEncryptKeyId; private final String defaultJweDecryptKeyId; - private final JWKSetKeyStore keystore; + private final JwkSetKeyStore keystore; - public IamJWTEncryptionService(IamProperties properties, JWKSetKeyStore keyStore) { - checkNotNull(keyStore, "Please provide a keystore"); - checkNotNull(properties, "Please provide properties"); - this.keystore = keyStore; + public IamJwtEncryptionAndDecryptionService(IamProperties properties, JwkSetKeyStore keystore) { + requireNonNull(keystore, "Please provide a keystore"); + requireNonNull(properties, "Please provide properties"); + this.keystore = keystore; this.defaultJweEncryptKeyId = properties.getJwk().getDefaultJweEncryptKeyId(); this.defaultJweDecryptKeyId = properties.getJwk().getDefaultJweDecryptKeyId(); initializeEncryptersAndDecrypters(); } - public IamJWTEncryptionService(JWKSetKeyStore keyStore, String defaultKeyId) { - checkNotNull(keyStore, "Please provide a keystore"); - checkArgument(!keyStore.getKeys().isEmpty(), "Please provide a non-empty keystore"); - this.keystore = keyStore; + public IamJwtEncryptionAndDecryptionService(JwkSetKeyStore keystore, String defaultKeyId) { + requireNonNull(keystore, "Please provide a keystore"); + if (keystore.getKeys().isEmpty()) { + throw new IllegalArgumentException("Please provide a non-empty keystore"); + } + this.keystore = keystore; if (!isNull(defaultKeyId)) { this.defaultJweDecryptKeyId = defaultKeyId; @@ -98,7 +97,7 @@ public IamJWTEncryptionService(JWKSetKeyStore keyStore, String defaultKeyId) { } - public IamJWTEncryptionService(JWKSetKeyStore keyStore) { + public IamJwtEncryptionAndDecryptionService(JwkSetKeyStore keyStore) { this(keyStore, null); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWTSigningService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwtSigningAndValidationService.java similarity index 80% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWTSigningService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwtSigningAndValidationService.java index 2146b9d02c..503408bd4c 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwk/IamJWTSigningService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/IamJwtSigningAndValidationService.java @@ -13,25 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.core.jwk; +package it.infn.mw.iam.core.jwt; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Maps.newHashMap; -import static com.google.common.collect.Sets.newHashSet; import static it.infn.mw.iam.core.jwk.JWKUtils.buildSigner; import static it.infn.mw.iam.core.jwk.JWKUtils.buildVerifier; import static java.lang.String.format; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.util.Strings; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,10 +42,9 @@ import it.infn.mw.iam.config.IamProperties.JWKProperties; import it.infn.mw.iam.core.error.StartupError; +public class IamJwtSigningAndValidationService implements JwtSigningAndValidationService { -public class IamJWTSigningService implements JWTSigningAndValidationService { - - private static final Logger LOG = LoggerFactory.getLogger(IamJWTSigningService.class); + private static final Logger LOG = LoggerFactory.getLogger(IamJwtSigningAndValidationService.class); private static final String SIGNER_NOT_FOUND_FOR_KEY_MSG = "Signer not found for key %s"; private static final String SIGNER_NOT_FOUND_FOR_ALGO_MSG = "Signer not found for algorithm {}"; @@ -57,37 +53,38 @@ public class IamJWTSigningService implements JWTSigningAndValidationService { private static final String VERIFIER_NOT_FOUND_MSG = "JWS verifier not found for key {}"; private static final String KEY_INIT_ERROR_MSG = "Error initializing keys"; - private final JWKSetKeyStore keystore; + private final JwkSetKeyStore keystore; - private final Set allAlgorithms = newHashSet(); - private Map signers = newHashMap(); - private Map verifiers = newHashMap(); - private Map allPublicKeys = newHashMap(); + private final Set allAlgorithms = new HashSet<>(); + private Map signers = new HashMap<>(); + private Map verifiers = new HashMap<>(); + private Map allPublicKeys = new HashMap<>(); private final JWSAlgorithm defaultAlgorithm; private final String defaultSignerKeyId; - public IamJWTSigningService(JWKProperties properties, JWKSetKeyStore keystore) { - checkNotNull(keystore, "null keystore"); - checkNotNull(properties, "null properties"); - - checkArgument(!keystore.getKeys().isEmpty(), - "empty keystore"); + public IamJwtSigningAndValidationService(JWKProperties properties, JwkSetKeyStore keystore) { + requireNonNull(keystore, "null keystore"); + requireNonNull(properties, "null properties"); + if (keystore.getKeys().isEmpty()) { + throw new IllegalArgumentException("Please provide a non-empty keystore"); + } this.keystore = keystore; - this.defaultAlgorithm = JWSAlgorithm.parse(properties.getDefaultJwsAlgorithm()); this.defaultSignerKeyId = properties.getDefaultKeyId(); initializeSignersAndVerifiers(); } - public IamJWTSigningService(JWKSetKeyStore keystore) { + public IamJwtSigningAndValidationService(JwkSetKeyStore keystore) { this(keystore, null, null); } - public IamJWTSigningService(JWKSetKeyStore keystore, String defaultKeyId, + public IamJwtSigningAndValidationService(JwkSetKeyStore keystore, String defaultKeyId, String defaultAlgorithm) { - checkNotNull(keystore, "null keystore"); - checkArgument(!keystore.getKeys().isEmpty(), "Please provide a non-empty keystore"); + requireNonNull(keystore, "null keystore"); + if (keystore.getKeys().isEmpty()) { + throw new IllegalArgumentException("Please provide a non-empty keystore"); + } this.keystore = keystore; this.defaultAlgorithm = Optional.ofNullable(defaultAlgorithm).map(JWSAlgorithm::parse).orElse(null); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwkSetCacheService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwkSetCacheService.java new file mode 100644 index 0000000000..f7391e8267 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwkSetCacheService.java @@ -0,0 +1,23 @@ +/** + * 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.jwt; + +public interface JwkSetCacheService { + + public JwtSigningAndValidationService getValidator(String jwksUri); + + public JwtEncryptionAndDecryptionService getEncrypter(String jwksUri); +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwkSetKeyStore.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwkSetKeyStore.java new file mode 100644 index 0000000000..745622fbcb --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwkSetKeyStore.java @@ -0,0 +1,78 @@ +/** + * 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.jwt; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.List; +import java.util.Objects; + +import org.springframework.core.io.Resource; + +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; + +public class JwkSetKeyStore { + + private JWKSet jwkSet; + + private Resource location; + + public JwkSetKeyStore(JWKSet jwkSet) { + Objects.requireNonNull(jwkSet); + this.jwkSet = jwkSet; + this.location = null; + } + + public JwkSetKeyStore(Resource location) { + Objects.requireNonNull(location); + initializeJwkSetFromLocation(location); + } + + private void initializeJwkSetFromLocation(Resource location) { + + if (!location.exists() || !location.isReadable()) { + throw new IllegalArgumentException("Key Set resource could not be read: " + location); + } + try { + String jwkStr = new String(location.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + this.jwkSet = JWKSet.parse(jwkStr); + } catch (IOException e) { + throw new IllegalArgumentException("Key Set resource could not be read: " + location); + } catch (ParseException e) { + throw new IllegalArgumentException("Key Set resource could not be parsed: " + location); + } + this.location = location; + } + + public JWKSet getJwkSet() { + return jwkSet; + } + + public Resource getLocation() { + return location; + } + + public List getKeys() { + if (jwkSet != null) { + return jwkSet.getKeys(); + } + return List.of(); + } + + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwtEncryptionAndDecryptionService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwtEncryptionAndDecryptionService.java new file mode 100644 index 0000000000..dedcd6e404 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwtEncryptionAndDecryptionService.java @@ -0,0 +1,70 @@ +/** + * 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.jwt; + +import java.util.Collection; +import java.util.Map; + +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWEObject; +import com.nimbusds.jose.jwk.JWK; + +public interface JwtEncryptionAndDecryptionService { + + /** + * Encrypts the JWT in place with the default encrypter. If an arbitrary payload is used, then + * pass in a JWEObject. Otherwise, if JWT claims are the payload, then use the JWEObject subclass + * EncryptedJWT instead. + * + * @param jwt + */ + public void encryptJwt(JWEObject jwt); + + /** + * Decrypts the JWT in place with the default decrypter. If an arbitrary payload is used, then + * pass in a JWEObject. Otherwise, if JWT claims are the payload, then use the JWEObject subclass + * EncryptedJWT instead. + * + * @param jwt + */ + public void decryptJwt(JWEObject jwt); + + /** + * Get all public keys for this service, mapped by their Key ID + */ + public Map getAllPublicKeys(); + + /** + * Get the list of all encryption algorithms supported by this service. + * + * @return + */ + public Collection getAllEncryptionAlgsSupported(); + + /** + * Get the list of all encryption methods supported by this service. + * + * @return + */ + public Collection getAllEncryptionEncsSupported(); + + /** + * TODO add functionality for encrypting and decrypting using a specified key id. Example: public + * void encryptJwt(EncryptedJWT jwt, String kid); + */ + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwtSigningAndValidationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwtSigningAndValidationService.java new file mode 100644 index 0000000000..542c976b77 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/JwtSigningAndValidationService.java @@ -0,0 +1,78 @@ +/** + * 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.jwt; + +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.Map; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jwt.SignedJWT; + +public interface JwtSigningAndValidationService { + + /** + * Get all public keys + * @return the map of all the public keys identified by their kid + */ + public Map getAllPublicKeys(); + + /** + * Checks the signature of the given JWT against all configured signers. + * + * @param jwtString + * the string representation of the JWT + * @return true if at least one of the signers validates the given JWT + * @throws NoSuchAlgorithmException + */ + public boolean validateSignature(SignedJWT jwtString); + + /** + * Sign the given JWT using the default algorithm and key. + * + * @param jwt the JWT to sign + * @throws NoSuchAlgorithmException + */ + public void signJwt(SignedJWT jwt); + + /** + * Get the default signing algorithm to use. + * @return the default signing algorithm + */ + public JWSAlgorithm getDefaultSigningAlgorithm(); + + /** + * Get the list of all the supported signing algorithms. + * @return the list of all the supported signing algorithms + */ + public Collection getAllSigningAlgsSupported(); + + /** + * Sign a JWT using the selected algorithm. + * + * @param jwt the JWT to sign + * @param alg the name of the algorithm to use, as specified in JWS s.6 + */ + public void signJwt(SignedJWT jwt, JWSAlgorithm alg); + + /** + * Get the list of the kid of the default signer object. + * @return the kid of the default signer object + */ + public String getDefaultSignerKeyId(); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/SymmetricKeyJWTValidatorCacheService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/SymmetricKeyJWTValidatorCacheService.java new file mode 100644 index 0000000000..9f37ea6994 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/SymmetricKeyJWTValidatorCacheService.java @@ -0,0 +1,86 @@ +/** + * 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.jwt; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.util.Base64URL; + +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + +@Service +public class SymmetricKeyJWTValidatorCacheService { + + private static final Logger logger = + LoggerFactory.getLogger(SymmetricKeyJWTValidatorCacheService.class); + + private LoadingCache validators; + + public SymmetricKeyJWTValidatorCacheService() { + validators = CacheBuilder.newBuilder() + .expireAfterAccess(24, TimeUnit.HOURS) + .maximumSize(100) + .build(new SymmetricValidatorBuilder()); + } + + public JwtSigningAndValidationService getSymmetricValidator(ClientDetailsEntity client) { + + if (client == null) { + logger.error("Couldn't create symmetric validator for null client"); + return null; + } + + if (Strings.isNullOrEmpty(client.getClientSecret())) { + logger.error("Couldn't create symmetric validator for client " + client.getClientId() + + " without a client secret"); + return null; + } + + try { + return validators.get(client.getClientSecret()); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.error("Problem loading client validator", e); + } + return null; + } + + public class SymmetricValidatorBuilder + extends CacheLoader { + @Override + public JwtSigningAndValidationService load(String key) throws Exception { + + JWK jwk = new OctetSequenceKey.Builder(Base64URL.encode(key)).keyUse(KeyUse.SIGNATURE) + .keyID("SYMMETRIC-KEY") + .build(); + return new IamJwtSigningAndValidationService(new JwkSetKeyStore(new JWKSet(jwk))); + } + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/AssertionOAuth2RequestFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/AssertionOAuth2RequestFactory.java new file mode 100644 index 0000000000..cc98e2687c --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/AssertionOAuth2RequestFactory.java @@ -0,0 +1,28 @@ +/** + * 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.jwt.assertion; + +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +import com.nimbusds.jwt.JWT; + +@SuppressWarnings("deprecation") +public interface AssertionOAuth2RequestFactory { + + OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion); +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/AssertionValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/AssertionValidator.java new file mode 100644 index 0000000000..c0a8535409 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/AssertionValidator.java @@ -0,0 +1,24 @@ +/** + * 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.jwt.assertion; + +import com.nimbusds.jwt.JWT; + +public interface AssertionValidator { + + public boolean isValid(JWT assertion); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/DirectCopyRequestFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/DirectCopyRequestFactory.java new file mode 100644 index 0000000000..d9aaa59416 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/DirectCopyRequestFactory.java @@ -0,0 +1,51 @@ +/** + * 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.jwt.assertion; + +import java.text.ParseException; +import java.util.Set; + +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +import com.google.common.collect.Sets; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; + +@SuppressWarnings("deprecation") +public class DirectCopyRequestFactory implements AssertionOAuth2RequestFactory { + + @Override + public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, + JWT assertion) { + + try { + JWTClaimsSet claims = assertion.getJWTClaimsSet(); + Set scope = OAuth2Utils.parseParameterList(claims.getStringClaim("scope")); + + Set resources = Sets.newHashSet(claims.getAudience()); + + return new OAuth2Request(tokenRequest.getRequestParameters(), client.getClientId(), + client.getAuthorities(), true, scope, resources, null, null, null); + } catch (ParseException e) { + return null; + } + + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/SelfAssertionValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/SelfAssertionValidator.java new file mode 100644 index 0000000000..8352d62840 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/SelfAssertionValidator.java @@ -0,0 +1,73 @@ +/** + * 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.jwt.assertion; + +import java.text.ParseException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Strings; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; + +public class SelfAssertionValidator implements AssertionValidator { + + private static Logger logger = LoggerFactory.getLogger(SelfAssertionValidator.class); + + private final IamProperties config; + private final JwtSigningAndValidationService jwtService; + + public SelfAssertionValidator(IamProperties config, JwtSigningAndValidationService jwtService) { + + this.config = config; + this.jwtService = jwtService; + } + + @Override + public boolean isValid(JWT assertion) { + + if (!(assertion instanceof SignedJWT)) { + return false; + } + + JWTClaimsSet claims; + try { + claims = assertion.getJWTClaimsSet(); + } catch (ParseException e) { + logger.debug("Invalid assertion claims"); + return false; + } + + if (Strings.isNullOrEmpty(claims.getIssuer())) { + logger.debug("No issuer for assertion, rejecting"); + return false; + } + + if (claims.getIssuer().equals(config.getIssuer())) { + return jwtService.validateSignature((SignedJWT) assertion); + } + + logger.debug("Issuer is not the same as this server, rejecting"); + return false; + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/WhitelistedIssuerAssertionValidator.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/WhitelistedIssuerAssertionValidator.java new file mode 100644 index 0000000000..553b897d2f --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/jwt/assertion/WhitelistedIssuerAssertionValidator.java @@ -0,0 +1,86 @@ +/** + * 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.jwt.assertion; + +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Strings; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; + +public class WhitelistedIssuerAssertionValidator implements AssertionValidator { + + private static Logger logger = LoggerFactory.getLogger(WhitelistedIssuerAssertionValidator.class); + + private final JwkSetCacheService jwkCache; + private Map whitelist; + + public WhitelistedIssuerAssertionValidator(JwkSetCacheService jwkCache) { + + this.jwkCache = jwkCache; + this.whitelist = new HashMap<>(); + } + + public Map getWhitelist() { + return whitelist; + } + + public void setWhitelist(Map whitelist) { + this.whitelist = whitelist; + } + + @Override + public boolean isValid(JWT assertion) { + + if (!(assertion instanceof SignedJWT)) { + return false; + } + + JWTClaimsSet claims; + try { + claims = assertion.getJWTClaimsSet(); + } catch (ParseException e) { + logger.debug("Invalid assertion claims"); + return false; + } + + if (Strings.isNullOrEmpty(claims.getIssuer())) { + logger.debug("No issuer for assertion, rejecting"); + return false; + } + + if (!whitelist.containsKey(claims.getIssuer())) { + logger.debug("Issuer is not in whitelist, rejecting"); + return false; + } + + String jwksUri = whitelist.get(claims.getIssuer()); + + JwtSigningAndValidationService validator = jwkCache.getValidator(jwksUri); + + return validator.validateSignature((SignedJWT) assertion); + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/BlacklistedSiteService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/BlacklistedSiteService.java new file mode 100644 index 0000000000..21410c33f6 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/BlacklistedSiteService.java @@ -0,0 +1,34 @@ +/** + * 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.oauth; + +import java.util.Collection; + +import it.infn.mw.iam.persistence.model.BlacklistedSite; + +public interface BlacklistedSiteService { + + public Collection getAll(); + + public BlacklistedSite getById(Long id); + + public void remove(BlacklistedSite blacklistedSite); + + public BlacklistedSite save(BlacklistedSite blacklistedSite); + + public boolean isBlacklisted(String uri); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamBlacklistedSiteService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamBlacklistedSiteService.java new file mode 100644 index 0000000000..2cb605457a --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamBlacklistedSiteService.java @@ -0,0 +1,64 @@ +/** + * 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.oauth; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; + +import it.infn.mw.iam.persistence.model.BlacklistedSite; +import it.infn.mw.iam.persistence.repository.IamBlacklistedSiteRepository; + +@Service +public class IamBlacklistedSiteService implements BlacklistedSiteService { + + @Autowired + private IamBlacklistedSiteRepository repository; + + @Override + public Collection getAll() { + return repository.findAll(); + } + + @Override + public BlacklistedSite getById(Long id) { + return repository.findById(id).orElse(null); + } + + @Override + public void remove(BlacklistedSite blacklistedSite) { + repository.delete(blacklistedSite); + } + + @Override + public BlacklistedSite save(BlacklistedSite blacklistedSite) { + return repository.save(blacklistedSite); + } + + @Override + public boolean isBlacklisted(String uri) { + + if (Strings.isNullOrEmpty(uri)) { + return false; + } + Collection sites = repository.findByUri(uri); + return sites.stream().filter(bs -> bs.getUri() != null).anyMatch(bs -> uri.equals(bs.getUri())); + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2ParameterNames.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2ParameterNames.java new file mode 100644 index 0000000000..894db7e362 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2ParameterNames.java @@ -0,0 +1,55 @@ +/** + * 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.oauth; + +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; + +public interface IamOAuth2ParameterNames extends OAuth2ParameterNames { + + public String CLIENT_ID = "client_id"; + public String RESPONSE_TYPE = "response_type"; + public String REDIRECT_URI = "redirect_uri"; + public String STATE = "state"; + public String DISPLAY = "display"; + public String REQUEST = "request"; + public String LOGIN_HINT = "login_hint"; + public String MAX_AGE = "max_age"; + public String CLAIMS = "claims"; + public String SCOPE = "scope"; + public String NONCE = "nonce"; + public String PROMPT = "prompt"; + + // prompt values + public String PROMPT_LOGIN = "login"; + public String PROMPT_NONE = "none"; + public String PROMPT_CONSENT = "consent"; + public String PROMPT_SEPARATOR = " "; + + // extensions + public String APPROVED_SITE = "approved_site"; + + // responses + public String ERROR = "error"; + public String LOGIN_REQUIRED = "login_required"; + + // audience + public String AUD = "aud"; + + // PKCE + public static final String CODE_VERIFIER = "code_verifier"; + public static final String CODE_CHALLENGE = "code_challenge"; + public static final String CODE_CHALLENGE_METHOD = "code_challenge_method"; +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2RequestFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2RequestFactory.java index d7f4eed474..431529bac3 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2RequestFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuth2RequestFactory.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.core.oauth; -import static com.google.common.base.Strings.isNullOrEmpty; import static it.infn.mw.iam.core.oauth.granters.TokenExchangeTokenGranter.TOKEN_EXCHANGE_GRANT_TYPE; import java.net.MalformedURLException; @@ -31,14 +30,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.mitre.oauth2.model.AuthorizationCodeEntity; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.oauth2.repository.AuthorizationCodeRepository; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.DeviceCodeService; -import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.openid.connect.request.ConnectOAuth2RequestFactory; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -49,22 +40,31 @@ import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; +import com.google.common.base.Strings; import it.infn.mw.iam.authn.AbstractExternalAuthenticationToken; import it.infn.mw.iam.authn.multi_factor_authentication.IamAuthenticationMethodReference; import it.infn.mw.iam.core.ExtendedAuthenticationToken; +import it.infn.mw.iam.core.OAuth2TokenEntityService; import it.infn.mw.iam.core.error.InvalidResourceError; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; +import it.infn.mw.iam.persistence.model.AuthorizationCodeEntity; +import it.infn.mw.iam.persistence.model.DeviceCode; +import it.infn.mw.iam.persistence.repository.IamAuthorizationCodeRepository; @SuppressWarnings("deprecation") -public class IamOAuth2RequestFactory extends ConnectOAuth2RequestFactory { +public class IamOAuth2RequestFactory extends DefaultOAuth2RequestFactory { public static final Logger LOG = LoggerFactory.getLogger(IamOAuth2RequestFactory.class); @@ -86,15 +86,14 @@ public class IamOAuth2RequestFactory extends ConnectOAuth2RequestFactory { private final JWTProfileResolver profileResolver; private final Joiner joiner = Joiner.on(' '); - private final ClientDetailsEntityService clientDetailsService; + private final ClientDetailsService clientDetailsService; private final DeviceCodeService deviceCodeService; - private final AuthorizationCodeRepository authzCodeRepository; + private final IamAuthorizationCodeRepository authzCodeRepository; private final OAuth2TokenEntityService tokenServices; - public IamOAuth2RequestFactory(ClientDetailsEntityService clientDetailsService, - ScopeFilter scopeFilter, JWTProfileResolver profileResolver, - DeviceCodeService deviceCodeService, AuthorizationCodeRepository authzCodeRepository, - OAuth2TokenEntityService tokenServices) { + public IamOAuth2RequestFactory(ClientDetailsService clientDetailsService, ScopeFilter scopeFilter, + JWTProfileResolver profileResolver, DeviceCodeService deviceCodeService, + IamAuthorizationCodeRepository authzCodeRepository, OAuth2TokenEntityService tokenServices) { super(clientDetailsService); this.clientDetailsService = clientDetailsService; this.scopeFilter = scopeFilter; @@ -217,16 +216,16 @@ private Map updatedTokenRequestParameters( switch (grantType) { case AUTHZ_CODE_GRANT: - authzRequestParams = Optional - .ofNullable(authzCodeRepository.getByCode(tokenRequestParameters.get(AUTHZ_CODE_KEY))) - .map(AuthorizationCodeEntity::getAuthenticationHolder) - .map(holder -> holder.getRequestParameters()); + authzRequestParams = + authzCodeRepository.findByCode(tokenRequestParameters.get(AUTHZ_CODE_KEY)) + .map(AuthorizationCodeEntity::getAuthenticationHolder) + .map(holder -> holder.getRequestParameters()); break; case DEVICE_CODE_GRANT: authzRequestParams = Optional .ofNullable( - deviceCodeService.findDeviceCode(tokenRequestParameters.get(DEVICE_CODE_KEY), client)) + deviceCodeService.findByDeviceCode(tokenRequestParameters.get(DEVICE_CODE_KEY), client)) .map(DeviceCode::getAuthenticationHolder) .map(holder -> holder.getRequestParameters()); break; @@ -260,11 +259,8 @@ private Map updatedTokenRequestParameters( // Required by RT flow after device tokenRequestParameters.put(RESOURCE, arp.get(RESOURCE)); } - }); - return tokenRequestParameters; - } private void validateAndUpdateAudienceRequest(Map params) { @@ -281,7 +277,7 @@ private void validateAndUpdateAudienceRequest(Map params) { private String getFirstNotEmptyAudience(Map params) { return AUD_KEYS.stream() .map(params::get) - .filter(aud -> !isNullOrEmpty(aud)) + .filter(aud -> !Strings.isNullOrEmpty(aud)) .findFirst() .orElse(null); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthConfirmationController.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthConfirmationController.java index bd471562c3..c4a76b1f32 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthConfirmationController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthConfirmationController.java @@ -17,12 +17,11 @@ import static it.infn.mw.iam.core.oauth.IamOAuth2RequestFactory.RESOURCE; import static it.infn.mw.iam.core.oauth.IamOAuth2RequestFactory.splitBySpace; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.APPROVE_AUTHZ_PAGE; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.AUTHZ_CODE_URL; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.ERROR_STRING; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.STATE_PARAMETER_KEY; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.APPROVE_AUTHZ_PAGE; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.AUTHZ_CODE_URL; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.ERROR_STRING; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.STATE_PARAMETER_KEY; + import java.net.URISyntaxException; import java.util.List; @@ -31,10 +30,6 @@ import java.util.Set; import org.apache.http.client.utils.URIBuilder; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.view.HttpCodeView; -import org.mitre.openid.connect.view.JsonErrorView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -52,7 +47,11 @@ import com.google.common.base.Splitter; import com.google.common.base.Strings; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.core.web.view.HttpCodeView; +import it.infn.mw.iam.core.web.view.JsonErrorView; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @SuppressWarnings("deprecation") @Controller @@ -70,8 +69,9 @@ public class IamOAuthConfirmationController { private IamUserApprovalUtils userApprovalUtils; - public IamOAuthConfirmationController(IamClientRepository clientRepository, SystemScopeService scopeService, - RedirectResolver redirectResolver, IamUserApprovalUtils userApprovalUtils) { + public IamOAuthConfirmationController(IamClientRepository clientRepository, + SystemScopeService scopeService, RedirectResolver redirectResolver, + IamUserApprovalUtils userApprovalUtils) { this.clientRepository = clientRepository; this.scopeService = scopeService; @@ -85,8 +85,9 @@ public String confimAccess(Map model, @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest, Authentication authUser, SessionStatus status) { - String prompt = (String) authRequest.getExtensions().get(PROMPT); - List prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); + String prompt = (String) authRequest.getExtensions().get(IamOAuth2ParameterNames.PROMPT); + List prompts = Splitter.on(IamOAuth2ParameterNames.PROMPT_SEPARATOR) + .splitToList(Strings.nullToEmpty(prompt)); String clientId = authRequest.getClientId(); if (clientId == null || clientId.isBlank()) { @@ -153,10 +154,9 @@ private void setModelForConsentPage(Map model, AuthorizationRequ model.put("gras", userApprovalUtils.isSafeClient(count, client.getCreatedAt())); model.put("contacts", userApprovalUtils.getClientContactsAsString(client.getContacts())); - + if (authRequest.getRequestParameters().containsKey(RESOURCE)) { - model.put("resources", - splitBySpace(authRequest.getRequestParameters().get(RESOURCE))); + model.put("resources", splitBySpace(authRequest.getRequestParameters().get(RESOURCE))); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOauthRequestParameters.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthRequestParameters.java similarity index 89% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOauthRequestParameters.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthRequestParameters.java index 06b5ace5f3..bf67e0d4d2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOauthRequestParameters.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamOAuthRequestParameters.java @@ -15,10 +15,9 @@ */ package it.infn.mw.iam.core.oauth; -public abstract class IamOauthRequestParameters { +public abstract class IamOAuthRequestParameters { public static final String AUTHZ_CODE_URL = "/oauth/confirm_access"; - public static final String DEVICE_CODE_URL = "devicecode"; public static final String USER_CODE_URL = "device"; public static final String REQUEST_USER_CODE_STRING = "requestUserCode"; @@ -34,6 +33,8 @@ public abstract class IamOauthRequestParameters { public static final String APPROVAL_ATTRIBUTE_KEY = "approved"; - private IamOauthRequestParameters() {} + public static final String APPROVED_SITE = "approved_site"; + + private IamOAuthRequestParameters() {} } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalHandler.java index e21ae96052..82503529bd 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalHandler.java @@ -15,12 +15,8 @@ */ package it.infn.mw.iam.core.oauth; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.REMEMBER_PARAMETER_KEY; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.REMEMBER_PARAMETER_KEY; import static java.lang.String.valueOf; -import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_CONSENT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; import static org.springframework.security.oauth2.common.util.OAuth2Utils.USER_OAUTH_APPROVAL; import java.util.Calendar; @@ -33,14 +29,6 @@ import javax.servlet.http.HttpSession; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.model.ApprovedSite; -import org.mitre.openid.connect.model.WhitelistedSite; -import org.mitre.openid.connect.service.ApprovedSiteService; -import org.mitre.openid.connect.service.WhitelistedSiteService; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; @@ -54,13 +42,20 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.client.service.ClientService; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.core.oauth.approvedsite.ApprovedSiteService; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.WhitelistedSite; @SuppressWarnings("deprecation") @Component("iamUserApprovalHandler") public class IamUserApprovalHandler implements UserApprovalHandler { - private final ClientDetailsEntityService clientDetailsService; + private final IamClientDetailsService clientDetailsService; private final ApprovedSiteService approvedSiteService; private final WhitelistedSiteService whitelistedSiteService; private final SystemScopeService systemScopeService; @@ -69,7 +64,7 @@ public class IamUserApprovalHandler implements UserApprovalHandler { public static final String OIDC_AGENT_PREFIX_NAME = "oidc-agent:"; - public IamUserApprovalHandler(ClientDetailsEntityService clientDetailsService, + public IamUserApprovalHandler(IamClientDetailsService clientDetailsService, ApprovedSiteService approvedSiteService, WhitelistedSiteService whitelistedSiteService, SystemScopeService systemScopeService, AccountUtils accountUtils, ClientService clientService) { @@ -96,19 +91,25 @@ public boolean isApproved(AuthorizationRequest authorizationRequest, public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - String prompt = (String) authorizationRequest.getExtensions().get(PROMPT); - List prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); - if (prompts.contains(PROMPT_CONSENT)) { + String prompt = + (String) authorizationRequest.getExtensions().get(IamOAuth2ParameterNames.PROMPT); + List prompts = Splitter.on(IamOAuth2ParameterNames.PROMPT_SEPARATOR) + .splitToList(Strings.nullToEmpty(prompt)); + if (prompts.contains(IamOAuth2ParameterNames.PROMPT_CONSENT)) { return authorizationRequest; } - String userId = userAuthentication.getName(); - String clientId = authorizationRequest.getClientId(); + ClientDetailsEntity client = + clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); + IamAccount account = accountUtils.getAuthenticatedUserAccount(userAuthentication).orElseThrow(); + +// String userId = userAuthentication.getName(); +// String clientId = authorizationRequest.getClientId(); Set scopes = authorizationRequest.getScope(); boolean alreadyApproved = false; - Collection aps = approvedSiteService.getByClientIdAndUserId(clientId, userId); + Collection aps = approvedSiteService.getByClientAndUser(client, account); for (ApprovedSite ap : aps) { @@ -118,7 +119,8 @@ public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizati ap.setAccessDate(new Date()); approvedSiteService.save(ap); - authorizationRequest.getExtensions().put(APPROVED_SITE, valueOf(ap.getId())); + authorizationRequest.getExtensions() + .put(IamOAuth2ParameterNames.APPROVED_SITE, valueOf(ap.getId())); authorizationRequest.setApproved(true); alreadyApproved = true; @@ -127,7 +129,7 @@ public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizati } if (!alreadyApproved) { - WhitelistedSite ws = whitelistedSiteService.getByClientId(clientId); + WhitelistedSite ws = whitelistedSiteService.getByClientId(client.getClientId()); if (ws != null && systemScopeService.scopesMatch(ws.getAllowedScopes(), scopes)) { authorizationRequest.setApproved(true); @@ -142,9 +144,10 @@ public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizati public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { - String userId = userAuthentication.getName(); - String clientId = authorizationRequest.getClientId(); - ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientId); + ClientDetailsEntity client = + clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); + IamAccount account = accountUtils.getAuthenticatedUserAccount(userAuthentication).orElseThrow(); + Map approvalParams = authorizationRequest.getApprovalParameters(); if (!Boolean.parseBoolean(approvalParams.get(USER_OAUTH_APPROVAL))) { @@ -179,17 +182,15 @@ public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizati } ApprovedSite newSite = - approvedSiteService.createApprovedSite(clientId, userId, timeout, approvedScopes); + approvedSiteService.createApprovedSite(client, account, timeout, approvedScopes); String newSiteId = newSite.getId().toString(); - authorizationRequest.getExtensions().put(APPROVED_SITE, newSiteId); + authorizationRequest.getExtensions().put(IamOAuth2ParameterNames.APPROVED_SITE, newSiteId); } setAuthTime(authorizationRequest); - IamAccount account = accountUtils.getAuthenticatedUserAccount(userAuthentication).orElseThrow(); - if (client.getClientName().startsWith(OIDC_AGENT_PREFIX_NAME) - && clientService.findClientOwners(clientId, null).isEmpty()) { + && clientService.findClientOwners(client.getClientId(), null).isEmpty()) { clientService.linkClientToAccount(client, account); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalUtils.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalUtils.java index 77d0739f6d..b86b6b2284 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalUtils.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamUserApprovalUtils.java @@ -19,36 +19,40 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.model.UserInfo; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; -import org.mitre.openid.connect.service.StatsService; -import org.mitre.openid.connect.service.UserInfoService; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.stereotype.Component; import com.google.common.base.Joiner; import com.google.common.collect.Sets; -import com.google.gson.JsonObject; +import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; - +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.core.stats.StatsService; +import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.SystemScope; + +@SuppressWarnings("deprecation") @Component public class IamUserApprovalUtils { private final SystemScopeService scopeService; private final StatsService statsService; - private final UserInfoService userInfoService; + private final IamAccountService accountService; private final JWTProfileResolver profileResolver; public IamUserApprovalUtils(SystemScopeService scopeService, StatsService statsService, - UserInfoService userInfoService, JWTProfileResolver profileResolver) { + IamAccountService accountService, JWTProfileResolver profileResolver) { this.scopeService = scopeService; this.statsService = statsService; - this.userInfoService = userInfoService; + this.accountService = accountService; this.profileResolver = profileResolver; } @@ -68,28 +72,20 @@ public Set sortScopes(Set scopes) { return scopeService.toStrings(sortedScopes); } - public Map> claimsForScopes(Authentication authUser, + public Map> claimsForScopes(Authentication authUser, Set scopes) { - UserInfo user = userInfoService.getByUsername(authUser.getName()); + JWTProfile jwtProfile = profileResolver.resolveProfile(scopeService.toStrings(scopes)); + Optional account = accountService.findByUsername(authUser.getName()); ScopeClaimTranslationService scopeClaimTranslationService = - profileResolver.resolveProfile(scopeService.toStrings(scopes)) - .getScopeClaimTranslationService(); - - Map> claimsForScopes = new HashMap<>(); - if (user != null) { - JsonObject userJson = user.toJson(); + jwtProfile.getScopeClaimTranslationService(); + ClaimValueHelper claimValueHelper = jwtProfile.getClaimValueHelper(); + Map> claimsForScopes = new HashMap<>(); + if (account.isPresent()) { for (SystemScope systemScope : scopes) { - Map claimValues = new HashMap<>(); - Set claims = scopeClaimTranslationService.getClaimsForScope(systemScope.getValue()); - for (String claim : claims) { - if (userJson.has(claim) && userJson.get(claim).isJsonPrimitive()) { - claimValues.put(claim, userJson.get(claim).getAsString()); - } - } - + Map claimValues = claimValueHelper.resolveClaims(claims, authUser, account); claimsForScopes.put(systemScope.getValue(), claimValues); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamWhitelistedSiteService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamWhitelistedSiteService.java new file mode 100644 index 0000000000..7759af1094 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamWhitelistedSiteService.java @@ -0,0 +1,65 @@ +/** + * 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.oauth; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import it.infn.mw.iam.persistence.model.WhitelistedSite; +import it.infn.mw.iam.persistence.repository.IamWhitelistedSiteRepository; + +@Service +@Transactional +public class IamWhitelistedSiteService implements WhitelistedSiteService { + + @Autowired + private IamWhitelistedSiteRepository repository; + + @Override + public WhitelistedSite getById(Long id) { + return repository.getById(id); + } + + @Override + public void remove(WhitelistedSite whitelistedSite) { + repository.delete(whitelistedSite); + } + + @Override + public WhitelistedSite saveNew(WhitelistedSite whitelistedSite) { + if (whitelistedSite.getId() != null) { + throw new IllegalArgumentException( + "A new whitelisted site cannot be created with an id value already set: " + + whitelistedSite.getId()); + } + return repository.save(whitelistedSite); + } + + @Override + public Collection getAll() { + return repository.findAll(); + } + + @Override + public WhitelistedSite getByClientId(String clientId) { + return repository.findByClientId(clientId).orElse(null); + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/WhitelistedSiteService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/WhitelistedSiteService.java new file mode 100644 index 0000000000..b93ea9154b --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/WhitelistedSiteService.java @@ -0,0 +1,34 @@ +/** + * 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.oauth; + +import java.util.Collection; + +import it.infn.mw.iam.persistence.model.WhitelistedSite; + +public interface WhitelistedSiteService { + + public Collection getAll(); + + public WhitelistedSite getById(Long id); + + public WhitelistedSite getByClientId(String clientId); + + public void remove(WhitelistedSite whitelistedSite); + + public WhitelistedSite saveNew(WhitelistedSite whitelistedSite); + +} \ No newline at end of file diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/approvedsite/ApprovedSiteService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/approvedsite/ApprovedSiteService.java new file mode 100644 index 0000000000..4e2fbbd4db --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/approvedsite/ApprovedSiteService.java @@ -0,0 +1,54 @@ +/** + * 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.oauth.approvedsite; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; + +public interface ApprovedSiteService { + + public ApprovedSite createApprovedSite(ClientDetailsEntity client, IamAccount account, + Date timeoutDate, Set allowedScopes); + + public Collection getAll(); + + public Collection getByClientAndUser(ClientDetailsEntity client, + IamAccount account); + + public ApprovedSite save(ApprovedSite approvedSite); + + public ApprovedSite getById(Long id); + + public void remove(ApprovedSite approvedSite); + + public Collection getByUser(IamAccount account); + + public Collection getByClient(ClientDetailsEntity client); + + public void clearApprovedSitesForClient(ClientDetailsEntity client); + + public void clearExpiredSites(); + + public List getApprovedAccessTokens(ApprovedSite approvedSite); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/approvedsite/DefaultApprovedSiteService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/approvedsite/DefaultApprovedSiteService.java new file mode 100644 index 0000000000..629e14340b --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/approvedsite/DefaultApprovedSiteService.java @@ -0,0 +1,163 @@ +/** + * 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.oauth.approvedsite; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.core.stats.StatsService; +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.repository.IamApprovedSiteRepository; +import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; + +@Service +public class DefaultApprovedSiteService implements ApprovedSiteService { + + private static final Logger logger = LoggerFactory.getLogger(DefaultApprovedSiteService.class); + + @Autowired + private IamApprovedSiteRepository approvedSiteRepository; + + @Autowired + private TokenRevocationService revocationService; + + @Autowired + private IamOAuthAccessTokenRepository accessTokenRepository; + + @Autowired + private StatsService statsService; + + @Override + public Collection getAll() { + return approvedSiteRepository.findAll(); + } + + @Override + @Transactional(value = "defaultTransactionManager") + public ApprovedSite save(ApprovedSite approvedSite) { + + ApprovedSite a = approvedSiteRepository.saveAndFlush(approvedSite); + statsService.resetCache(); + return a; + } + + @Override + public ApprovedSite getById(Long id) { + return approvedSiteRepository.getById(id); + } + + @Override + @Transactional(value = "defaultTransactionManager") + public void remove(ApprovedSite approvedSite) { + + // Remove any associated access and refresh tokens + List accessTokens = getApprovedAccessTokens(approvedSite); + + for (OAuth2AccessTokenEntity token : accessTokens) { + if (token.getRefreshToken() != null) { + revocationService.revokeRefreshToken(token.getRefreshToken()); + } + revocationService.revokeAccessToken(token); + } + approvedSiteRepository.delete(approvedSite); + statsService.resetCache(); + } + + @Override + @Transactional(value = "defaultTransactionManager") + public ApprovedSite createApprovedSite(ClientDetailsEntity client, IamAccount account, Date timeoutDate, + Set allowedScopes) { + + ApprovedSite as = new ApprovedSite(); + + Date now = new Date(); + as.setCreationDate(now); + as.setAccessDate(now); + as.setClient(client); + as.setAccount(account); + as.setTimeoutDate(timeoutDate); + as.setAllowedScopes(allowedScopes); + + return save(as); + } + + @Override + public Collection getByClientAndUser(ClientDetailsEntity client, IamAccount account) { + + return approvedSiteRepository.findByClientIdAndUserId(client.getClientId(), account.getUsername()); + } + + @Override + public Collection getByUser(IamAccount account) { + + return approvedSiteRepository.findByUserId(account.getUsername()); + } + + @Override + public Collection getByClient(ClientDetailsEntity client) { + + return approvedSiteRepository.findByClientId(client.getClientId()); + } + + + @Override + public void clearApprovedSitesForClient(ClientDetailsEntity client) { + + Collection approvedSites = + approvedSiteRepository.findByClientId(client.getClientId()); + approvedSites.forEach(this::remove); + } + + @Override + public void clearExpiredSites() { + + logger.debug("Clearing expired approved sites"); + + Collection expiredSites = getExpired(); + if (expiredSites.size() > 0) { + logger.info("Found " + expiredSites.size() + " expired approved sites."); + } + if (expiredSites != null) { + for (ApprovedSite expired : expiredSites) { + remove(expired); + } + } + } + + private Collection getExpired() { + + return approvedSiteRepository.findExpired(); + } + + @Override + public List getApprovedAccessTokens(ApprovedSite approvedSite) { + + return accessTokenRepository.findByApprovedSiteId(approvedSite.getId()); + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/assertion/IAMJWTBearerAuthenticationProvider.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/assertion/IAMJWTBearerAuthenticationProvider.java index fc5f9581db..d8f854e238 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/assertion/IAMJWTBearerAuthenticationProvider.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/assertion/IAMJWTBearerAuthenticationProvider.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.core.oauth.assertion; -import static java.lang.String.format; import static java.util.Objects.isNull; import java.text.ParseException; @@ -26,12 +25,6 @@ import java.util.Optional; import java.util.Set; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.openid.connect.assertion.JWTBearerAssertionAuthenticationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationProvider; @@ -47,7 +40,13 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.authn.jwt.JwtBearerAssertionAuthenticationToken; import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.core.jwt.ClientKeyCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class IAMJWTBearerAuthenticationProvider implements AuthenticationProvider { @@ -61,13 +60,13 @@ public class IAMJWTBearerAuthenticationProvider implements AuthenticationProvide private static final String INVALID_SIGNATURE_ALGO = "Invalid signature algorithm: %s"; private final Clock clock; - private final ClientDetailsEntityService clientService; + private final IamClientDetailsService clientService; private final ClientKeyCacheService validators; private final String tokenEndpoint; public IAMJWTBearerAuthenticationProvider(Clock clock, IamProperties iamProperties, - ClientDetailsEntityService clientService, ClientKeyCacheService validators) { + IamClientDetailsService clientService, ClientKeyCacheService validators) { this.clock = clock; this.clientService = clientService; @@ -116,10 +115,10 @@ private void clientAuthMethodChecks(ClientDetailsEntity client, SignedJWT jws) { private void signatureChecks(ClientDetailsEntity client, SignedJWT jws) { JWSAlgorithm alg = jws.getHeader().getAlgorithm(); - JWTSigningAndValidationService validator = + JwtSigningAndValidationService validator = Optional.ofNullable(validators.getValidator(client, alg)) .orElseThrow(() -> new AuthenticationServiceException( - format("Unable to resolve validator for client '%s' and algorithm '%s'", + String.format("Unable to resolve validator for client '%s' and algorithm '%s'", client.getClientId(), alg.getName()))); if (!validator.validateSignature(jws)) { @@ -184,8 +183,8 @@ private void assertionChecks(ClientDetailsEntity client, SignedJWT jws) throws P @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - JWTBearerAssertionAuthenticationToken jwtAuth = - (JWTBearerAssertionAuthenticationToken) authentication; + JwtBearerAssertionAuthenticationToken jwtAuth = + (JwtBearerAssertionAuthenticationToken) authentication; ClientDetailsEntity client = clientService.loadClientByClientId(jwtAuth.getName()); @@ -217,7 +216,7 @@ public Authentication authenticate(Authentication authentication) throws Authent Set authorities = new HashSet<>(client.getAuthorities()); authorities.add(ROLE_CLIENT); - return new JWTBearerAssertionAuthenticationToken(jwt, authorities); + return new JwtBearerAssertionAuthenticationToken(jwt, authorities); } catch (ParseException e) { throw new AuthenticationServiceException("JWT parse error:" + e.getMessage(), e); @@ -227,7 +226,7 @@ public Authentication authenticate(Authentication authentication) throws Authent @Override public boolean supports(Class authentication) { - return JWTBearerAssertionAuthenticationToken.class.isAssignableFrom(authentication); + return JwtBearerAssertionAuthenticationToken.class.isAssignableFrom(authentication); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/code/IamAuthorizationCodeService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/code/IamAuthorizationCodeService.java new file mode 100644 index 0000000000..7f1debc708 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/code/IamAuthorizationCodeService.java @@ -0,0 +1,93 @@ +/** + * 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.oauth.code; + +import java.util.Date; + +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import it.infn.mw.iam.api.client.service.ClientService; +import it.infn.mw.iam.core.AuthenticationHolderService; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.AuthorizationCodeEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamAuthorizationCodeRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; +import it.infn.mw.iam.util.SimpleRandomValueStringGenerator; + +@SuppressWarnings("deprecation") +@Service +public class IamAuthorizationCodeService implements AuthorizationCodeServices { + + private static final SimpleRandomValueStringGenerator generator = new SimpleRandomValueStringGenerator(22); + + private final IamAuthorizationCodeRepository codeRepository; + private final ClientService clientService; + private final AuthenticationHolderService authenticationHolderService; + + /* 5 minutes expiration */ + private final int authCodeExpirationSeconds = 300; + + public IamAuthorizationCodeService(IamAuthorizationCodeRepository codeRepository, + ClientService clientService, + AuthenticationHolderService authenticationHolderService) { + + this.codeRepository = codeRepository; + this.clientService = clientService; + this.authenticationHolderService = authenticationHolderService; + } + + @Override + @Transactional + public String createAuthorizationCode(OAuth2Authentication authentication) { + + String code = generator.generate(); + + ClientDetailsEntity client = + clientService.findClientByClientId(authentication.getOAuth2Request().getClientId()) + .orElseThrow( + () -> new IllegalStateException("Invalid requesting client id: client not found")); + + AuthenticationHolderEntity authHolder = authenticationHolderService.create(client, authentication); + + Date expiration = new Date(System.currentTimeMillis() + (authCodeExpirationSeconds * 1000L)); + + AuthorizationCodeEntity entity = new AuthorizationCodeEntity(code, authHolder, expiration); + authHolder.addAuthorizationCode(entity); + + authenticationHolderService.save(authHolder); + return code; + } + + @Override + public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException { + + AuthorizationCodeEntity result = codeRepository.findByCode(code) + .orElseThrow( + () -> new InvalidGrantException("No authorization code found for value " + code)); + + OAuth2Authentication auth = result.getAuthenticationHolder().getAuthentication(); + + codeRepository.delete(result); + + return auth; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeCreationException.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeCreationException.java new file mode 100644 index 0000000000..595dd5809d --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeCreationException.java @@ -0,0 +1,36 @@ +/** + * 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.oauth.devicecode; + +public class DeviceCodeCreationException extends Exception { + + private static final long serialVersionUID = 8078568710169208466L; + + private String error; + + public DeviceCodeCreationException(String error, String message) { + super(message); + this.error = error; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeExpiredException.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeExpiredException.java new file mode 100644 index 0000000000..9c7c015991 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeExpiredException.java @@ -0,0 +1,33 @@ +/** + * 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.oauth.devicecode; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +@SuppressWarnings("deprecation") +public class DeviceCodeExpiredException extends OAuth2Exception { + + public DeviceCodeExpiredException(String msg) { + super(msg); + } + + private static final long serialVersionUID = -7078098692596870940L; + + @Override + public String getOAuth2ErrorCode() { + return "expired_token"; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeService.java new file mode 100644 index 0000000000..5b24e4f810 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/DeviceCodeService.java @@ -0,0 +1,44 @@ +/** + * 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.oauth.devicecode; + +import java.util.Map; +import java.util.Set; + +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Authentication; + +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.DeviceCode; + +@SuppressWarnings("deprecation") +public interface DeviceCodeService { + + public DeviceCode findByUserCode(String userCode); + + public DeviceCode approve(DeviceCode dc, OAuth2Authentication o2Auth); + + public DeviceCode findByDeviceCode(String deviceCode, ClientDetails client); + + public DeviceCode update(DeviceCode deviceCode); + + public void clearDeviceCode(String deviceCode, ClientDetails client); + + public DeviceCode createNew(Set requestedScopes, ClientDetailsEntity client, + Map parameters) throws DeviceCodeCreationException; + + public int clearExpired(); +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/IamDeviceCodeService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/IamDeviceCodeService.java new file mode 100644 index 0000000000..06ffdedb65 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/IamDeviceCodeService.java @@ -0,0 +1,120 @@ +/** + * 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.oauth.devicecode; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.springframework.data.domain.Page; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import it.infn.mw.iam.api.common.OffsetPageable; +import it.infn.mw.iam.core.AuthenticationHolderService; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.DeviceCode; +import it.infn.mw.iam.persistence.repository.IamDeviceCodeRepository; +import it.infn.mw.iam.util.SimpleRandomValueStringGenerator; + +@SuppressWarnings("deprecation") +@Service +public class IamDeviceCodeService implements DeviceCodeService { + + private final static SimpleRandomValueStringGenerator randomGenerator = new SimpleRandomValueStringGenerator(6); + + private IamDeviceCodeRepository dcRepository; + private AuthenticationHolderService authHolderService; + + public IamDeviceCodeService(IamDeviceCodeRepository dcRepository, AuthenticationHolderService authHolderService) { + + this.dcRepository = dcRepository; + this.authHolderService = authHolderService; + } + + @Override + public DeviceCode createNew(Set requestedScopes, ClientDetailsEntity client, + Map parameters) { + + // create a device code, should be big and random + String deviceCode = UUID.randomUUID().toString(); + + // create a user code, should be random but small, type-able, and always upper-case + String userCode = randomGenerator.generate().toUpperCase(); + + DeviceCode dc = new DeviceCode(deviceCode, userCode, requestedScopes, client, parameters); + + return dcRepository.save(dc); + } + + @Override + public DeviceCode findByUserCode(String userCode) { + + return dcRepository.findByUserCode(userCode.toUpperCase()).orElse(null); + } + + @Override + public DeviceCode approve(DeviceCode dc, OAuth2Authentication auth) { + + DeviceCode approvedDc = dcRepository.getById(dc.getId()); + approvedDc.approve(); + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(approvedDc.getClient(), auth); + authHolder.addDeviceCode(approvedDc); + approvedDc.setAuthenticationHolder(authHolder); + return authHolderService.save(authHolder).getDeviceCodes().stream().findFirst().get(); + } + + @Override + public DeviceCode findByDeviceCode(String deviceCode, ClientDetails client) { + + Optional dc = dcRepository.findByDeviceCode(deviceCode); + if (dc.isPresent() && dc.get().getClient().getClientId().equals(client.getClientId())) { + return dc.get(); + } + return null; + } + + @Override + @Transactional(value = "defaultTransactionManager") + public int clearExpired() { + + Page expired = dcRepository.findExpiredDeviceCode(new OffsetPageable(0, 100)); + int found = expired.getContent().size(); + expired.getContent().forEach(dcRepository::delete); + return found; + } + + @Override + public void clearDeviceCode(String deviceCode, ClientDetails client) { + + DeviceCode found = findByDeviceCode(deviceCode, client); + if (found != null) { + dcRepository.delete(found); + } + } + + @Override + public DeviceCode update(DeviceCode deviceCode) { + + return dcRepository.save(deviceCode); + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamDeviceEndpointController.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/IamDeviceEndpoint.java similarity index 76% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamDeviceEndpointController.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/IamDeviceEndpoint.java index 4a7e0c7b22..e98995ab80 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/IamDeviceEndpointController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/devicecode/IamDeviceEndpoint.java @@ -13,19 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.core.oauth; +package it.infn.mw.iam.core.oauth.devicecode; import static it.infn.mw.iam.core.oauth.IamOAuth2RequestFactory.RESOURCE; import static it.infn.mw.iam.core.oauth.IamOAuth2RequestFactory.splitBySpace; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.APPROVAL_ATTRIBUTE_KEY; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.APPROVE_DEVICE_PAGE; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.DEVICE_APPROVED_PAGE; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.DEVICE_CODE_URL; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.ERROR_STRING; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.REMEMBER_PARAMETER_KEY; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.REQUEST_USER_CODE_STRING; -import static it.infn.mw.iam.core.oauth.IamOauthRequestParameters.USER_CODE_URL; -import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.APPROVAL_ATTRIBUTE_KEY; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.APPROVED_SITE; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.APPROVE_DEVICE_PAGE; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.DEVICE_APPROVED_PAGE; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.ERROR_STRING; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.REMEMBER_PARAMETER_KEY; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.REQUEST_USER_CODE_STRING; +import static it.infn.mw.iam.core.oauth.IamOAuthRequestParameters.USER_CODE_URL; import java.net.URI; import java.net.URISyntaxException; @@ -39,26 +38,12 @@ import javax.servlet.http.HttpSession; import org.apache.http.client.utils.URIBuilder; -import org.mitre.oauth2.exception.DeviceCodeCreationException; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.repository.impl.DeviceCodeRepository; -import org.mitre.oauth2.service.DeviceCodeService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.oauth2.token.DeviceTokenGranter; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; -import org.mitre.openid.connect.view.HttpCodeView; -import org.mitre.openid.connect.view.JsonEntityView; -import org.mitre.openid.connect.view.JsonErrorView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -71,30 +56,45 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; +import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.AuthenticationHolderService; +import it.infn.mw.iam.core.oauth.IamUserApprovalUtils; +import it.infn.mw.iam.core.oauth.granters.IamDeviceCodeTokenGranter; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.core.web.view.HttpCodeView; +import it.infn.mw.iam.core.web.view.JsonEntityView; +import it.infn.mw.iam.core.web.view.JsonErrorView; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.DeviceCode; +import it.infn.mw.iam.persistence.model.SystemScope; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @SuppressWarnings("deprecation") @Controller -public class IamDeviceEndpointController { +public class IamDeviceEndpoint { - public static final Logger logger = LoggerFactory.getLogger(IamDeviceEndpointController.class); + public static final String URL = "/devicecode"; + + public static final Logger logger = LoggerFactory.getLogger(IamDeviceEndpoint.class); private final IamClientRepository clientRepository; private final SystemScopeService scopeService; - private final ConfigurationPropertiesBean config; + private final IamProperties config; private final DeviceCodeService deviceCodeService; private final OAuth2RequestFactory oAuth2RequestFactory; private final UserApprovalHandler iamUserApprovalHandler; private final IamUserApprovalUtils userApprovalUtils; - private final DeviceCodeRepository deviceCodeRepository; + private final AuthenticationHolderService authHolderService; private final ScopeFilter scopeFilter; - public IamDeviceEndpointController(IamClientRepository clientRepository, - SystemScopeService scopeService, ConfigurationPropertiesBean config, - DeviceCodeService deviceCodeService, OAuth2RequestFactory oAuth2RequestFactory, - UserApprovalHandler iamUserApprovalHandler, IamUserApprovalUtils userApprovalUtils, - DeviceCodeRepository deviceCodeRepository, ScopeFilter scopeFilter) { + public IamDeviceEndpoint(IamClientRepository clientRepository, SystemScopeService scopeService, + IamProperties config, DeviceCodeService deviceCodeService, + OAuth2RequestFactory oAuth2RequestFactory, UserApprovalHandler iamUserApprovalHandler, + IamUserApprovalUtils userApprovalUtils, AuthenticationHolderService authHolderService, + ScopeFilter scopeFilter) { + this.clientRepository = clientRepository; this.scopeService = scopeService; this.config = config; @@ -102,12 +102,11 @@ public IamDeviceEndpointController(IamClientRepository clientRepository, this.oAuth2RequestFactory = oAuth2RequestFactory; this.iamUserApprovalHandler = iamUserApprovalHandler; this.userApprovalUtils = userApprovalUtils; - this.deviceCodeRepository = deviceCodeRepository; + this.authHolderService = authHolderService; this.scopeFilter = scopeFilter; } - @PostMapping(value = "/" + DEVICE_CODE_URL, - consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + @PostMapping(value = URL, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public String requestDeviceCode(@RequestParam("client_id") String clientId, @RequestParam(required = false) String scope, @RequestParam Map parameters, @@ -122,7 +121,15 @@ public String requestDeviceCode(@RequestParam("client_id") String clientId, model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - checkAuthzGrant(client.get()); + Collection authorizedGrantTypes = client.get().getAuthorizedGrantTypes(); + if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty() + && !authorizedGrantTypes.contains(IamDeviceCodeTokenGranter.GRANT_TYPE)) { + model.put(HttpCodeView.CODE, HttpStatus.UNAUTHORIZED); + model.put(JsonErrorView.ERROR, "invalid_client"); + model.put(JsonErrorView.ERROR_MESSAGE, + "Unauthorized grant type: " + IamDeviceCodeTokenGranter.GRANT_TYPE); + return JsonErrorView.VIEWNAME; + } Set requestedScopes = OAuth2Utils.parseParameterList(scope); Set allowedScopes = client.get().getScope(); @@ -137,8 +144,7 @@ public String requestDeviceCode(@RequestParam("client_id") String clientId, } try { - DeviceCode dc = - deviceCodeService.createNewDeviceCode(requestedScopes, client.get(), parameters); + DeviceCode dc = deviceCodeService.createNew(requestedScopes, client.get(), parameters); Map response = new HashMap<>(); response.put("device_code", dc.getDeviceCode()); @@ -148,7 +154,7 @@ public String requestDeviceCode(@RequestParam("client_id") String clientId, response.put("expires_in", client.get().getDeviceCodeValiditySeconds()); } - if (config.isAllowCompleteDeviceCodeUri()) { + if (config.getDeviceCode().getAllowCompleteVerificationUri()) { URI verificationUriComplete = new URIBuilder(config.getIssuer() + USER_CODE_URL) .addParameter("user_code", dc.getUserCode()) .build(); @@ -158,7 +164,6 @@ public String requestDeviceCode(@RequestParam("client_id") String clientId, model.put(JsonEntityView.ENTITY, response); - return JsonEntityView.VIEWNAME; } catch (DeviceCodeCreationException dcce) { @@ -182,7 +187,7 @@ public String requestUserCode( @RequestParam(value = "user_code", required = false) String userCode, ModelMap model, HttpSession session, Authentication authn) { - if (!config.isAllowCompleteDeviceCodeUri() || userCode == null) { + if (!config.getDeviceCode().getAllowCompleteVerificationUri() || userCode == null) { return REQUEST_USER_CODE_STRING; } return readUserCode(userCode, model, session, authn); @@ -193,7 +198,7 @@ public String requestUserCode( public String readUserCode(@RequestParam("user_code") String userCode, ModelMap model, HttpSession session, Authentication authn) { - DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode); + DeviceCode dc = deviceCodeService.findByUserCode(userCode); if (dc == null) { model.addAttribute(ERROR_STRING, "noUserCode"); @@ -210,7 +215,7 @@ public String readUserCode(@RequestParam("user_code") String userCode, ModelMap return REQUEST_USER_CODE_STRING; } - ClientDetailsEntity client = clientRepository.findByClientId(dc.getClientId()) + ClientDetailsEntity client = clientRepository.findByClientId(dc.getClient().getClientId()) .orElseThrow(() -> new IllegalStateException("Stored device code client id not found")); AuthorizationRequest authorizationRequest = @@ -223,7 +228,7 @@ public String readUserCode(@RequestParam("user_code") String userCode, ModelMap Set sortedAndFilteredScopes = userApprovalUtils.sortScopes( scopeService.fromStrings(scopeFilter.filterScopes(authorizationRequest.getScope(), authn))); dc.setScope(sortedAndFilteredScopes); - deviceCodeRepository.save(dc); + deviceCodeService.update(dc); if (authorizationRequest.getExtensions().get(APPROVED_SITE) != null || authorizationRequest.isApproved()) { @@ -266,7 +271,7 @@ public String confirmAccess(@RequestParam("user_code") String userCode, return REQUEST_USER_CODE_STRING; } - ClientDetailsEntity client = clientRepository.findByClientId(dc.getClientId()) + ClientDetailsEntity client = clientRepository.findByClientId(dc.getClient().getClientId()) .orElseThrow(() -> new IllegalStateException("Stored device code client id not found")); model.put("client", client); @@ -288,13 +293,14 @@ public String confirmAccess(@RequestParam("user_code") String userCode, return DEVICE_APPROVED_PAGE; } - private void checkAuthzGrant(ClientDetailsEntity client) { - Collection authorizedGrantTypes = client.getAuthorizedGrantTypes(); - if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty() - && !authorizedGrantTypes.contains(DeviceTokenGranter.GRANT_TYPE)) { - throw new InvalidClientException("Unauthorized grant type: " + DeviceTokenGranter.GRANT_TYPE); - } - } +// private void checkAuthzGrant(ClientDetailsEntity client) { +// Collection authorizedGrantTypes = client.getAuthorizedGrantTypes(); +// if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty() +// && !authorizedGrantTypes.contains(IamDeviceCodeTokenGranter.GRANT_TYPE)) { +// throw new InvalidClientException( +// "Unauthorized grant type: " + IamDeviceCodeTokenGranter.GRANT_TYPE); +// } +// } private void setModelForConsentPage(ModelMap model, Authentication authn, DeviceCode dc, ClientDetailsEntity client) { @@ -321,11 +327,11 @@ private void setModelForConsentPage(ModelMap model, Authentication authn, Device private void approveDevice(DeviceCode dc, OAuth2Authentication o2Auth) { - dc.setApproved(true); - AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); - authHolder.setAuthentication(o2Auth); + dc.approve(); + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(dc.getClient(), o2Auth); + authHolder.addDeviceCode(dc); dc.setAuthenticationHolder(authHolder); - deviceCodeRepository.save(dc); + authHolderService.save(authHolder); } private void setAuthzRequestAfterApproval(AuthorizationRequest authorizationRequest, diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/exceptions/AuthorizationPendingException.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/exceptions/AuthorizationPendingException.java new file mode 100644 index 0000000000..f589f1caa3 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/exceptions/AuthorizationPendingException.java @@ -0,0 +1,33 @@ +/** + * 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.oauth.exceptions; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +@SuppressWarnings("deprecation") +public class AuthorizationPendingException extends OAuth2Exception { + + public AuthorizationPendingException(String msg) { + super(msg); + } + + private static final long serialVersionUID = -7078098692596870940L; + + @Override + public String getOAuth2ErrorCode() { + return "authorization_pending"; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/ChainedTokenGranter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/ChainedTokenGranter.java new file mode 100644 index 0000000000..d341492044 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/ChainedTokenGranter.java @@ -0,0 +1,98 @@ +/** + * 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.oauth.granters; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; + +import com.google.common.collect.Sets; + +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; + +@SuppressWarnings("deprecation") +public class ChainedTokenGranter extends AbstractTokenGranter { + + public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant_type:redelegate"; + + private OAuth2TokenEntityService tokenServices; + + public ChainedTokenGranter(OAuth2TokenEntityService tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + this.tokenServices = tokenServices; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, + TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException { + // read and load up the existing token + String incomingTokenValue = tokenRequest.getRequestParameters().get("token"); + OAuth2AccessTokenEntity incomingToken = tokenServices.readAccessToken(incomingTokenValue); + + // check for scoping in the request, can't up-scope with a chained request + Set approvedScopes = incomingToken.getScope(); + Set requestedScopes = tokenRequest.getScope(); + + if (requestedScopes == null) { + requestedScopes = new HashSet<>(); + } + + // do a check on the requested scopes -- if they exactly match the client scopes, they were + // probably shadowed by the token granter + if (client.getScope().equals(requestedScopes)) { + requestedScopes = new HashSet<>(); + } + + // if our scopes are a valid subset of what's allowed, we can continue + if (approvedScopes.containsAll(requestedScopes)) { + + if (requestedScopes.isEmpty()) { + // if there are no scopes, inherit the original scopes from the token + tokenRequest.setScope(approvedScopes); + } else { + // if scopes were asked for, give only the subset of scopes requested + // this allows safe downscoping + tokenRequest.setScope(Sets.intersection(requestedScopes, approvedScopes)); + } + + // NOTE: don't revoke the existing access token + + // create a new access token + OAuth2Authentication authentication = + new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), + incomingToken.getAuthenticationHolder().getAuthentication().getUserAuthentication()); + + return authentication; + + } else { + throw new InvalidScopeException("Invalid scope requested in chained request", approvedScopes); + } + + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamDeviceCodeTokenGranter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamDeviceCodeTokenGranter.java index 131734dcdf..1cdd170ee5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamDeviceCodeTokenGranter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamDeviceCodeTokenGranter.java @@ -18,10 +18,6 @@ import java.util.Collection; import java.util.Date; -import org.mitre.oauth2.exception.AuthorizationPendingException; -import org.mitre.oauth2.exception.DeviceCodeExpiredException; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.oauth2.service.DeviceCodeService; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.provider.ClientDetails; @@ -32,6 +28,11 @@ import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeExpiredException; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; +import it.infn.mw.iam.core.oauth.exceptions.AuthorizationPendingException; +import it.infn.mw.iam.persistence.model.DeviceCode; + @SuppressWarnings("deprecation") public class IamDeviceCodeTokenGranter extends AbstractTokenGranter { @@ -56,7 +57,7 @@ protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, String deviceCode = tokenRequest.getRequestParameters().get("device_code"); // look up the device code and consume it - DeviceCode dc = deviceCodeService.findDeviceCode(deviceCode, client); + DeviceCode dc = deviceCodeService.findByDeviceCode(deviceCode, client); if (dc == null) { throw new InvalidGrantException("Invalid device code: " + deviceCode); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamRefreshTokenGranter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamRefreshTokenGranter.java index e529a42b28..2f00228a6d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamRefreshTokenGranter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamRefreshTokenGranter.java @@ -19,8 +19,6 @@ import java.util.Optional; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.springframework.security.authentication.DisabledException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; @@ -31,20 +29,25 @@ import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter; import it.infn.mw.iam.api.account.AccountUtils; +import it.infn.mw.iam.core.OAuth2TokenEntityService; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.service.aup.AUPSignatureCheckService; @SuppressWarnings("deprecation") public class IamRefreshTokenGranter extends RefreshTokenGranter { private final OAuth2TokenEntityService tokenServices; - private AUPSignatureCheckService signatureCheckService; - private AccountUtils accountUtils; + private final AUPSignatureCheckService signatureCheckService; + private final AccountUtils accountUtils; public IamRefreshTokenGranter(OAuth2TokenEntityService tokenServices, - ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, + AUPSignatureCheckService signatureCheckService, AccountUtils accountUtils) { super(tokenServices, clientDetailsService, requestFactory); this.tokenServices = tokenServices; + this.signatureCheckService = signatureCheckService; + this.accountUtils = accountUtils; } @Override @@ -67,14 +70,5 @@ protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest to return getTokenServices().refreshAccessToken(refreshTokenValue, tokenRequest); } - - public void setSignatureCheckService(AUPSignatureCheckService signatureCheckService) { - this.signatureCheckService = signatureCheckService; - } - - public void setAccountUtils(AccountUtils accountUtils) { - this.accountUtils = accountUtils; - } - } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamResourceOwnerPasswordTokenGranter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamResourceOwnerPasswordTokenGranter.java index 9c3f7ee449..d5fbc920b2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamResourceOwnerPasswordTokenGranter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/IamResourceOwnerPasswordTokenGranter.java @@ -38,19 +38,16 @@ @SuppressWarnings("deprecation") public class IamResourceOwnerPasswordTokenGranter extends ResourceOwnerPasswordTokenGranter { - private AUPSignatureCheckService signatureCheckService; - private AccountUtils accountUtils; + private final AUPSignatureCheckService signatureCheckService; + private final AccountUtils accountUtils; public IamResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, - OAuth2RequestFactory requestFactory) { + OAuth2RequestFactory requestFactory, AUPSignatureCheckService signatureCheckService, + AccountUtils accountUtils) { super(authenticationManager, tokenServices, clientDetailsService, requestFactory); - } - - public IamResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, - AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, - OAuth2RequestFactory requestFactory, String grantType) { - super(authenticationManager, tokenServices, clientDetailsService, requestFactory, grantType); + this.signatureCheckService = signatureCheckService; + this.accountUtils = accountUtils; } @Override @@ -73,18 +70,10 @@ public AUPSignatureCheckService getSignatureCheckService() { return signatureCheckService; } - public void setSignatureCheckService(AUPSignatureCheckService signatureCheckService) { - this.signatureCheckService = signatureCheckService; - } - public AccountUtils getAccountUtils() { return accountUtils; } - public void setAccountUtils(AccountUtils accountUtils) { - this.accountUtils = accountUtils; - } - @Override protected void validateGrantType(String grantType, ClientDetails clientDetails) { Collection authorizedGrantTypes = clientDetails.getAuthorizedGrantTypes(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/TokenExchangeTokenGranter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/TokenExchangeTokenGranter.java index dc32a6b9b0..8be19d3684 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/TokenExchangeTokenGranter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/granters/TokenExchangeTokenGranter.java @@ -25,28 +25,27 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import it.infn.mw.iam.api.account.AccountUtils; +import it.infn.mw.iam.core.OAuth2TokenEntityService; import it.infn.mw.iam.core.oauth.exchange.TokenExchangePdp; import it.infn.mw.iam.core.oauth.exchange.TokenExchangePdpResult; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.service.aup.AUPSignatureCheckService; @SuppressWarnings("deprecation") @@ -66,17 +65,12 @@ public class TokenExchangeTokenGranter extends AbstractTokenGranter { private AUPSignatureCheckService signatureCheckService; private TokenExchangePdp exchangePdp; - - @Autowired public TokenExchangeTokenGranter(final OAuth2TokenEntityService tokenServices, - final ClientDetailsEntityService clientDetailsService, - final OAuth2RequestFactory requestFactory) { + final ClientDetailsService clientDetailsService, final OAuth2RequestFactory requestFactory) { super(tokenServices, clientDetailsService, requestFactory, TOKEN_EXCHANGE_GRANT_TYPE); this.tokenServices = tokenServices; } - - protected void validateExchange(final ClientDetails actorClient, final TokenRequest tokenRequest, OAuth2AccessTokenEntity subjectToken) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionEndpoint.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionEndpoint.java index f1ea186f61..79f0b93f6f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionEndpoint.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionEndpoint.java @@ -37,13 +37,15 @@ @RestController public class IamIntrospectionEndpoint { + public static final String URL = "/introspect"; + private IntrospectionService introspectionService; public IamIntrospectionEndpoint(IntrospectionService introspectionService) { this.introspectionService = introspectionService; } - @PostMapping(value = "/introspect", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}, + @PostMapping(value = URL, consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE}) @PreAuthorize("hasRole('ROLE_CLIENT')") public IntrospectionResponse introspect( diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionService.java index 3fb6fb5aab..6ab8de7397 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/introspection/IamIntrospectionService.java @@ -20,10 +20,6 @@ import java.util.Objects; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; @@ -40,11 +36,15 @@ import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.audit.events.tokens.IntrospectionEvent; +import it.infn.mw.iam.core.OAuth2TokenEntityService; import it.infn.mw.iam.core.oauth.exceptions.UnauthorizedClientException; import it.infn.mw.iam.core.oauth.introspection.model.IntrospectionResponse; import it.infn.mw.iam.core.oauth.introspection.model.TokenTypeHint; import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; @SuppressWarnings("deprecation") @Service diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/AccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/AccessTokenBuilder.java index 8f2e4b3186..8aab01ed94 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/AccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/AccessTokenBuilder.java @@ -19,12 +19,12 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.security.oauth2.provider.OAuth2Authentication; import com.nimbusds.jwt.JWTClaimsSet; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public interface AccessTokenBuilder { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ClaimValueHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ClaimValueHelper.java index 7cda478a63..9a0521ca23 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ClaimValueHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ClaimValueHelper.java @@ -19,11 +19,10 @@ import java.util.Optional; import java.util.Set; -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.core.Authentication; import it.infn.mw.iam.persistence.model.IamAccount; -@SuppressWarnings("deprecation") public interface ClaimValueHelper { /** @@ -34,7 +33,7 @@ public interface ClaimValueHelper { * @param account The user account info * @return the value of claim name */ - Object resolveClaim(String claimName, OAuth2Authentication auth, Optional account); + Object resolveClaim(String claimName, Authentication auth, Optional account); /** * Resolve claim names to a value (if available) @@ -44,7 +43,7 @@ public interface ClaimValueHelper { * @param account The user account info * @return the map of claim names and values */ - Map resolveClaims(Set claimNames, OAuth2Authentication auth, Optional account); + Map resolveClaims(Set claimNames, Authentication auth, Optional account); boolean isValidClaimValue(Object claimValue); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IDTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IDTokenCustomizer.java index 77b0ce1979..222aef5909 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IDTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IDTokenCustomizer.java @@ -15,13 +15,13 @@ */ package it.infn.mw.iam.core.oauth.profile; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.security.oauth2.provider.OAuth2Request; import com.nimbusds.jwt.JWTClaimsSet.Builder; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public interface IDTokenCustomizer { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamAccountIDTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamAccountIDTokenCustomizer.java index 3610f71f18..c44dfa2401 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamAccountIDTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamAccountIDTokenCustomizer.java @@ -15,18 +15,17 @@ */ package it.infn.mw.iam.core.oauth.profile; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.security.oauth2.provider.OAuth2Request; import com.nimbusds.jwt.JWTClaimsSet.Builder; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public interface IamAccountIDTokenCustomizer { - void customizeIdTokenClaims(Builder idClaims, ClientDetailsEntity client, OAuth2Request request, IamAccount account, OAuth2AccessTokenEntity accessToken); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamOIDCTokenService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamOIDCTokenService.java deleted file mode 100644 index 617c98978a..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamOIDCTokenService.java +++ /dev/null @@ -1,293 +0,0 @@ -/** - * 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.oauth.profile; - -import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE; - -import java.time.Clock; -import java.util.Date; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - -import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; -import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; -import org.mitre.openid.connect.service.OIDCTokenService; -import org.mitre.openid.connect.util.IdTokenHashUtils; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Primary; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.stereotype.Service; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.nimbusds.jose.Algorithm; -import com.nimbusds.jose.JWEHeader; -import com.nimbusds.jose.JWEObject; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.util.Base64URL; -import com.nimbusds.jwt.EncryptedJWT; -import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.JWTClaimsSet.Builder; -import com.nimbusds.jwt.SignedJWT; - -import it.infn.mw.iam.api.common.error.NoSuchAccountError; -import it.infn.mw.iam.authn.util.Authorities; -import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; -import it.infn.mw.iam.persistence.model.IamAccount; -import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; - -@Service -@Primary -@SuppressWarnings("deprecation") -public class IamOIDCTokenService implements OIDCTokenService { - - public static final Logger LOG = LoggerFactory.getLogger(IamOIDCTokenService.class); - - private final Clock clock; - private final JWTProfileResolver profileResolver; - private final IamAccountRepository accountRepository; - private final JWTSigningAndValidationService jwtService; - private final IamOAuthAccessTokenRepository accessTokenRepo; - private final AuthenticationHolderRepository authenticationHolderRepository; - private final TokenRevocationService revocationService; - private final ConfigurationPropertiesBean configBean; - private final ClientKeyCacheService encrypters; - private final SymmetricKeyJWTValidatorCacheService symmetricCacheService; - - public IamOIDCTokenService(Clock clock, JWTProfileResolver profileResolver, - IamAccountRepository accountRepository, JWTSigningAndValidationService jwtService, - IamOAuthAccessTokenRepository accessTokenRepo, - AuthenticationHolderRepository authenticationHolderRepository, - TokenRevocationService revocationService, ConfigurationPropertiesBean configBean, - ClientKeyCacheService encrypters, SymmetricKeyJWTValidatorCacheService symmetricCacheService) { - this.clock = clock; - this.profileResolver = profileResolver; - this.accountRepository = accountRepository; - this.jwtService = jwtService; - this.accessTokenRepo = accessTokenRepo; - this.authenticationHolderRepository = authenticationHolderRepository; - this.revocationService = revocationService; - this.configBean = configBean; - this.encrypters = encrypters; - this.symmetricCacheService = symmetricCacheService; - } - - - protected void addCustomIdTokenClaims(Builder idClaims, ClientDetailsEntity client, - OAuth2Request request, String sub, OAuth2AccessTokenEntity accessToken) { - - IamAccount account = - accountRepository.findByUuid(sub).orElseThrow(() -> NoSuchAccountError.forUuid(sub)); - - JWTProfile profile = profileResolver.resolveProfile(client.getScope(), accessToken.getScope()); - - profile.getIDTokenCustomizer() - .customizeIdTokenClaims(idClaims, client, request, sub, accessToken, account); - } - - - private void handleAuthTimestamp(ClientDetailsEntity client, OAuth2Request request, - JWTClaimsSet.Builder idClaims) { - - if (request.getExtensions().containsKey(MAX_AGE) - || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) { - - if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) { - Long authTimestamp = Long.parseLong( - (String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP)); - if (authTimestamp != null) { - idClaims.claim("auth_time", authTimestamp / 1000L); - } - } else { - LOG.debug("Unable to find authentication timestamp while creating ID token"); - } - } - - } - - @Override - public JWT createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, - String sub, OAuth2AccessTokenEntity accessToken) { - - JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); - - if (client.getIdTokenSignedResponseAlg() != null) { - signingAlg = client.getIdTokenSignedResponseAlg(); - } - - JWT idToken = null; - JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder(); - - idClaims.issuer(configBean.getIssuer()); - idClaims.subject(sub); - idClaims.audience(Lists.newArrayList(client.getClientId())); - idClaims.jwtID(UUID.randomUUID().toString()); - - idClaims.issueTime(issueTime); - handleAuthTimestamp(client, request, idClaims); - - if (client.getIdTokenValiditySeconds() != null) { - Date expiration = - new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); - idClaims.expirationTime(expiration); - } - - String nonce = (String) request.getExtensions().get(NONCE); - if (!Strings.isNullOrEmpty(nonce)) { - idClaims.claim("nonce", nonce); - } - - Set responseTypes = request.getResponseTypes(); - - if (responseTypes.contains("token")) { - Base64URL atHash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken); - idClaims.claim("at_hash", atHash); - } - - addCustomIdTokenClaims(idClaims, client, request, sub, accessToken); - - if (clientWantsEncryptedIdTokens(client)) { - - JWTEncryptionAndDecryptionService encrypter = - Optional.ofNullable(encrypters.getEncrypter(client)).orElseThrow(); - - JWEHeader header = new JWEHeader.Builder(client.getIdTokenEncryptedResponseAlg(), - client.getIdTokenEncryptedResponseEnc()).build(); - idToken = new EncryptedJWT(header, idClaims.build()); - encrypter.encryptJwt((JWEObject) idToken); - - } else { - - JWSHeader header = - new JWSHeader.Builder(signingAlg).keyID(jwtService.getDefaultSignerKeyId()).build(); - - if (JWSAlgorithm.Family.HMAC_SHA.contains(signingAlg)) { - idToken = new SignedJWT(header, idClaims.build()); - JWTSigningAndValidationService signer = - Optional.ofNullable(symmetricCacheService.getSymmetricValidtor(client)).orElseThrow(); - signer.signJwt((SignedJWT) idToken); - } else { - idClaims.claim("kid", jwtService.getDefaultSignerKeyId()); - idToken = new SignedJWT(header, idClaims.build()); - jwtService.signJwt((SignedJWT) idToken); - } - } - - return idToken; - } - - - private boolean clientWantsEncryptedIdTokens(ClientDetailsEntity client) { - return client.getIdTokenEncryptedResponseAlg() != null - && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) - && client.getIdTokenEncryptedResponseEnc() != null - && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) - && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null); - } - - - @Override - public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) { - return buildRegistrationAccessToken(client, - Sets.newHashSet(SystemScopeService.REGISTRATION_TOKEN_SCOPE)); - } - - - - @Override - public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client) { - return buildRegistrationAccessToken(client, - Sets.newHashSet(SystemScopeService.RESOURCE_TOKEN_SCOPE)); - } - - - - @Override - public OAuth2AccessTokenEntity rotateRegistrationAccessTokenForClient( - ClientDetailsEntity client) { - - Optional oldToken = accessTokenRepo.findRegistrationToken(client.getId()); - if (oldToken.isEmpty()) { - return null; - } - Set scope = oldToken.get().getScope(); - revocationService.revokeAccessToken(oldToken.get()); - return buildRegistrationAccessToken(client, scope); - } - - private OAuth2AccessTokenEntity buildRegistrationAccessToken(ClientDetailsEntity client, - Set scope) { - - Optional oldToken = accessTokenRepo.findRegistrationToken(client.getId()); - if (oldToken.isPresent()) { - revocationService.revokeAccessToken(oldToken.get()); - } - - Map authorizationParameters = Maps.newHashMap(); - OAuth2Request clientAuth = new OAuth2Request(authorizationParameters, client.getClientId(), - Sets.newHashSet(Authorities.ROLE_CLIENT), true, scope, null, null, null, null); - - OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null); - - OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); - token.setClient(client); - token.setScope(scope); - - AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); - authHolder.setAuthentication(authentication); - authHolder = authenticationHolderRepository.save(authHolder); - token.setAuthenticationHolder(authHolder); - - JWTClaimsSet claims = - new JWTClaimsSet.Builder().audience(Lists.newArrayList(client.getClientId())) - .issuer(configBean.getIssuer()) - .issueTime(Date.from(clock.instant())) - .jwtID(UUID.randomUUID().toString()) - .build(); - - JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); - JWSHeader header = - new JWSHeader.Builder(signingAlg).keyID(jwtService.getDefaultSignerKeyId()).build(); - SignedJWT signed = new SignedJWT(header, claims); - - jwtService.signJwt(signed); - - token.setJwt(signed); - token.hashMe(); - - return token; - - } -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamTokenEnhancer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamTokenEnhancer.java deleted file mode 100644 index 851bea7cc2..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IamTokenEnhancer.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * 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.oauth.profile; - -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.Map; -import java.util.Optional; - -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.service.OIDCTokenService; -import org.mitre.openid.connect.token.ConnectTokenEnhancer; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.security.oauth2.provider.TokenRequest; - -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; - -import it.infn.mw.iam.api.client.service.ClientService; -import it.infn.mw.iam.core.user.IamAccountService; -import it.infn.mw.iam.persistence.model.IamAccount; - -@SuppressWarnings("deprecation") -public class IamTokenEnhancer extends ConnectTokenEnhancer { - - public static final String EXPIRES_IN_KEY = "expires_in"; - - public static final String INVALID_PARAMETER = "Value of 'expires_in' parameter is not valid"; - - private IamAccountService accountService; - private ClientService clientService; - private OIDCTokenService connectTokenService; - private JWTProfileResolver profileResolver; - private Clock clock; - - public IamTokenEnhancer(Clock clock, IamAccountService accountService, ClientService clientService, - OIDCTokenService connectTokenService, JWTProfileResolver profileResolver) { - - this.clock = clock; - this.accountService = accountService; - this.clientService = clientService; - this.connectTokenService = connectTokenService; - this.profileResolver = profileResolver; - } - - private SignedJWT signClaims(JWTClaimsSet claims) { - JWSAlgorithm signingAlg = getJwtService().getDefaultSigningAlgorithm(); - - JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, - null, getJwtService().getDefaultSignerKeyId(), null, null); - SignedJWT signedJWT = new SignedJWT(header, claims); - - getJwtService().signJwt(signedJWT); - return signedJWT; - - } - - private Date ensureValidExpiration(Map requestParameters, - OAuth2AccessTokenEntity token, Instant tokenIssueInstant) { - try { - Integer expiresIn = Integer.valueOf(requestParameters.get(EXPIRES_IN_KEY)); - Integer validExp = token.getClient().getAccessTokenValiditySeconds(); - if (expiresIn >= 0) { - validExp = Math.min(expiresIn, token.getClient().getAccessTokenValiditySeconds()); - } - return Date.from(tokenIssueInstant.plus(validExp, ChronoUnit.SECONDS)); - } catch (NumberFormatException e) { - throw new InvalidRequestException(INVALID_PARAMETER); - } - } - - private Date computeExpTime(OAuth2Authentication authentication, OAuth2AccessTokenEntity token, - Instant tokenIssueInstant) { - - OAuth2Request originalRequest = authentication.getOAuth2Request(); - if (originalRequest.isRefresh()) { - TokenRequest refreshRequest = originalRequest.getRefreshTokenRequest(); - if (refreshRequest.getRequestParameters().containsKey(EXPIRES_IN_KEY)) { - return ensureValidExpiration(refreshRequest.getRequestParameters(), token, - tokenIssueInstant); - } - // don't use custom value from original request - return Date.from(tokenIssueInstant.plus(token.getClient().getAccessTokenValiditySeconds(), - ChronoUnit.SECONDS)); - } - if (originalRequest.getRequestParameters().containsKey(EXPIRES_IN_KEY)) { - return ensureValidExpiration(originalRequest.getRequestParameters(), token, - tokenIssueInstant); - } - return token.getExpiration(); - } - - @Override - public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, - OAuth2Authentication authentication) { - - OAuth2AccessTokenEntity accessTokenEntity = (OAuth2AccessTokenEntity) accessToken; - - OAuth2Request originalAuthRequest = authentication.getOAuth2Request(); - - String clientId = originalAuthRequest.getClientId(); - - ClientDetailsEntity client = clientService.findClientByClientId(clientId) - .orElseThrow(() -> OAuth2Exception.create(OAuth2Exception.INVALID_CLIENT, - "Invalid client id " + clientId)); - - Instant tokenIssueInstant = clock.instant(); - - JWTProfile profile = - profileResolver.resolveProfile(client.getScope(), originalAuthRequest.getScope()); - - accessTokenEntity - .setExpiration(computeExpTime(authentication, accessTokenEntity, tokenIssueInstant)); - - Optional account = Optional.empty(); - if (!authentication.isClientOnly()) { - String username = authentication.getName(); - account = accountService.findByUsername(username); - } - - JWTClaimsSet atClaims = profile.getAccessTokenBuilder() - .buildAccessToken(accessTokenEntity, authentication, account, tokenIssueInstant); - - accessTokenEntity.setJwt(signClaims(atClaims)); - accessTokenEntity.hashMe(); - - /** - * Authorization request scope MUST include "openid" in OIDC, but access token request may or - * may not include the scope parameter. As long as the AuthorizationRequest has the proper - * scope, we can consider this a valid OpenID Connect request. Otherwise, we consider it to be a - * vanilla OAuth2 request. - * - * Also, there must be a user authentication involved in the request for it to be considered - * OIDC and not OAuth, so we check for that as well. - */ - if (originalAuthRequest.getScope().contains(SystemScopeService.OPENID_SCOPE) - && account.isPresent()) { - - JWT idToken = connectTokenService.createIdToken(client, originalAuthRequest, - Date.from(tokenIssueInstant), account.get().getUuid(), accessTokenEntity); - - accessTokenEntity.setIdToken(idToken); - } - - return accessTokenEntity; - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IntrospectionResultHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IntrospectionResultHelper.java index 1ada62e79e..cc88a1ca42 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IntrospectionResultHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/IntrospectionResultHelper.java @@ -17,9 +17,9 @@ import java.util.Map; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; public interface IntrospectionResultHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/JWTProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/JWTProfile.java index cebeed6951..31833fa003 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/JWTProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/JWTProfile.java @@ -15,8 +15,6 @@ */ package it.infn.mw.iam.core.oauth.profile; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - public interface JWTProfile { String id(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ScopeClaimTranslationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ScopeClaimTranslationService.java new file mode 100644 index 0000000000..a1ce6c7b55 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/ScopeClaimTranslationService.java @@ -0,0 +1,26 @@ +/** + * 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.oauth.profile; + +import java.util.Set; + +public interface ScopeClaimTranslationService { + + public Set getClaimsForScope(String scope); + + public Set getClaimsForScopeSet(Set scopes); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcAccessTokenBuilder.java index 430dcf966c..8c24149089 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcAccessTokenBuilder.java @@ -18,25 +18,16 @@ import static it.infn.mw.iam.core.oauth.profile.iam.IamExtraClaimNames.ATTR; import static it.infn.mw.iam.core.oauth.profile.iam.IamExtraClaimNames.ORGANISATION_NAME; import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.EMAIL; -import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.NAME; import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.PREFERRED_USERNAME; -import java.time.Instant; -import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; - -import com.nimbusds.jwt.JWTClaimsSet; - import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseAccessTokenBuilder; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; -import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; @SuppressWarnings("deprecation") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcClaimValueHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcClaimValueHelper.java index d0b61da997..7a472d3e82 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcClaimValueHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcClaimValueHelper.java @@ -22,18 +22,19 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.SavedUserAuthentication; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import it.infn.mw.iam.api.scim.converter.SshKeyConverter; import it.infn.mw.iam.authn.util.AuthenticationUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.attributes.AttributeMapHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamClaimValueHelper; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamGroup; import it.infn.mw.iam.persistence.model.IamUserInfo; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; @SuppressWarnings("deprecation") public class AarcClaimValueHelper extends IamClaimValueHelper { @@ -78,7 +79,7 @@ private String encodeGroup(IamGroup group) { } @Override - public Object resolveClaim(String claimName, OAuth2Authentication auth, + public Object resolveClaim(String claimName, Authentication auth, Optional account) { final String SCOPED_FORMAT = "%s@%s"; @@ -98,21 +99,23 @@ public Object resolveClaim(String claimName, OAuth2Authentication auth, return format(SCOPED_FORMAT, DEFAULT_AFFILIATION_TYPE, properties.getOrganisation().getName()); case AarcExtraClaimNames.VOPERSON_EXTERNAL_AFFILIATION: - Optional userAuth = - AuthenticationUtils.getExternalAuthenticationInfo(auth.getUserAuthentication()); - if (userAuth.isPresent()) { - Set scopedAffiliations = new HashSet<>(); - if (account.get().getUserInfo().getAffiliation() != null) { - scopedAffiliations - .add(format(SCOPED_FORMAT, account.get().getUserInfo().getAffiliation(), - properties.getOrganisation().getName())); + if (auth instanceof OAuth2Authentication oAuth2) { + Optional userAuth = + AuthenticationUtils.getExternalAuthenticationInfo(oAuth2.getUserAuthentication()); + if (userAuth.isPresent()) { + Set scopedAffiliations = new HashSet<>(); + if (account.get().getUserInfo().getAffiliation() != null) { + scopedAffiliations + .add(format(SCOPED_FORMAT, account.get().getUserInfo().getAffiliation(), + properties.getOrganisation().getName())); + } + String externalScopedAffiliation = firstOf(userAuth.get().getAdditionalInfo(), + Set.of("VPSA", "voPersonScopedAffiliation", "urn:oid:1.3.6.1.4.1.34998.3.3.1.12")); + if (externalScopedAffiliation != null) { + scopedAffiliations.add(externalScopedAffiliation); + } + return scopedAffiliations; } - String externalScopedAffiliation = firstOf(userAuth.get().getAdditionalInfo(), - Set.of("VPSA", "voPersonScopedAffiliation", "urn:oid:1.3.6.1.4.1.34998.3.3.1.12")); - if (externalScopedAffiliation != null) { - scopedAffiliations.add(externalScopedAffiliation); - } - return scopedAffiliations; } return null; default: diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIdTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIdTokenCustomizer.java index 579862ab7c..53f6acc0e8 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIdTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIdTokenCustomizer.java @@ -17,17 +17,17 @@ import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Request; import com.nimbusds.jwt.JWTClaimsSet.Builder; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseIdTokenCustomizer; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public class AarcIdTokenCustomizer extends BaseIdTokenCustomizer { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIntrospectionHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIntrospectionHelper.java index 97525d7e86..2597b7b529 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIntrospectionHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcIntrospectionHelper.java @@ -18,16 +18,15 @@ import java.util.Map; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; - import com.nimbusds.jwt.JWTClaimsSet; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.common.BaseIntrospectionHelper; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; public class AarcIntrospectionHelper extends BaseIntrospectionHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcJWTProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcJWTProfile.java index 2c9997846e..87b1fe3b9c 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcJWTProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcJWTProfile.java @@ -15,13 +15,13 @@ */ package it.infn.mw.iam.core.oauth.profile.aarc; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Request; import it.infn.mw.iam.core.oauth.profile.AccessTokenBuilder; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.IDTokenCustomizer; import it.infn.mw.iam.core.oauth.profile.IntrospectionResultHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.UserInfoHelper; import it.infn.mw.iam.core.oauth.profile.common.BaseJWTProfile; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcUserinfoHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcUserinfoHelper.java index a5fe71348e..95985b22f7 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcUserinfoHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/aarc/AarcUserinfoHelper.java @@ -17,10 +17,9 @@ import java.util.Set; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseUserinfoHelper; public class AarcUserinfoHelper extends BaseUserinfoHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseAccessTokenBuilder.java index 05bf942ea5..e358ee3a2e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseAccessTokenBuilder.java @@ -38,9 +38,6 @@ import java.util.UUID; import java.util.stream.Stream; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.SavedUserAuthentication; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; @@ -59,8 +56,11 @@ import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.AccessTokenBuilder; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; @SuppressWarnings("deprecation") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseClaimValueHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseClaimValueHelper.java index 046c46546f..363e12303a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseClaimValueHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseClaimValueHelper.java @@ -21,15 +21,14 @@ import java.util.Optional; import java.util.Set; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import org.springframework.security.oauth2.provider.OAuth2Authentication; import com.nimbusds.jwt.JWTClaimNames; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.persistence.model.IamAccount; -@SuppressWarnings("deprecation") public abstract class BaseClaimValueHelper implements ClaimValueHelper { protected static final Set OPENID_CLAIMS = Set.of(StandardClaimNames.SUB); @@ -50,8 +49,8 @@ public abstract class BaseClaimValueHelper implements ClaimValueHelper { Set.of(StandardClaimNames.PHONE_NUMBER, StandardClaimNames.PHONE_NUMBER_VERIFIED); @Override - public Map resolveClaims(Set claimNames, - OAuth2Authentication auth, Optional account) { + public Map resolveClaims(Set claimNames, Authentication auth, + Optional account) { Map claims = new HashMap<>(); claimNames.forEach( @@ -60,7 +59,7 @@ public Map resolveClaims(Set claimNames, } @Override - public Object resolveClaim(String claimName, OAuth2Authentication auth, Optional account) { + public Object resolveClaim(String claimName, Authentication auth, Optional account) { if (account.isEmpty()) { return null; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIdTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIdTokenCustomizer.java index 2309a7bf89..a9f0ef681e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIdTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIdTokenCustomizer.java @@ -29,9 +29,6 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -43,7 +40,10 @@ import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.IDTokenCustomizer; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public abstract class BaseIdTokenCustomizer implements IDTokenCustomizer { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIntrospectionHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIntrospectionHelper.java index e3adff515d..c2d04128ac 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIntrospectionHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseIntrospectionHelper.java @@ -34,9 +34,6 @@ import java.util.Map; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +43,10 @@ import it.infn.mw.iam.core.oauth.introspection.model.TokenTypeHint; import it.infn.mw.iam.core.oauth.profile.IntrospectionResultHelper; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; public abstract class BaseIntrospectionHelper implements IntrospectionResultHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseJWTProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseJWTProfile.java index b7df8c8d5c..78d7018e6d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseJWTProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseJWTProfile.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.core.oauth.profile.common; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Request; import it.infn.mw.iam.core.oauth.profile.AccessTokenBuilder; @@ -24,6 +23,7 @@ import it.infn.mw.iam.core.oauth.profile.IntrospectionResultHelper; import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.RequestValidator; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.UserInfoHelper; public abstract class BaseJWTProfile implements JWTProfile, RequestValidator { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseScopeClaimTranslationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseScopeClaimTranslationService.java index 7db5af79a8..88c7da83d4 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseScopeClaimTranslationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseScopeClaimTranslationService.java @@ -18,12 +18,13 @@ import java.util.Set; import java.util.stream.Collectors; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; import com.google.common.collect.Sets; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; + public class BaseScopeClaimTranslationService implements ScopeClaimTranslationService { @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseUserinfoHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseUserinfoHelper.java index c34d4cb929..3c4102491b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseUserinfoHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/common/BaseUserinfoHelper.java @@ -20,13 +20,13 @@ import java.util.Optional; import java.util.Set; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import com.nimbusds.jwt.JWTClaimNames; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.UserInfoHelper; import it.infn.mw.iam.persistence.model.IamAccount; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamAccessTokenBuilder.java index 2c8f146879..a220e3ce8f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamAccessTokenBuilder.java @@ -24,11 +24,10 @@ import java.util.Set; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseAccessTokenBuilder; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamClaimValueHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamClaimValueHelper.java index 3bf7cfdc6d..f7e54c1f5f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamClaimValueHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamClaimValueHelper.java @@ -28,8 +28,7 @@ import java.util.Set; import java.util.stream.Collectors; -import org.mitre.oauth2.model.SavedUserAuthentication; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import it.infn.mw.iam.api.scim.converter.SshKeyConverter; @@ -37,10 +36,12 @@ import it.infn.mw.iam.authn.util.AuthenticationUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.attributes.AttributeMapHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseClaimValueHelper; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamGroup; import it.infn.mw.iam.persistence.model.IamSshKey; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; @SuppressWarnings("deprecation") public class IamClaimValueHelper extends BaseClaimValueHelper { @@ -59,8 +60,7 @@ public IamClaimValueHelper(IamProperties properties, SshKeyConverter sshConverte } @Override - public Object resolveClaim(String claimName, OAuth2Authentication auth, - Optional account) { + public Object resolveClaim(String claimName, Authentication auth, Optional account) { switch (claimName) { case ORGANISATION_NAME: @@ -80,10 +80,12 @@ public Object resolveClaim(String claimName, OAuth2Authentication auth, ? attrHelper.getAttributeMapFromUserInfo(account.get().getUserInfo()) : null; case EXTERNAL_AUTHN: - Optional userAuth = - AuthenticationUtils.getExternalAuthenticationInfo(auth.getUserAuthentication()); - if (userAuth.isPresent()) { - return userAuth.get().getAdditionalInfo(); + if (auth instanceof OAuth2Authentication oauth2) { + Optional userAuth = + AuthenticationUtils.getExternalAuthenticationInfo(oauth2.getUserAuthentication()); + if (userAuth.isPresent()) { + return userAuth.get().getAdditionalInfo(); + } } return null; default: diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIdTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIdTokenCustomizer.java index d06135123a..d81bea16ca 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIdTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIdTokenCustomizer.java @@ -19,9 +19,6 @@ import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Request; import com.nimbusds.jwt.JWTClaimsSet.Builder; @@ -29,9 +26,12 @@ import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.config.IamTokenEnhancerProperties.IncludeLabelProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseIdTokenCustomizer; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamLabel; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public class IamIdTokenCustomizer extends BaseIdTokenCustomizer { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIntrospectionHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIntrospectionHelper.java index 533d4bfb02..dafbc2f522 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIntrospectionHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamIntrospectionHelper.java @@ -23,14 +23,14 @@ import java.util.stream.Collectors; import org.apache.tomcat.util.buf.StringUtils; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.core.oauth.profile.common.BaseIntrospectionHelper; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountGroupMembership; import it.infn.mw.iam.persistence.model.IamGroup; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; public class IamIntrospectionHelper extends BaseIntrospectionHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamJWTProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamJWTProfile.java index 37f4e27353..6547b6ec2f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamJWTProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamJWTProfile.java @@ -15,12 +15,11 @@ */ package it.infn.mw.iam.core.oauth.profile.iam; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.core.oauth.profile.AccessTokenBuilder; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.IDTokenCustomizer; import it.infn.mw.iam.core.oauth.profile.IntrospectionResultHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.UserInfoHelper; import it.infn.mw.iam.core.oauth.profile.common.BaseJWTProfile; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamUserinfoHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamUserinfoHelper.java index bcf783b433..c74280e00e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamUserinfoHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/iam/IamUserinfoHelper.java @@ -21,12 +21,12 @@ import java.util.Optional; import java.util.Set; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.config.IamTokenEnhancerProperties.IncludeLabelProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseUserinfoHelper; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamLabel; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakAccessTokenBuilder.java index f89ce4957f..733902920a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakAccessTokenBuilder.java @@ -19,8 +19,6 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import com.nimbusds.jwt.JWTClaimsSet; @@ -28,9 +26,11 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseAccessTokenBuilder; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; @SuppressWarnings("deprecation") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakClaimValueHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakClaimValueHelper.java index 1bb77cf265..3ca4bb72e2 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakClaimValueHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakClaimValueHelper.java @@ -17,17 +17,16 @@ import java.util.Optional; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.core.Authentication; import it.infn.mw.iam.api.scim.converter.SshKeyConverter; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.attributes.AttributeMapHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.iam.IamExtraClaimNames; import it.infn.mw.iam.persistence.model.IamAccount; -@SuppressWarnings("deprecation") public class KeycloakClaimValueHelper extends IamClaimValueHelper { public KeycloakClaimValueHelper(IamProperties properties, SshKeyConverter sshConverter, @@ -36,8 +35,7 @@ public KeycloakClaimValueHelper(IamProperties properties, SshKeyConverter sshCon } @Override - public Object resolveClaim(String claimName, OAuth2Authentication auth, - Optional account) { + public Object resolveClaim(String claimName, Authentication auth, Optional account) { if (KeycloakExtraClaimNames.ROLES.equals(claimName)) { if (account.isPresent()) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIdTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIdTokenCustomizer.java index 820d1e944c..7bf7d3fee6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIdTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIdTokenCustomizer.java @@ -15,10 +15,9 @@ */ package it.infn.mw.iam.core.oauth.profile.keycloak; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamIdTokenCustomizer; public class KeycloakIdTokenCustomizer extends IamIdTokenCustomizer { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIntrospectionHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIntrospectionHelper.java index d4304f2313..33c2baa2f5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIntrospectionHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakIntrospectionHelper.java @@ -21,12 +21,11 @@ import java.util.Map; import java.util.Set; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; - import it.infn.mw.iam.core.oauth.profile.common.BaseIntrospectionHelper; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; public class KeycloakIntrospectionHelper extends BaseIntrospectionHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakJWTProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakJWTProfile.java index 4a6ef6e6d8..39e573bc45 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakJWTProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakJWTProfile.java @@ -15,12 +15,11 @@ */ package it.infn.mw.iam.core.oauth.profile.keycloak; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.core.oauth.profile.AccessTokenBuilder; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.IDTokenCustomizer; import it.infn.mw.iam.core.oauth.profile.IntrospectionResultHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.UserInfoHelper; import it.infn.mw.iam.core.oauth.profile.common.BaseJWTProfile; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakUserinfoHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakUserinfoHelper.java index 10b0b22c7d..81b68e25c3 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakUserinfoHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/keycloak/KeycloakUserinfoHelper.java @@ -15,10 +15,9 @@ */ package it.infn.mw.iam.core.oauth.profile.keycloak; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseUserinfoHelper; public class KeycloakUserinfoHelper extends BaseUserinfoHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgAccessTokenBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgAccessTokenBuilder.java index 06d703dcfc..6b3283f5e5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgAccessTokenBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgAccessTokenBuilder.java @@ -28,8 +28,6 @@ import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import com.nimbusds.jwt.JWTClaimNames; @@ -39,9 +37,11 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.common.BaseAccessTokenBuilder; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; @SuppressWarnings("deprecation") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgClaimValueHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgClaimValueHelper.java index 4c074b05f3..0f6ba34f40 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgClaimValueHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgClaimValueHelper.java @@ -22,12 +22,13 @@ import java.util.Optional; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import it.infn.mw.iam.api.scim.converter.SshKeyConverter; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.attributes.AttributeMapHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamClaimValueHelper; import it.infn.mw.iam.persistence.model.IamAccount; @@ -40,15 +41,17 @@ public WlcgClaimValueHelper(IamProperties properties, SshKeyConverter sshConvert } @Override - public Object resolveClaim(String claimName, OAuth2Authentication auth, - Optional account) { + public Object resolveClaim(String claimName, Authentication auth, Optional account) { switch (claimName) { case WLCG_VER: return WlcgJWTProfile.PROFILE_VERSION; case WLCG_GROUPS: - return account.isPresent() ? WlcgGroupHelper.resolveGroupNames(auth.getOAuth2Request().getScope(), - account.get().getUserInfo().getGroups()) : null; + if (account.isPresent() && auth instanceof OAuth2Authentication oAuth2) { + return WlcgGroupHelper.resolveGroupNames(oAuth2.getOAuth2Request().getScope(), + account.get().getUserInfo().getGroups()); + } + return null; case AUTH_TIME: return account.isPresent() && account.get().getLastLoginTime() != null ? account.get().getLastLoginTime().getTime() / 1000 diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgGroupHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgGroupHelper.java index e632537650..4c7caade89 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgGroupHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgGroupHelper.java @@ -24,13 +24,13 @@ import java.util.regex.Pattern; import java.util.stream.Stream; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.provider.OAuth2Request; import it.infn.mw.iam.persistence.model.IamGroup; import it.infn.mw.iam.persistence.model.IamLabel; import it.infn.mw.iam.persistence.model.IamUserInfo; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") public class WlcgGroupHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIdTokenCustomizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIdTokenCustomizer.java index 9d3cfc99f1..534757dd08 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIdTokenCustomizer.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIdTokenCustomizer.java @@ -15,10 +15,9 @@ */ package it.infn.mw.iam.core.oauth.profile.wlcg; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; - import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamIdTokenCustomizer; public class WlcgIdTokenCustomizer extends IamIdTokenCustomizer { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIntrospectionHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIntrospectionHelper.java index 39f8193962..c44ec473f6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIntrospectionHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgIntrospectionHelper.java @@ -21,12 +21,11 @@ import java.util.Map; import java.util.Set; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; - import it.infn.mw.iam.core.oauth.profile.common.BaseIntrospectionHelper; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; public class WlcgIntrospectionHelper extends BaseIntrospectionHelper { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgJWTProfile.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgJWTProfile.java index 826316d9e6..6e2680a5bb 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgJWTProfile.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgJWTProfile.java @@ -15,13 +15,13 @@ */ package it.infn.mw.iam.core.oauth.profile.wlcg; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Request; import it.infn.mw.iam.core.oauth.profile.AccessTokenBuilder; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; import it.infn.mw.iam.core.oauth.profile.IDTokenCustomizer; import it.infn.mw.iam.core.oauth.profile.IntrospectionResultHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.UserInfoHelper; import it.infn.mw.iam.core.oauth.profile.common.BaseJWTProfile; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgUserinfoHelper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgUserinfoHelper.java index ce725871c5..5da0a2369e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgUserinfoHelper.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/profile/wlcg/WlcgUserinfoHelper.java @@ -18,11 +18,11 @@ import java.util.Map; import java.util.Set; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamUserinfoHelper; import it.infn.mw.iam.persistence.model.IamAccount; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamRevocationEndpoint.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamRevocationEndpoint.java index 3d225b9445..52a95e8715 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamRevocationEndpoint.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamRevocationEndpoint.java @@ -19,10 +19,6 @@ import javax.servlet.http.HttpServletRequest; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -45,12 +41,18 @@ import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.client.IamClientDetailsService; import it.infn.mw.iam.core.oauth.exceptions.UnauthorizedClientException; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; @SuppressWarnings("deprecation") @RestController public class IamRevocationEndpoint { + public static final String URL = "/revoke"; + public static final Logger LOG = LoggerFactory.getLogger(IamRevocationEndpoint.class); private static final String SUSPENDED_CLIENT_ERROR = @@ -59,11 +61,11 @@ public class IamRevocationEndpoint { "Client %s is not allowed to revoke a not owned token"; private final TokenRevocationService revocationService; - private final ClientDetailsEntityService clientService; + private final IamClientDetailsService clientService; private final IamTokenService tokenService; public IamRevocationEndpoint(TokenRevocationService revocationService, - ClientDetailsEntityService clientService, IamTokenService tokenService) { + IamClientDetailsService clientService, IamTokenService tokenService) { this.revocationService = revocationService; this.clientService = clientService; this.tokenService = tokenService; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamTokenRevocationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamTokenRevocationService.java index 9351d8671e..18d5ab69ef 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamTokenRevocationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/IamTokenRevocationService.java @@ -17,9 +17,6 @@ import java.text.ParseException; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; @@ -31,7 +28,10 @@ import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.audit.events.tokens.RevocationEvent; import it.infn.mw.iam.core.oauth.introspection.model.TokenTypeHint; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; @@ -65,7 +65,7 @@ public boolean isAccessTokenRevoked(OAuth2AccessTokenEntity token) { @Override public boolean isRefreshTokenRevoked(OAuth2RefreshTokenEntity token) { - return refreshTokenRepo.findByTokenValue(token.getJwt()).isEmpty(); + return refreshTokenRepo.findByTokenValue(token.getValue()).isEmpty(); } @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/TokenRevocationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/TokenRevocationService.java index f8c0010cd0..13aa895f20 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/TokenRevocationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/revocation/TokenRevocationService.java @@ -17,11 +17,10 @@ import java.text.ParseException; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; - +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; public interface TokenRevocationService { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/IamSystemScopeService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/IamSystemScopeService.java index 4f655b70d3..732f4dd329 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/IamSystemScopeService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/IamSystemScopeService.java @@ -17,22 +17,36 @@ import static java.util.stream.Collectors.toSet; +import java.util.LinkedHashSet; import java.util.Set; +import java.util.stream.Collectors; -import org.mitre.oauth2.service.impl.DefaultSystemScopeService; +import org.springframework.stereotype.Service; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcher; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcherRegistry; +import it.infn.mw.iam.persistence.model.SystemScope; +import it.infn.mw.iam.persistence.repository.IamSystemScopeRepository; -public class IamSystemScopeService extends DefaultSystemScopeService { +@Service +public class IamSystemScopeService implements SystemScopeService { - final ScopeMatcherRegistry scopeMatcherRegistry; + private final IamSystemScopeRepository systemScopeRepo; + private final ScopeMatcherRegistry scopeMatcherRegistry; - public IamSystemScopeService(ScopeMatcherRegistry matcherRegistry) { + public IamSystemScopeService(IamSystemScopeRepository systemScopeRepo, + ScopeMatcherRegistry matcherRegistry) { + this.systemScopeRepo = systemScopeRepo; this.scopeMatcherRegistry = matcherRegistry; } - @Override public boolean scopesMatch(Set allowedScopes, Set requestedScopes) { @@ -48,4 +62,131 @@ public boolean scopesMatch(Set allowedScopes, Set requestedScope return true; } + private Predicate isDefault = new Predicate() { + @Override + public boolean apply(SystemScope input) { + return (input != null && input.isDefaultScope()); + } + }; + + private Predicate isRestricted = new Predicate() { + @Override + public boolean apply(SystemScope input) { + return (input != null && input.isRestricted()); + } + }; + + private Predicate isReserved = new Predicate() { + @Override + public boolean apply(SystemScope input) { + return (input != null && getReserved().contains(input)); + } + }; + + private Function stringToSystemScope = new Function() { + @Override + public SystemScope apply(String input) { + if (Strings.isNullOrEmpty(input)) { + return null; + } else { + // get the real scope if it's available + SystemScope s = getByValue(input); + if (s == null) { + // make a fake one otherwise + s = new SystemScope(input); + } + + return s; + } + } + }; + + private Function systemScopeToString = new Function() { + @Override + public String apply(SystemScope input) { + if (input == null) { + return null; + } else { + return input.getValue(); + } + } + }; + + @Override + public Set getAll() { + return systemScopeRepo.findAll().stream().collect(Collectors.toSet()); + } + + @Override + public SystemScope getById(Long id) { + return systemScopeRepo.findById(id).orElse(null); + } + + @Override + public SystemScope getByValue(String value) { + return systemScopeRepo.findByValue(value).orElse(null); + } + + @Override + public void remove(SystemScope scope) { + systemScopeRepo.delete(scope); + } + + @Override + public SystemScope save(SystemScope scope) { + if (!isReserved.apply(scope)) { + return systemScopeRepo.save(scope); + } + return null; + } + + @Override + public Set fromStrings(Set scope) { + if (scope == null) { + return null; + } + return new LinkedHashSet<>(Collections2 + .filter(Collections2.transform(scope, stringToSystemScope), Predicates.notNull())); + } + + @Override + public Set toStrings(Set scope) { + if (scope == null) { + return null; + } + return new LinkedHashSet<>(Collections2 + .filter(Collections2.transform(scope, systemScopeToString), Predicates.notNull())); + } + + @Override + public Set getDefaults() { + return Sets.filter(getAll(), isDefault); + } + + + @Override + public Set getReserved() { + return reservedScopes; + } + + @Override + public Set getRestricted() { + return Sets.filter(getAll(), isRestricted); + } + + @Override + public Set getUnrestricted() { + return Sets.filter(getAll(), Predicates.not(isRestricted)); + } + + @Override + public Set removeRestrictedAndReservedScopes(Set scopes) { + return Sets.filter(scopes, Predicates.not(Predicates.or(isRestricted, isReserved))); + } + + @Override + public Set removeReservedScopes(Set scopes) { + return Sets.filter(scopes, Predicates.not(isReserved)); + } + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/SystemScopeService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/SystemScopeService.java new file mode 100644 index 0000000000..e1ba3d6393 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/SystemScopeService.java @@ -0,0 +1,62 @@ +/** + * 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.oauth.scope; + +import java.util.Set; + +import it.infn.mw.iam.persistence.model.SystemScope; + +public interface SystemScopeService { + + public static final String OFFLINE_ACCESS = "offline_access"; + public static final String OPENID_SCOPE = "openid"; + public static final String REGISTRATION_TOKEN_SCOPE = "registration-token"; + public static final String RESOURCE_TOKEN_SCOPE = "resource-token"; + public static final String UMA_PROTECTION_SCOPE = "uma_protection"; + public static final String UMA_AUTHORIZATION_SCOPE = "uma_authorization"; + + public static final Set reservedScopes = + Set.of(new SystemScope(REGISTRATION_TOKEN_SCOPE), new SystemScope(RESOURCE_TOKEN_SCOPE)); + + public Set getAll(); + + public Set getDefaults(); + + public Set getReserved(); + + public Set getRestricted(); + + public Set getUnrestricted(); + + public SystemScope getById(Long id); + + public SystemScope getByValue(String value); + + public void remove(SystemScope scope); + + public SystemScope save(SystemScope scope); + + public Set fromStrings(Set scope); + + public Set toStrings(Set scope); + + public boolean scopesMatch(Set expected, Set actual); + + public Set removeRestrictedAndReservedScopes(Set scopes); + + public Set removeReservedScopes(Set scopes); +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/matchers/DefaultScopeMatcherRegistry.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/matchers/DefaultScopeMatcherRegistry.java index 21bcd02b47..7e6c2027d9 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/matchers/DefaultScopeMatcherRegistry.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/matchers/DefaultScopeMatcherRegistry.java @@ -15,15 +15,17 @@ */ package it.infn.mw.iam.core.oauth.scope.matchers; +import java.util.List; import java.util.Set; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.repository.SystemScopeRepository; import org.springframework.cache.annotation.Cacheable; import org.springframework.security.oauth2.provider.ClientDetails; import com.google.common.collect.Sets; +import it.infn.mw.iam.persistence.model.SystemScope; +import it.infn.mw.iam.persistence.repository.IamSystemScopeRepository; + @SuppressWarnings("deprecation") public class DefaultScopeMatcherRegistry implements ScopeMatcherRegistry { @@ -31,9 +33,9 @@ public class DefaultScopeMatcherRegistry implements ScopeMatcherRegistry { private final Set customMatchers; - private final SystemScopeRepository scopeRepo; + private final IamSystemScopeRepository scopeRepo; - public DefaultScopeMatcherRegistry(Set customMatchers, SystemScopeRepository scopeRepo) { + public DefaultScopeMatcherRegistry(Set customMatchers, IamSystemScopeRepository scopeRepo) { this.customMatchers = customMatchers; this.scopeRepo = scopeRepo; } @@ -54,7 +56,7 @@ public Set findMatchersForClient(ClientDetails client) { @Override public ScopeMatcher findMatcherForScope(String scope) { - Set systemScopes = scopeRepo.getAll(); + List systemScopes = scopeRepo.findAll(); return customMatchers.stream() .filter(s -> systemScopes.toString().contains(scope)) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/DefaultScopeFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/DefaultScopeFilter.java index f46803bf75..cdef8e1355 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/DefaultScopeFilter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/DefaultScopeFilter.java @@ -21,12 +21,9 @@ import java.util.Set; import java.util.stream.Collectors; -import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.stereotype.Component; import com.google.common.cache.Cache; @@ -41,7 +38,6 @@ import it.infn.mw.iam.persistence.model.IamScopePolicy; import it.infn.mw.iam.persistence.repository.IamScopePolicyRepository; -@SuppressWarnings("deprecation") @Component public class DefaultScopeFilter implements ScopeFilter { @@ -89,24 +85,6 @@ public Set filterScopes(Set requestedScopes, IamAccount account) return filteredScopes; } - @Override - public AuthenticationHolderEntity filterScopes(AuthenticationHolderEntity authHolder) { - - authHolder.setScope(filterScopes(authHolder.getScope(), authHolder.getAuthentication())); - return authHolder; - } - - @Override - public OAuth2Authentication filterScopes(OAuth2Authentication authn) { - - OAuth2Request oldRequest = authn.getOAuth2Request(); - OAuth2Request updatedRequest = new OAuth2Request(oldRequest.getRequestParameters(), oldRequest.getClientId(), - oldRequest.getAuthorities(), oldRequest.isApproved(), filterScopes(oldRequest.getScope(), authn), - oldRequest.getResourceIds(), oldRequest.getRedirectUri(), oldRequest.getResponseTypes(), - oldRequest.getExtensions()); - return new OAuth2Authentication(updatedRequest, authn.getUserAuthentication()); - } - private Set excludedScopes(Set requestedScopes) { return EXCLUDED_SCOPES.stream() diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/ScopeFilter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/ScopeFilter.java index ede0b37e74..76f8e7cc28 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/ScopeFilter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oauth/scope/pdp/ScopeFilter.java @@ -17,21 +17,14 @@ import java.util.Set; -import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Authentication; import it.infn.mw.iam.persistence.model.IamAccount; -@SuppressWarnings("deprecation") public interface ScopeFilter { public Set filterScopes(Set scopes, Authentication authn); public Set filterScopes(Set scopes, IamAccount account); - public AuthenticationHolderEntity filterScopes(AuthenticationHolderEntity authHolder); - - public OAuth2Authentication filterScopes(OAuth2Authentication authHolder); - } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/EntityConfigurationBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/EntityConfigurationBuilder.java index 8a4676dc91..5ff88e943f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/EntityConfigurationBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/EntityConfigurationBuilder.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; -import org.mitre.jose.keystore.JWKSetKeyStore; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -40,6 +39,7 @@ import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.config.oidc.OpenidFederationProperties; import it.infn.mw.iam.core.jwk.JWKUtils; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; import it.infn.mw.iam.core.web.jwk.IamJWKSetPublishingEndpoint; import it.infn.mw.iam.core.web.wellknown.IamWellKnownInfoProvider; @@ -57,7 +57,7 @@ public class EntityConfigurationBuilder { private final long expirationSec; private final Map metadata; - public EntityConfigurationBuilder(JWKSetKeyStore keyStore, + public EntityConfigurationBuilder(JwkSetKeyStore keyStore, IamWellKnownInfoProvider wellKnownInfoProvider, OpenidFederationProperties fedProperties, IamProperties iamProperties, IamJWKSetPublishingEndpoint iamJwkEndpoint) { signingKey = keyStore.getKeys() diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/IamClientValidationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/IamClientValidationService.java deleted file mode 100644 index c3665ea7ed..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/IamClientValidationService.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 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 static java.util.stream.Collectors.toSet; - -import java.util.Set; - -import org.mitre.jwt.assertion.AssertionValidator; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; -import org.mitre.openid.connect.exception.ValidationException; -import org.mitre.openid.connect.service.BlacklistedSiteService; -import org.mitre.openid.connect.service.impl.DefaultDynamicClientValidationService; -import org.springframework.beans.factory.annotation.Qualifier; - -import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcher; -import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcherRegistry; - - -public class IamClientValidationService extends DefaultDynamicClientValidationService { - - private final ScopeMatcherRegistry scopeRegistry; - - public IamClientValidationService(ScopeMatcherRegistry scopeRegistry, - SystemScopeService scopeService, - @Qualifier("clientAssertionValidator") AssertionValidator validator, - BlacklistedSiteService blacklistService, ConfigurationPropertiesBean config, - ClientDetailsEntityService clientService) { - super(scopeService, validator, blacklistService, config, clientService); - this.scopeRegistry = scopeRegistry; - } - - @Override - protected ClientDetailsEntity validateScopes(ClientDetailsEntity newClient) - throws ValidationException { - - Set matchers = scopeService.getRestricted() - .stream() - .map(s -> scopeRegistry.findMatcherForScope(s.getValue())) - .collect(toSet()); - - Set filteredClientScopes = newClient.getScope() - .stream() - .filter(s -> matchers.stream().noneMatch(m -> m.matches(s))) - .collect(toSet()); - - newClient.setScope(filteredClientScopes); - - return newClient; - } -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/ValidationException.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/ValidationException.java new file mode 100644 index 0000000000..c054f46223 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/ValidationException.java @@ -0,0 +1,62 @@ +/** + * 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 org.springframework.http.HttpStatus; + +public class ValidationException extends Exception { + private static final long serialVersionUID = 1820497072989294627L; + + private String error; + private String errorDescription; + private HttpStatus status; + + public ValidationException(String error, String errorDescription, HttpStatus status) { + this.error = error; + this.errorDescription = errorDescription; + this.status = status; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getErrorDescription() { + return errorDescription; + } + + public void setErrorDescription(String errorDescription) { + this.errorDescription = errorDescription; + } + + public HttpStatus getStatus() { + return status; + } + + public void setStatus(HttpStatus status) { + this.status = status; + } + + @Override + public String toString() { + return "ValidationException [error=" + error + ", errorDescription=" + errorDescription + + ", status=" + status + "]"; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamThirdPartyIssuerService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IamThirdPartyIssuerService.java similarity index 92% rename from iam-login-service/src/main/java/it/infn/mw/iam/core/IamThirdPartyIssuerService.java rename to iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IamThirdPartyIssuerService.java index ea904414cf..42390524c3 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/IamThirdPartyIssuerService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IamThirdPartyIssuerService.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.core; +package it.infn.mw.iam.core.oidc.service; import java.util.HashSet; import java.util.Set; import javax.servlet.http.HttpServletRequest; -import org.mitre.openid.connect.client.model.IssuerServiceResponse; -import org.mitre.openid.connect.client.service.IssuerService; import org.springframework.security.authentication.AuthenticationServiceException; public class IamThirdPartyIssuerService implements IssuerService { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IssuerService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IssuerService.java new file mode 100644 index 0000000000..197ff9784d --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IssuerService.java @@ -0,0 +1,24 @@ +/** + * 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.service; + +import javax.servlet.http.HttpServletRequest; + +public interface IssuerService { + + public IssuerServiceResponse getIssuer(HttpServletRequest request); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IssuerServiceResponse.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IssuerServiceResponse.java new file mode 100644 index 0000000000..8831e253a1 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/oidc/service/IssuerServiceResponse.java @@ -0,0 +1,71 @@ +/** + * 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.service; + +public class IssuerServiceResponse { + + private String issuer; + private String loginHint; + private String targetLinkUri; + private String redirectUrl; + + public IssuerServiceResponse(String issuer, String loginHint, String targetLinkUri) { + this.issuer = issuer; + this.loginHint = loginHint; + this.targetLinkUri = targetLinkUri; + } + + public IssuerServiceResponse(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getLoginHint() { + return loginHint; + } + + public void setLoginHint(String loginHint) { + this.loginHint = loginHint; + } + + public String getTargetLinkUri() { + return targetLinkUri; + } + + public void setTargetLinkUri(String targetLinkUri) { + this.targetLinkUri = targetLinkUri; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + public boolean shouldRedirect() { + return this.redirectUrl != null; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/ClientStat.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/ClientStat.java new file mode 100644 index 0000000000..831e402751 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/ClientStat.java @@ -0,0 +1,29 @@ +/** + * 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.stats; + +public class ClientStat { + + private final int approvedSiteCount; + + public ClientStat(int count) { + this.approvedSiteCount = count; + } + + public int getApprovedSiteCount() { + return approvedSiteCount; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/DefaultStatsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/DefaultStatsService.java new file mode 100644 index 0000000000..fd97616885 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/DefaultStatsService.java @@ -0,0 +1,83 @@ +/** + * 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.stats; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.springframework.stereotype.Service; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; + +import it.infn.mw.iam.persistence.repository.IamApprovedSiteRepository; +import it.infn.mw.iam.persistence.repository.UserClientCounts; + +@Service +public class DefaultStatsService implements StatsService { + + private IamApprovedSiteRepository approvedSiteRepo; + + public DefaultStatsService(IamApprovedSiteRepository approvedSiteRepo) { + + this.approvedSiteRepo = approvedSiteRepo; + } + + private Supplier> summaryCache = createSummaryCache(); + + private Supplier> createSummaryCache() { + + return Suppliers.memoizeWithExpiration(new Supplier>() { + @Override + public Map get() { + return computeSummaryStats(); + } + }, 10, TimeUnit.MINUTES); + } + + @Override + public Map getSummaryStats() { + + return summaryCache.get(); + } + + private Map computeSummaryStats() { + + long total = approvedSiteRepo.count(); + UserClientCounts ucc = approvedSiteRepo.findDistinctUserAndClientCounts(); + + Map e = new HashMap<>(); + e.put("approvalCount", Long.valueOf(total).intValue()); + e.put("userCount", Long.valueOf(ucc.getUserCount()).intValue()); + e.put("clientCount", Long.valueOf(ucc.getClientCount()).intValue()); + return e; + } + + @Override + public ClientStat getCountForClientId(String clientId) { + + return new ClientStat(approvedSiteRepo.findByClientId(clientId).size()); + } + + @Override + public void resetCache() { + + summaryCache = createSummaryCache(); + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/StatsService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/StatsService.java new file mode 100644 index 0000000000..f63b509ff5 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/stats/StatsService.java @@ -0,0 +1,28 @@ +/** + * 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.stats; + +import java.util.Map; + +public interface StatsService { + + public Map getSummaryStats(); + + public ClientStat getCountForClientId(String clientId); + + public void resetCache(); + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/token/JwtAssertionTokenGranter.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/token/JwtAssertionTokenGranter.java new file mode 100644 index 0000000000..1e52ffb543 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/token/JwtAssertionTokenGranter.java @@ -0,0 +1,81 @@ +/** + * 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.token; + +import java.text.ParseException; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.stereotype.Component; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +import it.infn.mw.iam.authn.jwt.JwtBearerAssertionAuthenticationToken; +import it.infn.mw.iam.core.OAuth2TokenEntityService; +import it.infn.mw.iam.core.jwt.assertion.AssertionOAuth2RequestFactory; +import it.infn.mw.iam.core.jwt.assertion.AssertionValidator; + +@SuppressWarnings("deprecation") +@Component("jwtAssertionTokenGranter") +public class JwtAssertionTokenGranter extends AbstractTokenGranter { + + private static final String grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"; + + private final AssertionValidator validator; + private final AssertionOAuth2RequestFactory assertionFactory; + + public JwtAssertionTokenGranter(OAuth2TokenEntityService tokenServices, + ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, + AssertionValidator validator, AssertionOAuth2RequestFactory assertionFactory) { + super(tokenServices, clientDetailsService, requestFactory, grantType); + + this.validator = validator; + this.assertionFactory = assertionFactory; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, + TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException { + + try { + String incomingAssertionValue = tokenRequest.getRequestParameters().get("assertion"); + JWT assertion = JWTParser.parse(incomingAssertionValue); + + if (validator.isValid(assertion)) { + + return new OAuth2Authentication( + assertionFactory.createOAuth2Request(client, tokenRequest, assertion), + new JwtBearerAssertionAuthenticationToken(assertion, client.getAuthorities())); + + } else { + logger.warn("Incoming assertion did not pass validator, rejecting"); + return null; + } + + } catch (ParseException e) { + logger.warn("Unable to parse incoming assertion"); + } + + return null; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/user/DefaultIamAccountService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/user/DefaultIamAccountService.java index cc8d4f169f..3aa7078162 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/user/DefaultIamAccountService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/user/DefaultIamAccountService.java @@ -28,6 +28,7 @@ import static java.util.Objects.isNull; import java.time.Clock; +import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.LinkedHashSet; @@ -43,8 +44,16 @@ import org.apache.commons.lang3.ObjectUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.annotation.Primary; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -91,18 +100,20 @@ import it.infn.mw.iam.persistence.model.IamSamlId; import it.infn.mw.iam.persistence.model.IamSshKey; import it.infn.mw.iam.persistence.model.IamX509Certificate; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamAupSignatureRepository; import it.infn.mw.iam.persistence.repository.IamAuthoritiesRepository; import it.infn.mw.iam.persistence.repository.IamGroupRepository; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; import it.infn.mw.iam.registration.RegistrationRequestDto; import it.infn.mw.iam.registration.TokenGenerator; @Service +@Primary @Transactional -public class DefaultIamAccountService implements IamAccountService, ApplicationEventPublisherAware { +public class DefaultIamAccountService + implements UserDetailsService, IamAccountService, ApplicationEventPublisherAware { private final Clock clock; private final IamAccountRepository accountRepo; @@ -802,4 +813,33 @@ public IamAccount signAup(IamAccount account, IamAup aup) { return account; } + List convertAuthorities(final IamAccount a) { + + List authorities = new ArrayList<>(); + for (IamAuthority auth : a.getAuthorities()) { + authorities.add(new SimpleGrantedAuthority(auth.getAuthority())); + } + return authorities; + } + + @Override + public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { + + IamAccount a = findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User '" + username + "' not found.")); + + if (!a.isActive()) { + + throw new DisabledException("User '" + username + "' is not active."); + } + + return new User(a.getUsername(), a.getPassword(), convertAuthorities(a)); + } + + @Override + public Optional findByEmail(String email) { + + return accountRepo.findByEmail(email); + } + } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/user/IamAccountService.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/user/IamAccountService.java index c01467c67f..5ec92bf3f4 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/user/IamAccountService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/user/IamAccountService.java @@ -55,6 +55,14 @@ public interface IamAccountService { */ Optional findByUsername(String username); + /** + * Finds an account by email + * + * @param account email + * @return an {@link Optional} IAM account + */ + Optional findByEmail(String email); + /** * Creates a new {@link IamAccount} from a registration request. * diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/DefaultOAuth2AuthenticationScopeResolver.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/DefaultOAuth2AuthenticationScopeResolver.java index 5e4f0e58ed..125fcb68bf 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/DefaultOAuth2AuthenticationScopeResolver.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/DefaultOAuth2AuthenticationScopeResolver.java @@ -15,26 +15,29 @@ */ package it.infn.mw.iam.core.userinfo; +import static it.infn.mw.iam.core.IamTokenService.sha256; import static java.util.Objects.isNull; +import java.util.Optional; import java.util.Set; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.repository.OAuth2TokenRepository; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.stereotype.Component; import it.infn.mw.iam.api.scim.exception.IllegalArgumentException; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; @Component @SuppressWarnings("deprecation") public class DefaultOAuth2AuthenticationScopeResolver implements OAuth2AuthenticationScopeResolver { - private final OAuth2TokenRepository tokenRepo; + private final IamOAuthAccessTokenRepository accessTokenRepository; - public DefaultOAuth2AuthenticationScopeResolver(OAuth2TokenRepository tokenRepo) { - this.tokenRepo = tokenRepo; + public DefaultOAuth2AuthenticationScopeResolver( + IamOAuthAccessTokenRepository accessTokenRepository) { + this.accessTokenRepository = accessTokenRepository; } @Override @@ -46,15 +49,13 @@ public Set resolveScope(OAuth2Authentication auth) { return auth.getOAuth2Request().getScope(); } - OAuth2AccessTokenEntity accessTokenEntity = - tokenRepo.getAccessTokenByValue(details.getTokenValue()); + Optional accessTokenEntity = + accessTokenRepository.findByTokenValue(sha256(details.getTokenValue())); - if (isNull(accessTokenEntity)) { - throw new IllegalArgumentException("Invalid token"); - } else { - return accessTokenEntity.getScope(); + if (accessTokenEntity.isPresent()) { + return accessTokenEntity.get().getScope(); } - + throw new IllegalArgumentException("Invalid token"); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoEndpoint.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoEndpoint.java index 6b14107dfd..af8b7aa001 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoEndpoint.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoEndpoint.java @@ -22,7 +22,6 @@ import javax.security.auth.message.AuthException; import javax.servlet.http.HttpServletRequest; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -37,14 +36,17 @@ import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; @SuppressWarnings("deprecation") @RestController public class IamUserInfoEndpoint { + public static final String URL = "/userinfo"; + private static final Logger LOG = LoggerFactory.getLogger(IamUserInfoEndpoint.class); private static final String ACCOUNT_NOT_FOUND_ERROR = "User '%s' not found"; private static final String CLIENT_NOT_FOUND_ERROR = "Client '%s' not found"; @@ -64,7 +66,7 @@ public IamUserInfoEndpoint(JWTProfileResolver profileResolver, } @PreAuthorize("hasRole('ROLE_USER') and #iam.hasScope('openid')") - @GetMapping(path = "/userinfo", produces = {MediaType.APPLICATION_JSON_VALUE}) + @GetMapping(path = URL, produces = {MediaType.APPLICATION_JSON_VALUE}) public UserInfoResponse getInfo(OAuth2Authentication auth) throws AuthException { String username = auth.getName(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoInterceptor.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoInterceptor.java index 300a2230f3..79668fc8a9 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoInterceptor.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/userinfo/IamUserInfoInterceptor.java @@ -15,17 +15,18 @@ */ package it.infn.mw.iam.core.userinfo; +import java.util.Optional; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.mitre.openid.connect.model.UserInfo; -import org.mitre.openid.connect.service.UserInfoService; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.stereotype.Component; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor; @@ -34,20 +35,24 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializer; +import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.IamAccount; + @SuppressWarnings("deprecation") +@Component public class IamUserInfoInterceptor implements HandlerInterceptor, AsyncHandlerInterceptor { public static final String USERINFO_ATTR_NAME = "userInfo"; public static final String USERINFO_JSON_ATTR_NAME = "userInfoJson"; private final Gson gsonBuilder; - private final UserInfoService userInfoService; + private final IamAccountService userInfoService; private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); private static final JsonSerializer AUTHORITY_SERIALIZER = (src, type, context) -> new JsonPrimitive(src.getAuthority()); - public IamUserInfoInterceptor(UserInfoService userInfoService) { + public IamUserInfoInterceptor(IamAccountService userInfoService) { this.userInfoService = userInfoService; gsonBuilder = new GsonBuilder() .registerTypeHierarchyAdapter(GrantedAuthority.class, AUTHORITY_SERIALIZER) @@ -55,11 +60,11 @@ public IamUserInfoInterceptor(UserInfoService userInfoService) { } private void resolveUserInfo(Authentication auth, HttpServletRequest request) { - UserInfo user = userInfoService.getByUsername(auth.getName()); + Optional user = userInfoService.findByUsername(auth.getName()); - if (user != null) { - request.setAttribute(USERINFO_ATTR_NAME, user); - request.setAttribute(USERINFO_JSON_ATTR_NAME, user.toJson()); + if (user.isPresent()) { + request.setAttribute(USERINFO_ATTR_NAME, user.get().getUserInfo()); + request.setAttribute(USERINFO_JSON_ATTR_NAME, user.get().toJson()); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/util/PoliteJsonMessageSource.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/util/PoliteJsonMessageSource.java index 8629619907..b35187cdea 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/util/PoliteJsonMessageSource.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/util/PoliteJsonMessageSource.java @@ -26,7 +26,6 @@ import java.util.Locale; import java.util.Map; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -40,26 +39,22 @@ import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; +import it.infn.mw.iam.config.IamProperties; + @SuppressWarnings("deprecation") -/** - * This is a more polite {@link org.mitre.openid.connect.config.JsonMessageSource} that does not log - * errors for unsupported locales. - * - */ + public class PoliteJsonMessageSource extends AbstractMessageSource { - // Logger for this class private static final Logger LOG = LoggerFactory.getLogger(PoliteJsonMessageSource.class); private Resource baseDirectory; - private Locale fallbackLocale = new Locale("en"); // US English is the - // fallback language + private Locale fallbackLocale = new Locale("en"); private Map> languageMaps = new HashMap<>(); @Autowired - private ConfigurationPropertiesBean config; + private IamProperties config; @Override protected MessageFormat resolveCode(String code, Locale locale) { @@ -69,51 +64,33 @@ protected MessageFormat resolveCode(String code, Locale locale) { String value = getValue(code, langs); if (value == null) { - // if we haven't found anything, try the default locale langs = getLanguageMap(fallbackLocale); value = getValue(code, langs); } if (value == null) { - // if it's still null, return null return null; - } else { - // otherwise format the statusMessage - return new MessageFormat(value, locale); } - + return new MessageFormat(value, locale); } - /** - * Get a value from the set of maps, taking the first match in order @param code @param - * langs @return - */ private String getValue(String code, List langs) { if (langs == null || langs.isEmpty()) { - // no language maps, nothing to look up return null; } for (JsonObject lang : langs) { String value = getValue(code, lang); if (value != null) { - // short circuit out of here if we find a match, otherwise keep going - // through the list return value; } } - - // if we didn't find anything return null return null; } - /** - * Get a value from a single map @param code @param locale @param lang @return - */ private String getValue(String code, JsonObject lang) { - // if there's no language map, nothing to look up if (lang == null) { return null; } @@ -138,17 +115,14 @@ private String getValue(String code, JsonObject lang) { } } } else { - // didn't find it, stop processing break; } } else { - // didn't find it, stop processing break; } } return value; - } /** @@ -174,12 +148,11 @@ private List getLanguageMap(Locale locale) { } languageMaps.put(locale, set); } catch (JsonIOException | JsonSyntaxException | IOException e) { - LOG.debug("Unable to load locale: {}", e.getMessage(),e); + LOG.debug("Unable to load locale: {}", e.getMessage(), e); } } return languageMaps.get(locale); - } /** diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/BlacklistAwareRedirectResolver.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/BlacklistAwareRedirectResolver.java new file mode 100644 index 0000000000..b69a7cb7d4 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/BlacklistAwareRedirectResolver.java @@ -0,0 +1,73 @@ +/** + * 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.web; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.endpoint.DefaultRedirectResolver; +import org.springframework.stereotype.Component; + +import com.google.common.base.Strings; + +import it.infn.mw.iam.config.MitreConfigurationPropertiesBean; +import it.infn.mw.iam.core.oauth.BlacklistedSiteService; + +@SuppressWarnings("deprecation") +@Component +public class BlacklistAwareRedirectResolver extends DefaultRedirectResolver { + + @Autowired + private BlacklistedSiteService blacklistService; + + @Autowired + private MitreConfigurationPropertiesBean config; + + private boolean strictMatch = true; + + @Override + public String resolveRedirect(String requestedRedirect, ClientDetails client) + throws OAuth2Exception { + String redirect = super.resolveRedirect(requestedRedirect, client); + if (blacklistService.isBlacklisted(redirect)) { + throw new InvalidRequestException("The supplied redirect_uri is not allowed on this server."); + } + return redirect; + } + + @Override + protected boolean redirectMatches(String requestedRedirect, String redirectUri) { + + if (isStrictMatch()) { + // we're doing a strict string match for all clients + return Strings.nullToEmpty(requestedRedirect).equals(redirectUri); + } + return super.redirectMatches(requestedRedirect, redirectUri); + } + + public boolean isStrictMatch() { + if (config.isHeartMode()) { + return true; + } + return strictMatch; + } + + public void setStrictMatch(boolean strictMatch) { + this.strictMatch = strictMatch; + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/ServerConfigInterceptor.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/ServerConfigInterceptor.java new file mode 100644 index 0000000000..783a46f4ca --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/ServerConfigInterceptor.java @@ -0,0 +1,45 @@ +/** + * 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.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import it.infn.mw.iam.config.MitreConfigurationPropertiesBean; +import it.infn.mw.iam.config.UIConfiguration; + +@SuppressWarnings("deprecation") +@Component +public class ServerConfigInterceptor extends HandlerInterceptorAdapter { + + @Autowired + private MitreConfigurationPropertiesBean config; + + @Autowired + private UIConfiguration ui; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + request.setAttribute("config", config); + request.setAttribute("ui", ui); + return true; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/jwk/IamJWKSetPublishingEndpoint.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/jwk/IamJWKSetPublishingEndpoint.java index 6cf194a3a4..f596a424a1 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/jwk/IamJWKSetPublishingEndpoint.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/jwk/IamJWKSetPublishingEndpoint.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -28,13 +27,12 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; - import org.springframework.web.bind.annotation.RestController; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; @RestController public class IamJWKSetPublishingEndpoint implements InitializingBean { @@ -44,7 +42,7 @@ public class IamJWKSetPublishingEndpoint implements InitializingBean { private String jsonKeys; @Autowired - private IamJWTSigningService jwtService; + private JwtSigningAndValidationService jwtService; @Value("${spring.web.resources.cache.cachecontrol.max-age}") private int maxAge; @@ -58,14 +56,14 @@ public ResponseEntity getJwk() { /** * @return the jwtService */ - public JWTSigningAndValidationService getJwtService() { + public JwtSigningAndValidationService getJwtService() { return jwtService; } /** * @param jwtService the jwtService to set */ - public void setJwtService(IamJWTSigningService jwtService) { + public void setJwtService(JwtSigningAndValidationService jwtService) { this.jwtService = jwtService; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/util/AuthenticationTimeStamper.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/util/AuthenticationTimeStamper.java new file mode 100644 index 0000000000..4510571d83 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/util/AuthenticationTimeStamper.java @@ -0,0 +1,67 @@ +/** + * 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.web.util; + +import java.io.IOException; +import java.util.Date; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import it.infn.mw.iam.authn.oidc.AuthorizationRequestFilter; + +@Component +public class AuthenticationTimeStamper extends SavedRequestAwareAuthenticationSuccessHandler { + + private static final Logger logger = LoggerFactory.getLogger(AuthenticationTimeStamper.class); + + public static final String AUTH_TIMESTAMP = "AUTH_TIMESTAMP"; + + /** + * Set the timestamp on the session to mark when the authentication happened, useful for + * calculating authentication age. This gets stored in the session and can get pulled out by other + * components. + */ + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + Date authTimestamp = new Date(); + + HttpSession session = request.getSession(); + + session.setAttribute(AUTH_TIMESTAMP, authTimestamp); + + if (session.getAttribute(AuthorizationRequestFilter.PROMPT_REQUESTED) != null) { + session.setAttribute(AuthorizationRequestFilter.PROMPTED, Boolean.TRUE); + session.removeAttribute(AuthorizationRequestFilter.PROMPT_REQUESTED); + } + + logger.info("Successful Authentication of " + authentication.getName() + " at " + + authTimestamp.toString()); + + super.onAuthenticationSuccess(request, response, authentication); + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/HttpCodeView.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/HttpCodeView.java new file mode 100644 index 0000000000..61ba5a7366 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/HttpCodeView.java @@ -0,0 +1,47 @@ +/** + * 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.web.view; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.view.AbstractView; + +@Component(HttpCodeView.VIEWNAME) +public class HttpCodeView extends AbstractView { + + public static final String VIEWNAME = "httpCodeView"; + + public static final String CODE = "code"; + + @Override + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) { + + HttpStatus code = (HttpStatus) model.get(CODE); + if (code == null) { + code = HttpStatus.OK; + } + + response.setStatus(code.value()); + + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonApprovedSiteView.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonApprovedSiteView.java new file mode 100644 index 0000000000..5af9baeb38 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonApprovedSiteView.java @@ -0,0 +1,122 @@ +/** + * 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.web.view; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Type; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.web.servlet.view.AbstractView; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.WhitelistedSite; +import it.infn.mw.iam.persistence.model.serializer.ApprovedSiteSerializer; + +@Component(JsonApprovedSiteView.VIEWNAME) +public class JsonApprovedSiteView extends AbstractView { + + private static final Logger logger = LoggerFactory.getLogger(JsonApprovedSiteView.class); + + public static final String VIEWNAME = "jsonApprovedSiteView"; + + private Gson gson = + new GsonBuilder().registerTypeAdapter(ApprovedSite.class, new ApprovedSiteSerializer()) + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } + + }) + .registerTypeAdapter(OAuth2AccessTokenEntity.class, + new JsonSerializer() { + @Override + public JsonElement serialize(OAuth2AccessTokenEntity src, Type typeOfSrc, + JsonSerializationContext context) { + return new JsonPrimitive(src.getId()); + } + }) + .registerTypeAdapter(WhitelistedSite.class, new JsonSerializer() { + @Override + public JsonElement serialize(WhitelistedSite src, Type typeOfSrc, + JsonSerializationContext context) { + return new JsonPrimitive(src.getId()); + } + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); + + @Override + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) { + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + + HttpStatus code = (HttpStatus) model.get(HttpCodeView.CODE); + if (code == null) { + code = HttpStatus.OK; // default to 200 + } + + response.setStatus(code.value()); + + try { + + Writer out = response.getWriter(); + Object obj = model.get(JsonEntityView.ENTITY); + gson.toJson(obj, out); + + } catch (IOException e) { + + logger.error("IOException in JsonEntityView.java: ", e); + + } + } + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonEntityView.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonEntityView.java new file mode 100644 index 0000000000..39cc617631 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonEntityView.java @@ -0,0 +1,88 @@ +/** + * 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.web.view; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.web.servlet.view.AbstractView; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +@Component(JsonEntityView.VIEWNAME) +public class JsonEntityView extends AbstractView { + + public static final String VIEWNAME = "jsonEntityView"; + public static final String ENTITY = "entity"; + + private static final Logger logger = LoggerFactory.getLogger(JsonEntityView.class); + + private Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + + return clazz.equals(BeanPropertyBindingResult.class); + } + + }).serializeNulls().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); + + @Override + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) { + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + HttpStatus code = (HttpStatus) model.get(HttpCodeView.CODE); + if (code == null) { + code = HttpStatus.OK; // default to 200 + } + + response.setStatus(code.value()); + + try { + + Writer out = response.getWriter(); + Object obj = model.get(ENTITY); + gson.toJson(obj, out); + + } catch (IOException e) { + + logger.error("IOException in JsonEntityView.java: ", e); + } + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonErrorView.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonErrorView.java new file mode 100644 index 0000000000..40d6fdb794 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/view/JsonErrorView.java @@ -0,0 +1,97 @@ +/** + * 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.web.view; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.web.servlet.view.AbstractView; + +import com.google.common.base.Strings; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +@Component(JsonErrorView.VIEWNAME) +public class JsonErrorView extends AbstractView { + + private static final Logger logger = LoggerFactory.getLogger(JsonErrorView.class); + + public static final String VIEWNAME = "jsonErrorView"; + public static final String ERROR_MESSAGE = "errorMessage"; + public static final String ERROR = "error"; + + private Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + + return clazz.equals(BeanPropertyBindingResult.class); + } + + }).serializeNulls().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); + + @Override + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) { + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + HttpStatus code = (HttpStatus) model.get(HttpCodeView.CODE); + if (code == null) { + code = HttpStatus.INTERNAL_SERVER_ERROR; // default to 500 + } + response.setStatus(code.value()); + + try { + + Writer out = response.getWriter(); + + String errorTitle = (String) model.get(ERROR); + if (Strings.isNullOrEmpty(errorTitle)) { + errorTitle = "mitreid_error"; + } + String errorMessage = (String) model.get(ERROR_MESSAGE); + JsonObject obj = new JsonObject(); + obj.addProperty("error", errorTitle); + obj.addProperty("error_description", errorMessage); + gson.toJson(obj, out); + + } catch (IOException e) { + + logger.error("IOException in JsonErrorView.java: ", e); + } + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamDiscoveryEndpoint.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamDiscoveryEndpoint.java index 6695cce1ad..6ecec4d051 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamDiscoveryEndpoint.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamDiscoveryEndpoint.java @@ -15,12 +15,8 @@ */ package it.infn.mw.iam.core.web.wellknown; -import org.mitre.discovery.util.WebfingerURLNormalizer; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; -import org.mitre.openid.connect.model.UserInfo; -import org.mitre.openid.connect.service.UserInfoService; -import org.mitre.openid.connect.view.HttpCodeView; -import org.mitre.openid.connect.view.JsonEntityView; +import java.util.Optional; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -28,14 +24,18 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import com.google.common.base.Strings; +import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.core.web.view.HttpCodeView; +import it.infn.mw.iam.core.web.view.JsonEntityView; +import it.infn.mw.iam.persistence.model.IamAccount; + @Controller public class IamDiscoveryEndpoint { @@ -45,44 +45,41 @@ public class IamDiscoveryEndpoint { public static final String OPENID_CONFIGURATION_URL = WELL_KNOWN_URL + "/openid-configuration"; private static final String WEBFINGER_URL = WELL_KNOWN_URL + "/webfinger"; - private final ConfigurationPropertiesBean config; - private final UserInfoService userService; + private final IamProperties config; + private final IamAccountService accountService; private final WellKnownInfoProvider wellKnownInfoProvider; - public IamDiscoveryEndpoint(ConfigurationPropertiesBean config, UserInfoService userService, + public IamDiscoveryEndpoint(IamProperties config, IamAccountService accountService, WellKnownInfoProvider wellKnownInfoProvider) { this.config = config; - this.userService = userService; + this.accountService = accountService; this.wellKnownInfoProvider = wellKnownInfoProvider; } @GetMapping(value = {"/" + WEBFINGER_URL}, produces = MediaType.APPLICATION_JSON_VALUE) - public String webfinger(@RequestParam("resource") String resource, - @RequestParam(value = "rel", required = false) String rel, Model model) { + public String webfinger(@RequestParam String resource, @RequestParam(required = false) String rel, + Model model) { if (!Strings.isNullOrEmpty(rel) && !"http://openid.net/specs/connect/1.0/issuer".equals(rel)) { LOG.warn("Responding to webfinger request for non-OIDC relation: {}", rel); } if (!resource.equals(config.getIssuer())) { - // it's not the issuer directly, need to check other methods UriComponents resourceUri = WebfingerURLNormalizer.normalizeResource(resource); + if (resourceUri != null && resourceUri.getScheme() != null && "acct".equals(resourceUri.getScheme())) { - // acct: URI (email address format) - // check on email addresses first - UserInfo user = - userService.getByEmailAddress(resourceUri.getUserInfo() + "@" + resourceUri.getHost()); + Optional user = + accountService.findByEmail(resourceUri.getUserInfo() + "@" + resourceUri.getHost()); + + if (user.isEmpty()) { - if (user == null) { - // user wasn't found, see if the local part of the username matches, plus our issuer iamHost + user = accountService.findByUsername(resourceUri.getUserInfo()); - user = userService.getByUsername(resourceUri.getUserInfo()); // first part is the username + if (user.isPresent()) { - if (user != null) { - // username matched, check the iamHost component UriComponents issuerComponents = UriComponentsBuilder.fromHttpUrl(config.getIssuer()).build(); if (!Strings.nullToEmpty(issuerComponents.getHost()) @@ -114,7 +111,7 @@ public String webfinger(@RequestParam("resource") String resource, return "webfingerView"; } - @RequestMapping(value = {"/" + OPENID_CONFIGURATION_URL}, method = RequestMethod.GET) + @GetMapping(value = {"/" + OPENID_CONFIGURATION_URL}) public String providerConfiguration(Model model) { model.addAttribute(JsonEntityView.ENTITY, wellKnownInfoProvider.getWellKnownInfo()); return JsonEntityView.VIEWNAME; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamWellKnownInfoProvider.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamWellKnownInfoProvider.java index 942d2763f6..1652f03785 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamWellKnownInfoProvider.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/IamWellKnownInfoProvider.java @@ -23,13 +23,6 @@ import java.util.Map; import java.util.Set; -import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; -import org.mitre.oauth2.model.PKCEAlgorithm; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.oauth2.web.DeviceEndpoint; -import org.mitre.oauth2.web.IntrospectionEndpoint; -import org.mitre.oauth2.web.RevocationEndpoint; -import org.mitre.openid.connect.web.UserInfoEndpoint; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -40,7 +33,14 @@ import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.jwt.JwtEncryptionAndDecryptionService; +import it.infn.mw.iam.core.oauth.devicecode.IamDeviceEndpoint; +import it.infn.mw.iam.core.oauth.introspection.IamIntrospectionEndpoint; +import it.infn.mw.iam.core.oauth.revocation.IamRevocationEndpoint; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.core.userinfo.IamUserInfoEndpoint; import it.infn.mw.iam.core.web.jwk.IamJWKSetPublishingEndpoint; +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; @Service public class IamWellKnownInfoProvider implements WellKnownInfoProvider { @@ -110,7 +110,7 @@ public class IamWellKnownInfoProvider implements WellKnownInfoProvider { public IamWellKnownInfoProvider(IamProperties properties, - JWTEncryptionAndDecryptionService encService, SystemScopeService scopeService) { + JwtEncryptionAndDecryptionService encService, SystemScopeService scopeService) { this.properties = properties; this.systemScopeService = scopeService; @@ -126,12 +126,12 @@ public IamWellKnownInfoProvider(IamProperties properties, authorizeEndpoint = buildEndpointUrl(AUTHORIZE_ENDPOINT); tokenEndpoint = buildEndpointUrl(TOKEN_ENDPOINT); - userinfoEndpoint = buildEndpointUrl(UserInfoEndpoint.URL); + userinfoEndpoint = buildEndpointUrl(IamUserInfoEndpoint.URL); jwkEndpoint = buildEndpointUrl(IamJWKSetPublishingEndpoint.URL); clientRegistrationEndpoint = buildEndpointUrl(ClientRegistrationApiController.ENDPOINT); - introspectionEndpoint = buildEndpointUrl(IntrospectionEndpoint.URL); - revocationEndpoint = buildEndpointUrl(RevocationEndpoint.URL); - deviceAuthorizationEndpoint = buildEndpointUrl(DeviceEndpoint.URL); + introspectionEndpoint = buildEndpointUrl(IamIntrospectionEndpoint.URL); + revocationEndpoint = buildEndpointUrl(IamRevocationEndpoint.URL); + deviceAuthorizationEndpoint = buildEndpointUrl(IamDeviceEndpoint.URL); aboutEndpoint = buildEndpointUrl(ABOUT_ENDPOINT); scimEndpoint = buildEndpointUrl(SCIM_ENDPOINT); updateSupportedScopes(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/WebfingerURLNormalizer.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/WebfingerURLNormalizer.java new file mode 100644 index 0000000000..cb01ae9dcb --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/wellknown/WebfingerURLNormalizer.java @@ -0,0 +1,145 @@ +/** + * 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.web.wellknown; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import com.google.common.base.Strings; + +public class WebfingerURLNormalizer { + + private static final Logger logger = LoggerFactory.getLogger(WebfingerURLNormalizer.class); + + // pattern used to parse user input; we can't use the built-in java URI parser + private static final Pattern pattern = + Pattern.compile("^" + "((https|acct|http|mailto|tel|device):(//)?)?" + // scheme + "(" + "(([^@]+)@)?" + // userinfo + "(([^\\?#:/]+)" + // host + "(:(\\d*))?)" + // port + ")" + "([^\\?#]+)?" + // path + "(\\?([^#]+))?" + // query + "(#(.*))?" + // fragment + "$"); + + private WebfingerURLNormalizer() { + // intentionally blank + } + + public static UriComponents normalizeResource(String identifier) { + + if (Strings.isNullOrEmpty(identifier)) { + logger.warn("Can't normalize null or empty URI: " + identifier); + return null; // nothing we can do + } + + UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); + + Matcher m = pattern.matcher(identifier); + if (m.matches()) { + builder.scheme(m.group(2)); + builder.userInfo(m.group(6)); + builder.host(m.group(8)); + String port = m.group(10); + if (!Strings.isNullOrEmpty(port)) { + builder.port(Integer.parseInt(port)); + } + builder.path(m.group(11)); + builder.query(m.group(13)); + builder.fragment(m.group(15)); + } else { + logger.warn("Parser couldn't match input: " + identifier); + return null; + } + + UriComponents n = builder.build(); + + if (Strings.isNullOrEmpty(n.getScheme())) { + if (!Strings.isNullOrEmpty(n.getUserInfo()) && Strings.isNullOrEmpty(n.getPath()) + && Strings.isNullOrEmpty(n.getQuery()) && n.getPort() < 0) { + + builder.scheme("acct"); + + } else { + + builder.scheme("https"); + } + } + + builder.fragment(null); + return builder.build(); + + } + + public static String serializeURL(UriComponents uri) { + + if (uri.getScheme() != null + && (uri.getScheme().equals("acct") || uri.getScheme().equals("mailto") + || uri.getScheme().equals("tel") || uri.getScheme().equals("device"))) { + + StringBuilder uriBuilder = new StringBuilder(); + + if (uri.getScheme() != null) { + uriBuilder.append(uri.getScheme()); + uriBuilder.append(':'); + } + + if (uri.getUserInfo() != null || uri.getHost() != null) { + if (uri.getUserInfo() != null) { + uriBuilder.append(uri.getUserInfo()); + uriBuilder.append('@'); + } + if (uri.getHost() != null) { + uriBuilder.append(uri.getHost()); + } + if (uri.getPort() != -1) { + uriBuilder.append(':'); + uriBuilder.append(uri.getPort()); + } + } + + String path = uri.getPath(); + if (StringUtils.hasLength(path)) { + if (uriBuilder.length() != 0 && path.charAt(0) != '/') { + uriBuilder.append('/'); + } + uriBuilder.append(path); + } + + String query = uri.getQuery(); + if (query != null) { + uriBuilder.append('?'); + uriBuilder.append(query); + } + + if (uri.getFragment() != null) { + uriBuilder.append('#'); + uriBuilder.append(uri.getFragment()); + } + + return uriBuilder.toString(); + } else { + return uri.toUriString(); + } + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java index 15ef80a63b..d77eb41ff8 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java @@ -18,8 +18,7 @@ import java.util.List; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; - +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAup; import it.infn.mw.iam.persistence.model.IamEmailNotification; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java index f377b15a69..bbce191754 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java @@ -30,7 +30,6 @@ import java.util.UUID; import java.util.stream.Collectors; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -46,6 +45,7 @@ import it.infn.mw.iam.core.IamNotificationType; import it.infn.mw.iam.notification.service.resolver.AdminNotificationDeliveryStrategy; import it.infn.mw.iam.notification.service.resolver.GroupManagerNotificationDeliveryStrategy; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAup; import it.infn.mw.iam.persistence.model.IamEmailNotification; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/AddressAdapter.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/AddressAdapter.java deleted file mode 100644 index 99f0c2d1ae..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/AddressAdapter.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * 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.persistence.repository; - -import org.mitre.openid.connect.model.Address; - -import it.infn.mw.iam.persistence.model.IamAddress; - -public class AddressAdapter implements Address { - - private static final long serialVersionUID = 1L; - - final IamAddress address; - - private AddressAdapter(it.infn.mw.iam.persistence.model.IamAddress address) { - this.address = address; - } - - public Long getId() { - return address.getId(); - } - - public String getFormatted() { - return address.getFormatted(); - } - - public void setFormatted(String formatted) { - address.setFormatted(formatted); - } - - public String getStreetAddress() { - return address.getStreetAddress(); - } - - public void setStreetAddress(String streetAddress) { - address.setStreetAddress(streetAddress); - } - - public String getLocality() { - return address.getLocality(); - } - - public void setLocality(String locality) { - address.setLocality(locality); - } - - public String getRegion() { - return address.getRegion(); - } - - public void setRegion(String region) { - address.setRegion(region); - } - - public String getPostalCode() { - return address.getPostalCode(); - } - - public void setPostalCode(String postalCode) { - address.setPostalCode(postalCode); - } - - public String getCountry() { - return address.getCountry(); - } - - public void setCountry(String country) { - address.setCountry(country); - } - - public static AddressAdapter forIamAddress(IamAddress a) { - return new AddressAdapter(a); - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/DefaultPageCriteria.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/DefaultPageCriteria.java new file mode 100644 index 0000000000..3ef4dfc828 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/DefaultPageCriteria.java @@ -0,0 +1,45 @@ +/** + * 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.persistence.repository; + +public class DefaultPageCriteria implements PageCriteria { + + public static final int DEFAULT_PAGE_NUMBER = 0; + public static final int DEFAULT_PAGE_SIZE = 100; + + private final int pageNumber; + private final int pageSize; + + public DefaultPageCriteria(){ + this(DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE); + } + + public DefaultPageCriteria(int pageNumber, int pageSize) { + this.pageNumber = pageNumber; + this.pageSize = pageSize; + } + + @Override + public int getPageNumber() { + return pageNumber; + } + + @Override + public int getPageSize() { + return pageSize; + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamUserinfoRepository.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamUserinfoRepository.java deleted file mode 100644 index 5de0bdd97f..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamUserinfoRepository.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 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.persistence.repository; - -import java.util.Optional; - -import org.mitre.openid.connect.model.UserInfo; -import org.mitre.openid.connect.repository.UserInfoRepository; -import org.springframework.beans.factory.annotation.Autowired; - -import it.infn.mw.iam.persistence.model.IamAccount; - - -public class IamUserinfoRepository implements UserInfoRepository { - - @Autowired - private IamAccountRepository repo; - - @Override - public UserInfo getByUsername(String username) { - - Optional account = repo.findByUsername(username); - - if (account.isPresent()) { - return UserInfoAdapter.forIamUserInfo(account.get().getUserInfo()); - } - - return null; - } - - @Override - public UserInfo getByEmailAddress(String email) { - - Optional account = repo.findByEmail(email); - - if (account.isPresent()) { - return UserInfoAdapter.forIamUserInfo(account.get().getUserInfo()); - } - - return null; - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/OAuth2TokenRepository.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/OAuth2TokenRepository.java new file mode 100644 index 0000000000..f8b44fa1e9 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/OAuth2TokenRepository.java @@ -0,0 +1,71 @@ +/** + * 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.persistence.repository; + +import java.util.List; +import java.util.Set; + +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; + +public interface OAuth2TokenRepository { + + public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity token); + + public OAuth2RefreshTokenEntity getRefreshTokenByValue(String refreshTokenValue); + + public OAuth2RefreshTokenEntity getRefreshTokenById(Long Id); + + public void clearAccessTokensForRefreshToken(OAuth2RefreshTokenEntity refreshToken); + + public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken); + + public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken); + + public OAuth2AccessTokenEntity getAccessTokenByValue(String accessTokenValue); + + public OAuth2AccessTokenEntity getAccessTokenById(Long id); + + public void removeAccessToken(OAuth2AccessTokenEntity accessToken); + + public void clearTokensForClient(ClientDetailsEntity client); + + public List getAccessTokensForClient(ClientDetailsEntity client); + + public List getRefreshTokensForClient(ClientDetailsEntity client); + + public Set getAccessTokensByUserName(String name); + + public Set getRefreshTokensByUserName(String name); + + public Set getAllAccessTokens(); + + public Set getAllRefreshTokens(); + + public Set getAllExpiredAccessTokens(); + + public Set getAllExpiredAccessTokens(PageCriteria pageCriteria); + + public Set getAllExpiredRefreshTokens(); + + public Set getAllExpiredRefreshTokens(PageCriteria pageCriteria); + + public List getAccessTokensForApprovedSite(ApprovedSite approvedSite); + +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamDeviceCodeRepository.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/PageCriteria.java similarity index 78% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamDeviceCodeRepository.java rename to iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/PageCriteria.java index 46a649bdae..9061f3556d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamDeviceCodeRepository.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/PageCriteria.java @@ -15,9 +15,8 @@ */ package it.infn.mw.iam.persistence.repository; -import org.mitre.oauth2.model.DeviceCode; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface IamDeviceCodeRepository extends JpaRepository { +public interface PageCriteria { + public int getPageNumber(); + public int getPageSize(); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/UserInfoAdapter.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/UserInfoAdapter.java deleted file mode 100644 index 45ee1b53da..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/UserInfoAdapter.java +++ /dev/null @@ -1,221 +0,0 @@ -/** - * 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.persistence.repository; - -import static java.lang.String.format; - -import org.mitre.openid.connect.model.Address; -import org.mitre.openid.connect.model.UserInfo; - -import com.google.gson.JsonObject; - -import it.infn.mw.iam.persistence.model.IamUserInfo; - -public class UserInfoAdapter implements UserInfo { - - private static final long serialVersionUID = 1L; - - final IamUserInfo userinfo; - - private UserInfoAdapter(IamUserInfo userinfo) { - this.userinfo = userinfo; - } - - public String getBirthdate() { - return userinfo.getBirthdate(); - } - - public String getEmail() { - return userinfo.getEmail(); - } - - public Boolean getEmailVerified() { - return userinfo.getEmailVerified(); - } - - public String getFamilyName() { - return userinfo.getFamilyName(); - } - - public String getGender() { - return userinfo.getGender(); - } - - public String getGivenName() { - return userinfo.getGivenName(); - } - - public String getLocale() { - return userinfo.getLocale(); - } - - public String getMiddleName() { - return userinfo.getMiddleName(); - } - - public String getNickname() { - return userinfo.getNickname(); - } - - public String getPhoneNumber() { - return userinfo.getPhoneNumber(); - } - - public Boolean getPhoneNumberVerified() { - return userinfo.getPhoneNumberVerified(); - } - - public String getPicture() { - return userinfo.getPicture(); - } - - public String getPreferredUsername() { - return userinfo.getPreferredUsername(); - } - - public String getProfile() { - return userinfo.getProfile(); - } - - public JsonObject getSource() { - return userinfo.getSource(); - } - - public String getSub() { - return userinfo.getSub(); - } - - public String getWebsite() { - return userinfo.getWebsite(); - } - - public String getZoneinfo() { - return userinfo.getZoneinfo(); - } - - public void setBirthdate(String birthdate) { - userinfo.setBirthdate(birthdate); - } - - public void setEmail(String email) { - userinfo.setEmail(email); - } - - public void setEmailVerified(Boolean emailVerified) { - userinfo.setEmailVerified(emailVerified); - } - - public void setFamilyName(String familyName) { - userinfo.setFamilyName(familyName); - } - - public void setGender(String gender) { - userinfo.setGender(gender); - } - - public void setGivenName(String givenName) { - userinfo.setGivenName(givenName); - } - - public void setLocale(String locale) { - userinfo.setLocale(locale); - } - - public void setMiddleName(String middleName) { - userinfo.setMiddleName(middleName); - } - - public void setNickname(String nickname) { - userinfo.setNickname(nickname); - } - - public void setPhoneNumber(String phoneNumber) { - userinfo.setPhoneNumber(phoneNumber); - } - - public void setPhoneNumberVerified(Boolean phoneNumberVerified) { - userinfo.setPhoneNumberVerified(phoneNumberVerified); - } - - public void setPicture(String picture) { - userinfo.setPicture(picture); - } - - public void setPreferredUsername(String preferredUsername) { - userinfo.setPreferredUsername(preferredUsername); - } - - public void setProfile(String profile) { - userinfo.setProfile(profile); - } - - public void setSrc(JsonObject src) { - userinfo.setSrc(src); - } - - public void setSub(String sub) { - userinfo.setSub(sub); - } - - public void setUpdatedTime(String updatedTime) { - userinfo.setUpdatedTime(updatedTime); - } - - public void setWebsite(String website) { - userinfo.setWebsite(website); - } - - public void setZoneinfo(String zoneinfo) { - userinfo.setZoneinfo(zoneinfo); - } - - public JsonObject toJson() { - return userinfo.toJson(); - } - - public String getName() { - return userinfo.getName(); - } - - public void setName(String name) { - userinfo.setName(name); - } - - @Override - public Address getAddress() { - - return AddressAdapter.forIamAddress(userinfo.getAddress()); - } - - @Override - public void setAddress(Address address) { - // no op - } - - @Override - public String getUpdatedTime() { - return format("%d", userinfo.getUpdatedTime()); - } - - public static UserInfoAdapter forIamUserInfo(IamUserInfo info) { - return new UserInfoAdapter(info); - } - - public IamUserInfo getUserinfo() { - return userinfo; - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/ClientSpecs.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/ClientSpecs.java index ea68ccb43a..263970df53 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/ClientSpecs.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/ClientSpecs.java @@ -15,14 +15,12 @@ */ package it.infn.mw.iam.persistence.repository.client; - - import static javax.persistence.criteria.JoinType.LEFT; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.data.jpa.domain.Specification; import it.infn.mw.iam.api.client.search.ClientSearchForm; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; public class ClientSpecs { static final String CLIENT_ID = "clientId"; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamOAuth2ClientRepositoryAdapter.java b/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamOAuth2ClientRepositoryAdapter.java deleted file mode 100644 index b1ce57fd79..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamOAuth2ClientRepositoryAdapter.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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.persistence.repository.client; - -import java.util.Collection; - -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.repository.OAuth2ClientRepository; -import org.springframework.transaction.annotation.Transactional; - -import com.google.common.collect.ImmutableList; - -@Transactional -public class IamOAuth2ClientRepositoryAdapter implements OAuth2ClientRepository { - - private final IamClientRepository iamClientRepo; - private final IamAccountClientRepository iamAccountClientRepo; - - public IamOAuth2ClientRepositoryAdapter(IamClientRepository iamClientRepo, - IamAccountClientRepository iamAccountClientRepo) { - this.iamClientRepo = iamClientRepo; - this.iamAccountClientRepo = iamAccountClientRepo; - } - - @Override - public ClientDetailsEntity getById(Long id) { - return iamClientRepo.findById(id).orElse(null); - } - - @Override - public ClientDetailsEntity getClientByClientId(String clientId) { - return iamClientRepo.findByClientId(clientId).orElse(null); - } - - @Override - public ClientDetailsEntity saveClient(ClientDetailsEntity client) { - return iamClientRepo.save(client); - } - - @Override - public void deleteClient(ClientDetailsEntity client) { - - iamAccountClientRepo.deleteByClientId(client.getId()); - iamClientRepo.delete(client); - } - - @Override - public ClientDetailsEntity updateClient(Long id, ClientDetailsEntity client) { - client.setId(id); - return iamClientRepo.save(client); - } - - @Override - public Collection getAllClients() { - return ImmutableList.copyOf(iamClientRepo.findAll()); - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRCAuthTokenRequestor.java b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRCAuthTokenRequestor.java index 376ae084c7..bd36122a3b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRCAuthTokenRequestor.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRCAuthTokenRequestor.java @@ -23,11 +23,8 @@ import java.nio.charset.StandardCharsets; import java.util.Optional; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.config.ServerConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -41,6 +38,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import it.infn.mw.iam.authn.oidc.RestTemplateFactory; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; import it.infn.mw.iam.authn.oidc.model.TokenEndpointErrorResponse; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.rcauth.oidc.RCAuthTokenResponseVerifier; @@ -57,8 +56,7 @@ public class DefaultRCAuthTokenRequestor implements RCAuthTokenRequestor { final ServerConfigurationService serverConfigService; final ObjectMapper objectMapper; final RCAuthTokenResponseVerifier responseVerifier; - - @Autowired + public DefaultRCAuthTokenRequestor(RestTemplateFactory restFactory, RCAuthProperties props, RCAuthTokenResponseVerifier verifier, IamProperties iamProps, ServerConfigurationService scs, ObjectMapper mapper) { @@ -97,13 +95,13 @@ protected HttpEntity> prepareTokenRequest(String c HttpHeaders headers = new HttpHeaders(); prepareBasicAuthenticationHeader(headers); - + MultiValueMap tokenRequestParams = new LinkedMultiValueMap<>(); - + tokenRequestParams.add("grant_type", "authorization_code"); tokenRequestParams.add("redirect_uri", String.format("%s%s", iamProperties.getBaseUrl(), CALLBACK_PATH)); - + tokenRequestParams.add("client_id", rcAuthProperties.getClientId()); tokenRequestParams.add("client_secret", rcAuthProperties.getClientSecret()); tokenRequestParams.add("code", code); @@ -124,32 +122,32 @@ protected String resolveTokenEndpoint() { return conf.getTokenEndpointUri(); } } - + private void verifyTokenResponse(RCAuthTokenResponse response) { - + ServerConfiguration conf = serverConfigService.getServerConfiguration(rcAuthProperties.getIssuer()); - + responseVerifier.verify(conf, response); - + } @Override public RCAuthTokenResponse getAccessToken(String code) { RestTemplate rt = restFactory.newRestTemplate(); - + // ugly hack needed to workaround buggy oauth myproxy implementation // that does not set the content type for the token response rt.getInterceptors().add(new AddContentTypeInterceptor(APPLICATION_JSON_VALUE)); try { - RCAuthTokenResponse response = rt.postForObject(resolveTokenEndpoint(), prepareTokenRequest(code), - RCAuthTokenResponse.class); - + RCAuthTokenResponse response = rt.postForObject(resolveTokenEndpoint(), + prepareTokenRequest(code), RCAuthTokenResponse.class); + verifyTokenResponse(response); - + return response; } catch (HttpStatusCodeException e) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRcAuthRequestService.java b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRcAuthRequestService.java index ab8f4a6f1f..737538c170 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRcAuthRequestService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/DefaultRcAuthRequestService.java @@ -32,17 +32,16 @@ import javax.servlet.http.HttpSession; import org.bouncycastle.operator.OperatorCreationException; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.config.ServerConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; import it.infn.mw.iam.config.IamProperties; @Service @@ -79,8 +78,6 @@ public class DefaultRcAuthRequestService implements RCAuthRequestService { final RCAuthCertificateRequestor certRequestor; final SecureRandom rng; - - @Autowired public DefaultRcAuthRequestService(IamProperties iamProperties, RCAuthProperties properties, ServerConfigurationService scs, RCAuthTokenRequestor requestor, RCAuthCertificateRequestor certRequestor) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/DefaultRCAuthTokenResponseVerifier.java b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/DefaultRCAuthTokenResponseVerifier.java index 9821a50431..fbf95a9fef 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/DefaultRCAuthTokenResponseVerifier.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/DefaultRCAuthTokenResponseVerifier.java @@ -21,17 +21,16 @@ import java.time.Clock; import java.time.Instant; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.openid.connect.config.ServerConfiguration; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; import it.infn.mw.iam.rcauth.RCAuthError; import it.infn.mw.iam.rcauth.RCAuthProperties; import it.infn.mw.iam.rcauth.RCAuthTokenResponse; @@ -40,13 +39,12 @@ @ConditionalOnProperty(name="rcauth.enabled", havingValue="true") public class DefaultRCAuthTokenResponseVerifier implements RCAuthTokenResponseVerifier { - final JWKSetCacheService jwkService; + final JwkSetCacheService jwkService; final RCAuthProperties rcAuthProperties; final IamProperties iamProperties; final Clock clock; - @Autowired - public DefaultRCAuthTokenResponseVerifier(Clock clock, JWKSetCacheService jwkService, + public DefaultRCAuthTokenResponseVerifier(Clock clock, JwkSetCacheService jwkService, RCAuthProperties rcAuthProperties, IamProperties iamProperties) { this.clock = clock; this.jwkService = jwkService; @@ -74,7 +72,7 @@ protected void checkTemporalValidity(JWTClaimsSet idTokenClaims) { protected void validateTokenSignature(ServerConfiguration serverConfiguration, SignedJWT idToken) { - JWTSigningAndValidationService validator = + JwtSigningAndValidationService validator = jwkService.getValidator(serverConfiguration.getJwksUri()); if (isNull(validator)) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/RCAuthTokenResponseVerifier.java b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/RCAuthTokenResponseVerifier.java index 04f5155131..3db64e2c8f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/RCAuthTokenResponseVerifier.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/rcauth/oidc/RCAuthTokenResponseVerifier.java @@ -15,8 +15,7 @@ */ package it.infn.mw.iam.rcauth.oidc; -import org.mitre.openid.connect.config.ServerConfiguration; - +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; import it.infn.mw.iam.rcauth.RCAuthTokenResponse; public interface RCAuthTokenResponseVerifier { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/registration/DefaultRegistrationRequestService.java b/iam-login-service/src/main/java/it/infn/mw/iam/registration/DefaultRegistrationRequestService.java index 22eb82fd42..abbab50e27 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/registration/DefaultRegistrationRequestService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/registration/DefaultRegistrationRequestService.java @@ -25,9 +25,13 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -45,9 +49,6 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Table; - import it.infn.mw.iam.api.common.LabelDTOConverter; import it.infn.mw.iam.api.scim.exception.IllegalArgumentException; import it.infn.mw.iam.api.scim.exception.ScimResourceNotFoundException; @@ -124,7 +125,6 @@ public class DefaultRegistrationRequestService public static final String NICKNAME_ATTRIBUTE_KEY = "nickname"; - @Autowired public DefaultRegistrationRequestService(LabelDTOConverter labelConverter, IamProperties iamProperties) { this.labelConverter = labelConverter; @@ -137,16 +137,33 @@ private IamRegistrationRequest findRequestById(String requestUuid) { String.format("No request mapped to uuid [%s]", requestUuid))); } - private static final Table allowedStateTransitions = - new ImmutableTable.Builder() - .put(NEW, CONFIRMED, true) - .put(NEW, APPROVED, true) - .put(NEW, REJECTED, true) - .put(CONFIRMED, APPROVED, true) - .put(CONFIRMED, REJECTED, true) - .put(APPROVED, CONFIRMED, true) - .put(REJECTED, CONFIRMED, true) - .build(); + // private static final Table + // allowedStateTransitions = + // new ImmutableTable.Builder() + // .put(NEW, CONFIRMED, true) + // .put(NEW, APPROVED, true) + // .put(NEW, REJECTED, true) + // .put(CONFIRMED, APPROVED, true) + // .put(CONFIRMED, REJECTED, true) + // .put(APPROVED, CONFIRMED, true) + // .put(REJECTED, CONFIRMED, true) + // .build(); + + private static final Map> ALLOWED_STATE_TRANSITIONS; + + static { + EnumMap> map = + new EnumMap<>(IamRegistrationRequestStatus.class); + + map.put(NEW, EnumSet.of(CONFIRMED, APPROVED, REJECTED)); + map.put(CONFIRMED, EnumSet.of(APPROVED, REJECTED)); + map.put(APPROVED, EnumSet.of(CONFIRMED)); + map.put(REJECTED, EnumSet.of(CONFIRMED)); + + ALLOWED_STATE_TRANSITIONS = Collections.unmodifiableMap(map); + } + private void createAupSignatureForAccountIfNeeded(IamAccount account) { iamAupRepo.findDefaultAup().ifPresent(aup -> accountService.signAup(account, aup)); @@ -287,11 +304,10 @@ public Boolean emailAvailable(String emailAddress) { private boolean checkStatusTransition(IamRegistrationRequestStatus currentStatus, final IamRegistrationRequestStatus newStatus) { - return allowedStateTransitions.contains(currentStatus, newStatus); + return ALLOWED_STATE_TRANSITIONS.containsKey(currentStatus) + && ALLOWED_STATE_TRANSITIONS.get(currentStatus).contains(newStatus); } - - private RegistrationRequestDto handleApprove(IamRegistrationRequest request) { IamAccount account = request.getAccount(); account.setActive(true); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/registration/RegistrationConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/registration/RegistrationConverter.java index 4978a75570..d28337820e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/registration/RegistrationConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/registration/RegistrationConverter.java @@ -15,14 +15,13 @@ */ package it.infn.mw.iam.registration; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.springframework.stereotype.Service; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.common.LabelDTO; import it.infn.mw.iam.api.common.LabelDTOConverter; import it.infn.mw.iam.core.IamRegistrationRequestStatus; @@ -54,7 +53,7 @@ public RegistrationRequestDto fromEntity(final IamRegistrationRequest entity) { dto.setAccountId(entity.getAccount().getUuid()); dto.setNotes(entity.getNotes()); - List labels = Lists.newArrayList(); + List labels = new ArrayList<>(); for (IamLabel label : entity.getLabels()) { labels.add(labelConverter.dtoFromEntity(label)); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/registration/validation/CernHrDbRequestValidatorService.java b/iam-login-service/src/main/java/it/infn/mw/iam/registration/validation/CernHrDbRequestValidatorService.java index 24351d4b16..9ee5be082c 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/registration/validation/CernHrDbRequestValidatorService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/registration/validation/CernHrDbRequestValidatorService.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.registration.validation; -import static com.google.common.base.Strings.isNullOrEmpty; import static it.infn.mw.iam.core.lifecycle.cern.CernHrLifecycleUtils.LABEL_CERN_PREFIX; import static it.infn.mw.iam.registration.validation.RegistrationRequestValidationResult.error; import static it.infn.mw.iam.registration.validation.RegistrationRequestValidationResult.invalid; @@ -23,6 +22,7 @@ import static java.lang.String.format; import static java.util.Objects.isNull; +import java.util.ArrayList; import java.util.Optional; import org.slf4j.Logger; @@ -31,8 +31,6 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import com.google.common.collect.Lists; - import it.infn.mw.iam.api.common.LabelDTO; import it.infn.mw.iam.api.registration.cern.CernHrDBApiService; import it.infn.mw.iam.api.registration.cern.CernHrDbApiError; @@ -69,7 +67,7 @@ public void addPersonIdLabel(RegistrationRequestDto request, String personId) { .build(); if (isNull(request.getLabels())) { - request.setLabels(Lists.newArrayList()); + request.setLabels(new ArrayList<>()); } request.getLabels().add(label); @@ -108,7 +106,7 @@ public RegistrationRequestValidationResult validateRegistrationRequest( final String cernPersonId = auth.getAdditionalAttributes().get(cernProperties.getPersonIdClaim()); - if (isNullOrEmpty(cernPersonId)) { + if (cernPersonId == null || cernPersonId.isBlank()) { return invalid(format("CERN person id claim '%s' not found in authentication attributes", cernProperties.getPersonIdClaim())); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/IdTokenHashUtils.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/IdTokenHashUtils.java new file mode 100644 index 0000000000..d9482d2155 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/IdTokenHashUtils.java @@ -0,0 +1,88 @@ +/** + * 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.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.util.Base64URL; + +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; + +public class IdTokenHashUtils { + + private static final Logger logger = LoggerFactory.getLogger(IdTokenHashUtils.class); + + public static Base64URL getCodeHash(JWSAlgorithm signingAlg, String code) { + return getHash(signingAlg, code.getBytes()); + } + + public static Base64URL getAccessTokenHash(JWSAlgorithm signingAlg, + OAuth2AccessTokenEntity token) { + + byte[] tokenBytes = token.getJwt().serialize().getBytes(); + + return getHash(signingAlg, tokenBytes); + + } + + public static Base64URL getHash(JWSAlgorithm signingAlg, byte[] bytes) { + + String hashAlg = null; + + if (signingAlg.equals(JWSAlgorithm.HS256) || signingAlg.equals(JWSAlgorithm.ES256) + || signingAlg.equals(JWSAlgorithm.RS256) || signingAlg.equals(JWSAlgorithm.PS256)) { + hashAlg = "SHA-256"; + } + + else if (signingAlg.equals(JWSAlgorithm.ES384) || signingAlg.equals(JWSAlgorithm.HS384) + || signingAlg.equals(JWSAlgorithm.RS384) || signingAlg.equals(JWSAlgorithm.PS384)) { + hashAlg = "SHA-384"; + } + + else if (signingAlg.equals(JWSAlgorithm.ES512) || signingAlg.equals(JWSAlgorithm.HS512) + || signingAlg.equals(JWSAlgorithm.RS512) || signingAlg.equals(JWSAlgorithm.PS512)) { + hashAlg = "SHA-512"; + } + + if (hashAlg != null) { + + try { + MessageDigest hasher = MessageDigest.getInstance(hashAlg); + hasher.reset(); + hasher.update(bytes); + + byte[] hashBytes = hasher.digest(); + byte[] hashBytesLeftHalf = Arrays.copyOf(hashBytes, hashBytes.length / 2); + Base64URL encodedHash = Base64URL.encode(hashBytesLeftHalf); + + return encodedHash; + + } catch (NoSuchAlgorithmException e) { + + logger.error("No such algorithm error: ", e); + } + } + + return null; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/JWKKeystoreLoader.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/JWKKeystoreLoader.java index 16abdac52a..21e4e59b3a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/util/JWKKeystoreLoader.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/JWKKeystoreLoader.java @@ -15,11 +15,10 @@ */ package it.infn.mw.iam.util; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import it.infn.mw.iam.config.error.IAMJWTKeystoreError; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; public class JWKKeystoreLoader { @@ -29,14 +28,9 @@ public JWKKeystoreLoader(ResourceLoader resourceLoader) { this.loader = resourceLoader; } - public JWKSetKeyStore loadKeystoreFromLocation(String keyStoreLocation) { + public JwkSetKeyStore loadKeystoreFromLocation(String keyStoreLocation) { try { - Resource keyStoreResource = loader.getResource(keyStoreLocation); - - JWKSetKeyStore keyStore = new JWKSetKeyStore(); - keyStore.setLocation(keyStoreResource); - - return keyStore; + return new JwkSetKeyStore(loader.getResource(keyStoreLocation)); } catch (Exception e) { throw new IAMJWTKeystoreError("Error initializing JWKProperties keystore: " + e.getMessage(), e); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/JsonUtils.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/JsonUtils.java new file mode 100644 index 0000000000..66a1950165 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/JsonUtils.java @@ -0,0 +1,255 @@ +/** + * 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.util; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; + +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; + +public class JsonUtils { + + private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); + + private static Gson gson = new Gson(); + + public static JsonElement getAsArray(Set value) { + return getAsArray(value, false); + } + + public static JsonElement getAsArray(Set value, boolean preserveEmpty) { + if (!preserveEmpty && value != null && value.isEmpty()) { + return JsonNull.INSTANCE; + } + return gson.toJsonTree(value, new TypeToken>() {}.getType()); + } + + public static Date getAsDate(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return new Date(e.getAsInt() * 1000L); + } + } + return null; + } + + public static JWEAlgorithm getAsJweAlgorithm(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return JWEAlgorithm.parse(s); + } + return null; + } + + public static EncryptionMethod getAsJweEncryptionMethod(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return EncryptionMethod.parse(s); + } + return null; + } + + public static JWSAlgorithm getAsJwsAlgorithm(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return JWSAlgorithm.parse(s); + } + return null; + } + + public static PKCEAlgorithm getAsPkceAlgorithm(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return PKCEAlgorithm.parse(s); + } + return null; + } + + public static String getAsString(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return e.getAsString(); + } + } + return null; + } + + public static Boolean getAsBoolean(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return e.getAsBoolean(); + } + } + return null; + } + + public static Long getAsLong(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return e.getAsLong(); + } + } + return null; + } + + public static Set getAsStringSet(JsonObject o, String member) throws JsonSyntaxException { + if (o.has(member)) { + if (o.get(member).isJsonArray()) { + return gson.fromJson(o.get(member), new TypeToken>() {}.getType()); + } + return Sets.newHashSet(o.get(member).getAsString()); + } + return null; + } + + public static List getAsStringList(JsonObject o, String member) + throws JsonSyntaxException { + if (o.has(member)) { + if (o.get(member).isJsonArray()) { + return gson.fromJson(o.get(member), new TypeToken>() {}.getType()); + } + return Lists.newArrayList(o.get(member).getAsString()); + } + return null; + } + + public static List getAsJwsAlgorithmList(JsonObject o, String member) { + List strings = getAsStringList(o, member); + if (strings != null) { + List algs = new ArrayList<>(); + for (String alg : strings) { + algs.add(JWSAlgorithm.parse(alg)); + } + return algs; + } + return null; + } + + public static List getAsJweAlgorithmList(JsonObject o, String member) { + List strings = getAsStringList(o, member); + if (strings != null) { + List algs = new ArrayList<>(); + for (String alg : strings) { + algs.add(JWEAlgorithm.parse(alg)); + } + return algs; + } + return null; + } + + public static List getAsEncryptionMethodList(JsonObject o, String member) { + List strings = getAsStringList(o, member); + if (strings != null) { + List algs = new ArrayList<>(); + for (String alg : strings) { + algs.add(EncryptionMethod.parse(alg)); + } + return algs; + } + return null; + } + + public static Map readMap(JsonReader reader) throws IOException { + Map map = new HashMap<>(); + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + Object value = null; + switch (reader.peek()) { + case STRING: + value = reader.nextString(); + break; + case BOOLEAN: + value = reader.nextBoolean(); + break; + case NUMBER: + value = reader.nextLong(); + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + map.put(name, value); + } + reader.endObject(); + return map; + } + + public static Set readSet(JsonReader reader) throws IOException { + Set arraySet = null; + reader.beginArray(); + switch (reader.peek()) { + case STRING: + arraySet = new HashSet<>(); + while (reader.hasNext()) { + arraySet.add(reader.nextString()); + } + break; + case NUMBER: + arraySet = new HashSet<>(); + while (reader.hasNext()) { + arraySet.add(reader.nextLong()); + } + break; + default: + arraySet = new HashSet<>(); + break; + } + reader.endArray(); + return arraySet; + } + + public static void writeNullSafeArray(JsonWriter writer, Set items) throws IOException { + if (items != null) { + writer.beginArray(); + for (String s : items) { + writer.value(s); + } + writer.endArray(); + } else { + writer.nullValue(); + } + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/SimpleRandomValueStringGenerator.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/SimpleRandomValueStringGenerator.java new file mode 100644 index 0000000000..4917f0e4a4 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/SimpleRandomValueStringGenerator.java @@ -0,0 +1,50 @@ +/** + * 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.util; + +import java.security.SecureRandom; + +public class SimpleRandomValueStringGenerator { + + private static final String DEFAULT_ALPHABET = + "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"; + + private final SecureRandom random = new SecureRandom(); + private final String alphabet; + private final int length; + + public SimpleRandomValueStringGenerator() { + this(6, DEFAULT_ALPHABET); + } + + public SimpleRandomValueStringGenerator(int length) { + this(length, DEFAULT_ALPHABET); + } + + public SimpleRandomValueStringGenerator(int length, String alphabet) { + this.length = length; + this.alphabet = alphabet; + } + + public String generate() { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + sb.append(alphabet.charAt(random.nextInt(alphabet.length()))); + } + return sb.toString(); + } +} + diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/jpa/JpaUtil.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/jpa/JpaUtil.java new file mode 100644 index 0000000000..9bec11ea78 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/jpa/JpaUtil.java @@ -0,0 +1,51 @@ +/** + * 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.util.jpa; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import it.infn.mw.iam.persistence.repository.PageCriteria; + +public class JpaUtil { + + public static T getSingleResult(List list) { + switch (list.size()) { + case 0: + return null; + case 1: + return list.get(0); + default: + throw new IllegalStateException("Expected single result, got " + list.size()); + } + } + + public static List getResultPage(TypedQuery query, PageCriteria pageCriteria) { + + query.setMaxResults(pageCriteria.getPageSize()); + query.setFirstResult(pageCriteria.getPageNumber() * pageCriteria.getPageSize()); + return query.getResultList(); + } + + public static T saveOrUpdate(I id, EntityManager entityManager, T entity) { + + T tmp = entityManager.merge(entity); + entityManager.flush(); + return tmp; + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/test/OidcSecurityContextBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/test/OidcSecurityContextBuilder.java index faae365d8d..10a8956867 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/util/test/OidcSecurityContextBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/test/OidcSecurityContextBuilder.java @@ -22,8 +22,6 @@ import java.util.Date; import java.util.Map; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; -import org.mitre.openid.connect.model.UserInfo; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -34,10 +32,12 @@ import com.nimbusds.jwt.PlainJWT; import it.infn.mw.iam.authn.oidc.OidcExternalAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; +import it.infn.mw.iam.persistence.model.IamUserInfo; public class OidcSecurityContextBuilder extends SecurityContextBuilderSupport { - private UserInfo userInfo = null; + private IamUserInfo userInfo = null; private Map stringClaims = Maps.newHashMap(); public OidcSecurityContextBuilder() { @@ -47,14 +47,14 @@ public OidcSecurityContextBuilder() { username = "test-oidc-subject"; - userInfo = mock(UserInfo.class); + userInfo = mock(IamUserInfo.class); } @Override public SecurityContext buildSecurityContext() { OIDCAuthenticationToken authToken = mock(OIDCAuthenticationToken.class); - UserInfo ui = mock(UserInfo.class); + IamUserInfo ui = mock(IamUserInfo.class); when(authToken.getUserInfo()).thenReturn(ui); JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/test/saml/SamlSecurityContextBuilder.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/test/saml/SamlSecurityContextBuilder.java index 26f79c8f70..aa4fdddff4 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/util/test/saml/SamlSecurityContextBuilder.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/test/saml/SamlSecurityContextBuilder.java @@ -15,7 +15,6 @@ */ package it.infn.mw.iam.util.test.saml; -import static com.google.common.base.Strings.isNullOrEmpty; import static it.infn.mw.iam.authn.saml.util.Saml2Attribute.CERN_FIRST_NAME; import static it.infn.mw.iam.authn.saml.util.Saml2Attribute.CERN_PERSON_ID; import static it.infn.mw.iam.authn.saml.util.Saml2Attribute.EPPN; @@ -30,8 +29,6 @@ import org.springframework.security.providers.ExpiringUsernameAuthenticationToken; import org.springframework.security.saml.SAMLCredential; -import com.google.common.base.Strings; - import it.infn.mw.iam.authn.saml.SamlExternalAuthenticationToken; import it.infn.mw.iam.authn.saml.util.Saml2Attribute; import it.infn.mw.iam.authn.saml.util.SamlAttributeNames; @@ -53,7 +50,7 @@ public SamlSecurityContextBuilder() { public SamlSecurityContextBuilder notNullOrEmptySamlAttribute(Saml2Attribute attribute, String attributeValue) { - if (!isNullOrEmpty(attributeValue)) { + if (attributeValue != null && !attributeValue.isBlank()) { samlAttribute(attribute, attributeValue); } return this; @@ -79,7 +76,7 @@ public SecurityContextBuilderSupport email(String email) { @Override public SecurityContextBuilderSupport name(String givenName, String familyName) { - if (!Strings.isNullOrEmpty(givenName) && !Strings.isNullOrEmpty(familyName)) { + if (givenName != null && !givenName.isBlank() && familyName != null && !familyName.isBlank()) { when(samlCredential.getAttributeAsString(GIVEN_NAME.getAttributeName())) .thenReturn(givenName); when(samlCredential.getAttributeAsString(SN.getAttributeName())).thenReturn(familyName); @@ -90,9 +87,8 @@ public SecurityContextBuilderSupport name(String givenName, String familyName) { @Override public SecurityContextBuilderSupport username(String username) { - if (!Strings.isNullOrEmpty(username)) { - when(samlCredential.getAttributeAsString(EPPN.getAttributeName())) - .thenReturn(username); + if (username != null && !username.isBlank()) { + when(samlCredential.getAttributeAsString(EPPN.getAttributeName())).thenReturn(username); } return this; } diff --git a/iam-login-service/src/main/resources/application-h2.yml b/iam-login-service/src/main/resources/application-h2.yml index 38c3a3e5af..ed6e6ab54e 100644 --- a/iam-login-service/src/main/resources/application-h2.yml +++ b/iam-login-service/src/main/resources/application-h2.yml @@ -47,6 +47,13 @@ iam: versioned-static-resources: enable-versioning: false +task: + aupReminder: -1 + tokenCleanupPeriodMsec: -1 + approvalCleanupPeriodMsec: -1 + deviceCodeCleanupPeriodMsec: -1 + wellKnownCacheCleanupPeriodSecs: 300 + management: health: mail: diff --git a/iam-login-service/src/main/resources/application-openid-federation.yml b/iam-login-service/src/main/resources/application-openid-federation.yml index b21ea1e834..8d8d1bdef6 100644 --- a/iam-login-service/src/main/resources/application-openid-federation.yml +++ b/iam-login-service/src/main/resources/application-openid-federation.yml @@ -15,6 +15,7 @@ # openid-federation: + enabled: ${IAM_OIDFED_ENABLED:false} trust-anchors: ${IAM_OIDFED_TRUST_ANCHORS:https://trust-anchor.sandbox.eosc.grnet.gr} entity-configuration: expiration-seconds: ${IAM_OIDFED_ENTITY_CONFIGURATION_EXPIRATION_SECONDS:86400} diff --git a/iam-login-service/src/main/resources/application.properties b/iam-login-service/src/main/resources/application.properties index 3face04bfe..2158a40f0d 100644 --- a/iam-login-service/src/main/resources/application.properties +++ b/iam-login-service/src/main/resources/application.properties @@ -50,4 +50,6 @@ logging.level.org.eclipse.persistence=DEBUG # logging.level.com.fasterxml.jackson=INFO # logging.level.org.apache.http.wire=DEBUG -# logging.level.org.mitre=INFO \ No newline at end of file +# logging.level.org.mitre=INFO + +spring.freemarker.enabled=false diff --git a/iam-login-service/src/main/resources/application.yml b/iam-login-service/src/main/resources/application.yml index 1f97d874fb..209aff0b12 100644 --- a/iam-login-service/src/main/resources/application.yml +++ b/iam-login-service/src/main/resources/application.yml @@ -217,6 +217,7 @@ notification: resetPassword: Reset your ${iam.organisation.name} account password task: + aupReminder: ${IAM_AUP_REMINDER_PERIOD_MSEC:14400} tokenCleanupPeriodMsec: ${IAM_TOKEN_CLEANUP_PERIOD_MSEC:300000} approvalCleanupPeriodMsec: ${IAM_APPROVAL_CLEANUP_PERIOD_MSEC:300000} deviceCodeCleanupPeriodMsec: ${IAM_DEVICE_CODE_CLEANUP_PERIOD_MSEC:300000} @@ -287,4 +288,4 @@ lifecycle: scim: include_authorities: ${IAM_SCIM_INCLUDE_AUTHORITIES:false} - include_managed_groups: ${IAM_SCIM_INCLUDE_MANAGED_GROUPS:false} + include_managed_groups: ${IAM_SCIM_INCLUDE_MANAGED_GROUPS:false} \ No newline at end of file diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/TestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/TestSupport.java index 2df68f2f93..63a0ba3acd 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/TestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/TestSupport.java @@ -24,8 +24,6 @@ import java.util.Map; import java.util.UUID; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -36,6 +34,8 @@ import it.infn.mw.iam.api.common.LabelDTO; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.test.util.oauth.MockOAuth2Request; public class TestSupport { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java index 9d3b5ed17d..732a3264b9 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java @@ -28,7 +28,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -36,11 +35,12 @@ import org.springframework.test.web.servlet.MockMvc; import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.core.CoreControllerTestSupport; import it.infn.mw.iam.test.scim.ScimRestUtilsMvc; import it.infn.mw.iam.test.util.WithMockOAuthUser; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticationAppSettingsTotpTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticationAppSettingsTotpTests.java index b67ba60443..f8d1580fd5 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticationAppSettingsTotpTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticationAppSettingsTotpTests.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -59,6 +60,7 @@ @ExtendWith(SpringExtension.class) @IamMockMvcIntegrationTest +@ActiveProfiles({"h2-test", "mfa"}) class AuthenticationAppSettingsTotpTests extends MultiFactorTestSupport { private MockMvc mvc; @@ -138,4 +140,4 @@ void testEnableAuthenticatorAppViaOauthAuthn() throws Exception { verify(totpMfaService, times(1)).verifyTotp(account, totp); verify(totpMfaService, times(1)).enableTotpMfa(account); } -} \ No newline at end of file +} diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java index bc93f2d848..cff2e9ccd9 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java @@ -40,6 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -64,6 +65,7 @@ @ExtendWith(SpringExtension.class) @IamMockMvcIntegrationTest +@ActiveProfiles({"h2-test", "mfa"}) class AuthenticatorAppSettingsControllerTests extends MultiFactorTestSupport { private MockMvc mvc; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/EnforceAupSignatureSuccessHandlerTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/EnforceAupSignatureSuccessHandlerTests.java index 0c8cd4f89e..49b4c1a1c2 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/EnforceAupSignatureSuccessHandlerTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/EnforceAupSignatureSuccessHandlerTests.java @@ -29,7 +29,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; @@ -41,6 +40,7 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.authn.EnforceAupSignatureSuccessHandler; import it.infn.mw.iam.core.web.aup.EnforceAupFilter; +import it.infn.mw.iam.core.web.util.AuthenticationTimeStamper; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.service.aup.AUPSignatureCheckService; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java index 8fa263587a..02563a4407 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java @@ -15,28 +15,15 @@ */ package it.infn.mw.iam.test.api.client; -import java.util.Optional; - +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; -import org.junit.jupiter.api.AfterEach; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import static org.springframework.http.MediaType.APPLICATION_JSON; -import org.springframework.security.test.context.support.WithAnonymousUser; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; @@ -45,13 +32,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Optional; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultMatcher; + import com.fasterxml.jackson.databind.ObjectMapper; import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.api.client.management.ClientManagementAPIController; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.tokens.Constants; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.api.TestSupport; import it.infn.mw.iam.test.core.CoreControllerTestSupport; import it.infn.mw.iam.test.oauth.client_registration.ClientRegistrationTestSupport.ClientJsonStringBuilder; @@ -88,7 +89,7 @@ class ClientManagementAPIIntegrationTests extends TestSupport { private IamClientRepository clientRepo; @Autowired - private ClientDetailsEntityService clientDetailsService; + private IamClientDetailsService clientDetailsService; @BeforeEach diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientRegistrationAPIIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientRegistrationAPIIntegrationTests.java index 99842e6474..8ffa7e10fa 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientRegistrationAPIIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientRegistrationAPIIntegrationTests.java @@ -48,7 +48,7 @@ import it.infn.mw.iam.api.common.client.AuthorizationGrantType; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.api.TestSupport; import it.infn.mw.iam.test.oauth.client_registration.ClientRegistrationTestSupport.ClientJsonStringBuilder; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/mitre/ProtectedResourceIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/mitre/ProtectedResourceIntegrationTests.java index 983dcd5875..bda85b5a20 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/mitre/ProtectedResourceIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/mitre/ProtectedResourceIntegrationTests.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.openid.connect.web.ProtectedResourceRegistrationEndpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -57,7 +56,7 @@ private ValidatableResponse doCreateProtectedResource(String clientJson) { .log() .all(true) .when() - .post("/" + ProtectedResourceRegistrationEndpoint.URL) + .post("/resource") .then() .log() .all(true); @@ -72,7 +71,7 @@ private ValidatableResponse doGetProtectedResource(String clientId, String rat) .log() .all(true) .when() - .get("/" + ProtectedResourceRegistrationEndpoint.URL + "/" + clientId) + .get("/resource/" + clientId) .then() .log() .all(true); @@ -90,7 +89,7 @@ private ValidatableResponse doUpdateProtectedResource(String clientId, String cl .log() .all(true) .when() - .put("/" + ProtectedResourceRegistrationEndpoint.URL + "/" + clientId) + .put("/resource/" + clientId) .then() .log() .all(true); @@ -105,7 +104,7 @@ private ValidatableResponse doDeleteProtectedResource(String clientId, String ra .log() .all(true) .when() - .delete("/" + ProtectedResourceRegistrationEndpoint.URL + "/" + clientId) + .delete("/resource/" + clientId) .then() .log() .all(true); @@ -160,7 +159,6 @@ void protectedResourceLifeCycle() throws Exception { assertNull(fromAPI.getAccessTokenValiditySeconds()); assertNull(fromAPI.getIdTokenValiditySeconds()); assertNull(fromAPI.getRefreshTokenValiditySeconds()); - assertEquals(0, fromAPI.getClientSecretExpiresAt().toInstant().getEpochSecond()); assertFalse(fromAPI.getScope().isEmpty()); assertEquals(1, fromAPI.getScope().size()); assertTrue(fromAPI.getScope().contains("openid")); @@ -184,7 +182,6 @@ void protectedResourceLifeCycle() throws Exception { assertNull(updated.getAccessTokenValiditySeconds()); assertNull(updated.getIdTokenValiditySeconds()); assertNull(updated.getRefreshTokenValiditySeconds()); - assertEquals(0, updated.getClientSecretExpiresAt().toInstant().getEpochSecond()); assertFalse(updated.getScope().isEmpty()); assertEquals(2, updated.getScope().size()); assertTrue(updated.getScope().contains("openid")); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetListTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetListTests.java index c5eb902d09..e858391d85 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetListTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetListTests.java @@ -16,6 +16,7 @@ package it.infn.mw.iam.test.api.tokens; import static it.infn.mw.iam.api.tokens.TokensControllerSupport.TOKENS_MAX_PAGE_SIZE; + import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -30,8 +31,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -44,7 +43,9 @@ import it.infn.mw.iam.api.common.OffsetPageable; import it.infn.mw.iam.api.scim.converter.ScimResourceLocationProvider; import it.infn.mw.iam.api.tokens.model.AccessToken; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.test.util.DateEqualModulo1Second; import it.infn.mw.iam.test.util.WithMockOAuthUser; @@ -264,7 +265,9 @@ void getAccessTokenListWithFullClientIdAndUserIdFilter() throws Exception { accessTokens.add(buildAccessToken(client2, TESTUSER2_USERNAME, SCOPES)); MultiValueMap params = MultiValueMapBuilder.builder() - .userId(user1.getUsername()).clientId(client1.getClientId()).build(); + .userId(user1.getUsername()) + .clientId(client1.getClientId()) + .build(); ListResponseDTO atl = getAccessTokenList(params); @@ -484,8 +487,11 @@ void getAllValidAccessTokensCountForUserAndClientWithExpiredTokens() throws Exce TEST_CLIENT_ID, new Date(), new OffsetPageable(0, 10)); assertThat(tokens.getTotalElements(), equalTo(1L)); - MultiValueMap params = MultiValueMapBuilder.builder().count(0) - .userId(TESTUSER_USERNAME).clientId(TEST_CLIENT_ID).build(); + MultiValueMap params = MultiValueMapBuilder.builder() + .count(0) + .userId(TESTUSER_USERNAME) + .clientId(TEST_CLIENT_ID) + .build(); ListResponseDTO atl = getAccessTokenList(params); @@ -498,18 +504,17 @@ void getAllValidAccessTokensCountForUserAndClientWithExpiredTokens() throws Exce void getAccessTokenListWithoutRegistrationTokens() throws Exception { ClientDetailsEntity client = loadTestClient(TEST_CLIENT_ID); - + ClientDetailsEntity client2 = loadTestClient(TEST_CLIENT2_ID); - + List accessTokens = Lists.newArrayList(); - - OAuth2AccessTokenEntity at = buildAccessToken(client, null, - SCOPES_REGISTRATION); - + + OAuth2AccessTokenEntity at = buildRegistrationAccessToken(client); + accessTokens.add(at); - + ListResponseDTO atl = getAccessTokenList(); - + assertThat(tokenRepository.count(), equalTo(1L)); assertThat(atl.getTotalResults(), equalTo(0L)); assertThat(atl.getStartIndex(), equalTo(1)); @@ -517,59 +522,57 @@ void getAccessTokenListWithoutRegistrationTokens() throws Exception { assertThat(atl.getResources().size(), equalTo(0)); accessTokens.add(buildAccessToken(client2, TESTUSER_USERNAME, SCOPES)); - + atl = getAccessTokenList(); - + assertThat(tokenRepository.count(), equalTo(2L)); assertThat(atl.getTotalResults(), equalTo(1L)); - + Page tokens = tokenRepository.findAllValidAccessTokens(new Date(), new OffsetPageable(0, 10)); - + tokens.forEach(t -> assertThat(t.getScope(), not(hasItem("registration-token")))); - - + + } @Test - void getAccessTokenListWithoutResourceTokens() throws - Exception { - - ClientDetailsEntity client = loadTestClient(TEST_CLIENT_ID); - + void getAccessTokenListWithoutResourceTokens() throws Exception { + + ClientDetailsEntity client = loadTestClient(TEST_CLIENT_ID); + ClientDetailsEntity client2 = loadTestClient(TEST_CLIENT2_ID); - + List accessTokens = Lists.newArrayList(); - - OAuth2AccessTokenEntity at = buildAccessToken(client, null, - SCOPES_RESOURCE); + + OAuth2AccessTokenEntity at = buildAccessToken(client, null, SCOPES_RESOURCE); Set scopes = new HashSet(); scopes.add("resource-token"); at.setScope(scopes); accessTokens.add(at); - + ListResponseDTO atl = getAccessTokenList(); - + assertThat(tokenRepository.count(), equalTo(1L)); assertThat(atl.getTotalResults(), equalTo(0L)); assertThat(atl.getStartIndex(), equalTo(1)); assertThat(atl.getItemsPerPage(), equalTo(0)); - assertThat(atl.getResources() .size(), equalTo(0)); - + assertThat(atl.getResources().size(), equalTo(0)); + accessTokens.add(buildAccessToken(client2, TESTUSER_USERNAME, SCOPES)); - + atl = getAccessTokenList(); - + assertThat(tokenRepository.count(), equalTo(2L)); assertThat(atl.getTotalResults(), equalTo(1L)); - + Page tokens = - tokenRepository.findAllValidAccessTokens(new Date(), new OffsetPageable(0, 10)); - + tokenRepository.findAllValidAccessTokens(new Date(), new OffsetPageable(0, 10)); + tokens.forEach(t -> assertThat(t.getScope(), not(hasItem("resource-token")))); clientService.deleteClient(client); - - } + + } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetRevokeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetRevokeTests.java index 5a1532354b..24cd74af0f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetRevokeTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/AccessTokenGetRevokeTests.java @@ -30,8 +30,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -41,7 +39,9 @@ import it.infn.mw.iam.api.scim.converter.ScimResourceLocationProvider; import it.infn.mw.iam.api.tokens.model.AccessToken; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.test.util.DateEqualModulo1Second; import it.infn.mw.iam.test.util.WithMockOAuthUser; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetListTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetListTests.java index 81446795e2..42724198a4 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetListTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetListTests.java @@ -26,8 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -39,7 +37,9 @@ import it.infn.mw.iam.api.common.OffsetPageable; import it.infn.mw.iam.api.scim.converter.ScimResourceLocationProvider; import it.infn.mw.iam.api.tokens.model.RefreshToken; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; import it.infn.mw.iam.test.util.WithMockOAuthUser; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetRevokeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetRevokeTests.java index 176816c061..7ef30d9b10 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetRevokeTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/RefreshTokenGetRevokeTests.java @@ -26,8 +26,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrint; @@ -41,7 +39,9 @@ import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.api.scim.converter.ScimResourceLocationProvider; import it.infn.mw.iam.api.tokens.model.RefreshToken; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.test.core.CoreControllerTestSupport; import it.infn.mw.iam.test.scim.ScimRestUtilsMvc; import it.infn.mw.iam.test.util.WithMockOAuthUser; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/TestTokensUtils.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/TestTokensUtils.java index cd6da697b3..ac7a459fab 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/TestTokensUtils.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/tokens/TestTokensUtils.java @@ -28,13 +28,6 @@ import java.util.Optional; import java.util.Set; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -60,13 +53,20 @@ import it.infn.mw.iam.api.tokens.Constants; import it.infn.mw.iam.api.tokens.model.AccessToken; import it.infn.mw.iam.api.tokens.model.RefreshToken; -import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.core.oauth.profile.JWTProfile; import it.infn.mw.iam.core.oauth.profile.JWTProfileResolver; import it.infn.mw.iam.core.user.IamAccountService; import it.infn.mw.iam.core.user.exception.IamAccountException; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.persistence.repository.IamAuthenticationHolderRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; @@ -85,7 +85,7 @@ public class TestTokensUtils extends EndpointsTestUtils { protected IamOAuthRefreshTokenRepository refreshTokenRepository; @Autowired - private ClientDetailsEntityService clientDetailsService; + private IamClientDetailsService clientDetailsService; @Autowired protected IamAccountRepository accountRepository; @@ -106,13 +106,13 @@ public class TestTokensUtils extends EndpointsTestUtils { protected PasswordEncoder encoder; @Autowired - protected AuthenticationHolderRepository authenticationHolderRepository; + protected IamAuthenticationHolderRepository authenticationHolderRepository; @Autowired protected JWTProfileResolver profileResolver; @Autowired - protected JWTSigningAndValidationService jwtSigningService; + protected JwtSigningAndValidationService jwtSigningService; protected Date yesterday() { return Date.from(LocalDate.now().minusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()); @@ -148,6 +148,10 @@ protected OAuth2AccessTokenEntity buildAccessToken(ClientDetailsEntity client, S return tokenService.createAccessToken(oauth2Authentication(client, username, scopes)); } + protected OAuth2AccessTokenEntity buildRegistrationAccessToken(ClientDetailsEntity client) { + return tokenService.createRegistrationAccessToken(client); + } + protected OAuth2AccessTokenEntity buildExpiredAccessToken(ClientDetailsEntity client, String username, String[] scopes) { @@ -248,8 +252,7 @@ protected OAuth2AccessTokenEntity buildExpiredAccessToken(ClientDetailsEntity cl token.setExpiration(yesterday()); OAuth2Authentication authn = oauth2AuthenticationClient(client, authorities, scopes); - AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); - authHolder.setAuthentication(authn); + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(client, authn); authHolder = authenticationHolderRepository.save(authHolder); token.setAuthenticationHolder(authHolder); @@ -259,8 +262,7 @@ protected OAuth2AccessTokenEntity buildExpiredAccessToken(ClientDetailsEntity cl JWTClaimsSet atClaims = profile.getAccessTokenBuilder() .buildAccessToken(token, authn, Optional.empty(), yesterday().toInstant()); - token.setJwt(signClaims(atClaims)); - token.hashMe(); + token.setTokenJwtValue(signClaims(atClaims)); accessTokenRepository.save(token); return token; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/client/last_used/ClientLastUsedTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/client/last_used/ClientLastUsedTests.java index 432a7cec27..39d9dec77a 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/client/last_used/ClientLastUsedTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/client/last_used/ClientLastUsedTests.java @@ -27,14 +27,16 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.test.context.junit.jupiter.SpringExtension; +import com.nimbusds.oauth2.sdk.GrantType; + import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.test.api.tokens.TestTokensUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -138,7 +140,8 @@ void testClientLastUsedUpdateOnTokenRefresh() { // After refreshing the access token, the last used date is updated iamProperties.getClient().setTrackLastUsed(true); OAuth2RefreshTokenEntity refreshToken = accessToken.getRefreshToken(); - TokenRequest tokenRequest = new TokenRequest(emptyMap(), POST_CLIENT, Collections.emptySet(), ""); + TokenRequest tokenRequest = new TokenRequest(emptyMap(), POST_CLIENT, + Collections.emptySet(), GrantType.REFRESH_TOKEN.getValue()); tokenService.refreshAccessToken(refreshToken.getValue(), tokenRequest); assertNotNull(client.getClientLastUsed()); lastUsed = client.getClientLastUsed().getLastUsed(); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/client/management/ClientManagementAPIControllerTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/client/management/ClientManagementAPIControllerTests.java index b1cd68f29f..751b2b4d4e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/client/management/ClientManagementAPIControllerTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/client/management/ClientManagementAPIControllerTests.java @@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.NONE; + import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -40,7 +40,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; @@ -51,7 +50,9 @@ import it.infn.mw.iam.api.common.client.AuthorizationGrantType; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.util.WithAnonymousUser; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; import it.infn.mw.iam.test.util.oauth.MockOAuth2Filter; @@ -170,7 +171,7 @@ void updateAuthMethodToNone() throws Exception { ClientDetailsEntity clientEntity = clientRepository.findByClientId("test-client-creation").get(); - assertEquals(NONE, clientEntity.getTokenEndpointAuthMethod()); + assertEquals(AuthMethod.NONE, clientEntity.getTokenEndpointAuthMethod()); } @Test diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/client/registration/ClientRegistrationAPIControllerTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/client/registration/ClientRegistrationAPIControllerTests.java index c14c302365..0819e1311c 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/client/registration/ClientRegistrationAPIControllerTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/client/registration/ClientRegistrationAPIControllerTests.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; @@ -44,7 +43,8 @@ import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.util.WithAnonymousUser; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/EntityConfigurationEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/EntityConfigurationEndpointTests.java index 40de115820..2621e47b14 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/EntityConfigurationEndpointTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/EntityConfigurationEndpointTests.java @@ -39,7 +39,7 @@ @IamMockMvcIntegrationTest @SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) -@ActiveProfiles({"h2-test", "dev", "openid-federation"}) +@ActiveProfiles({"h2-test", "openid-federation"}) class EntityConfigurationEndpointTests { private String endpoint = "/.well-known/openid-federation"; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java index d1584fa844..23504879d4 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java @@ -22,8 +22,6 @@ import java.util.Optional; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -38,13 +36,15 @@ import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.requests.GroupRequestUtils; import it.infn.mw.iam.api.requests.model.GroupRequestDto; +import it.infn.mw.iam.core.client.IamClientDetailsService; import it.infn.mw.iam.core.expression.IamSecurityExpressionMethods; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.IamGroupRequestRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.api.requests.GroupRequestsTestUtils; @SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) @@ -72,7 +72,7 @@ class IamSecurityExpressionMethodsTests extends GroupRequestsTestUtils { ClientService clientService; @Autowired - ClientDetailsEntityService clientDetailsService; + IamClientDetailsService clientDetailsService; @Autowired AccountUtils accountUtils; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamTokenServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamTokenServiceTests.java index 574489c63a..af05b1ff74 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamTokenServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamTokenServiceTests.java @@ -19,14 +19,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.SavedUserAuthentication; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -35,6 +32,8 @@ import it.infn.mw.iam.authn.util.Authorities; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; import it.infn.mw.iam.test.api.tokens.TestTokensUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -54,7 +53,7 @@ void testPreAuthenticatedUserCannotGetToken() { SavedUserAuthentication savedAuth = new SavedUserAuthentication(); savedAuth.setName(TESTUSER_USERNAME); savedAuth.setAuthenticated(true); - savedAuth.setAuthorities(List.of(Authorities.ROLE_PRE_AUTHENTICATED)); + savedAuth.setAuthorities(Set.of(Authorities.ROLE_PRE_AUTHENTICATED)); ClientDetailsEntity client = loadTestClient(TEST_CLIENT_ID); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/OpenidFederationPropertiesTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/OpenidFederationPropertiesTests.java index 20ecbfd908..19c5629310 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/OpenidFederationPropertiesTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/OpenidFederationPropertiesTests.java @@ -39,7 +39,7 @@ @IamMockMvcIntegrationTest @SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) -@ActiveProfiles({"h2-test", "dev", "openid-federation"}) +@ActiveProfiles({"h2-test", "openid-federation"}) @TestPropertySource(properties = { "openid-federation.entity-configuration.federation-entity.logo-uri=https://logo-example.com", "openid-federation.entity-configuration.federation-entity.organization-name=INDIGO IAM", diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorIntegrationTests.java index 97372b01aa..a14134dbdf 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorIntegrationTests.java @@ -23,16 +23,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.exception.DeviceCodeCreationException; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.AuthorizationCodeEntity; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.service.AuthenticationHolderEntityService; -import org.mitre.oauth2.service.DeviceCodeService; -import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.openid.connect.service.ApprovedSiteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; @@ -41,10 +31,24 @@ import org.springframework.transaction.annotation.Transactional; import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.core.AuthenticationHolderService; +import it.infn.mw.iam.core.OAuth2TokenEntityService; import it.infn.mw.iam.core.gc.GarbageCollector; +import it.infn.mw.iam.core.oauth.approvedsite.ApprovedSiteService; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeCreationException; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.AuthorizationCodeEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.DeviceCode; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamApprovedSiteRepository; import it.infn.mw.iam.persistence.repository.IamAuthenticationHolderRepository; import it.infn.mw.iam.persistence.repository.IamAuthorizationCodeRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.IamDeviceCodeRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; @@ -70,7 +74,7 @@ class GarbageCollectorIntegrationTests extends TestTokensUtils { private IamAuthorizationCodeRepository codeRepository; @Autowired - private AuthenticationHolderEntityService authenticationHolderService; + private AuthenticationHolderService authenticationHolderService; @Autowired private IamAuthenticationHolderRepository authenticationHolderRepository; @@ -90,10 +94,18 @@ class GarbageCollectorIntegrationTests extends TestTokensUtils { @Autowired private IamDeviceCodeRepository deviceCodeRepository; + @Autowired + private IamClientRepository clientRepository; + + @Autowired + private IamAccountRepository accountRepository; + private AuthorizationCodeEntity createAuthorizationCode() { + + ClientDetailsEntity client = clientRepository.findByClientId(PASSWORD_CLIENT_ID).orElseThrow(); OAuth2Authentication auth = getOAuth2Authentication(); RandomValueStringGenerator generator = new RandomValueStringGenerator(22); - AuthenticationHolderEntity authHolder = authenticationHolderService.create(auth); + AuthenticationHolderEntity authHolder = authenticationHolderService.create(client, auth); return new AuthorizationCodeEntity(generator.generate(), authHolder, yesterday()); } @@ -120,9 +132,10 @@ void cleanAll() { @Transactional void clearExpiredApprovedSites() { + ClientDetailsEntity client = clientRepository.findByClientId(PASSWORD_CLIENT_ID).orElseThrow(); + IamAccount account = accountRepository.findByUsername(TEST_USERNAME).orElseThrow(); assertThat(siteRepository.count(), equalTo(0L)); - approvedSiteService.createApprovedSite(PASSWORD_CLIENT_ID, TEST_USERNAME, yesterday(), - Set.of("openid")); + approvedSiteService.createApprovedSite(client, account, yesterday(), Set.of("openid")); assertThat(siteRepository.count(), equalTo(1L)); gc.clearExpiredApprovedSites(1); assertThat(siteRepository.count(), equalTo(0L)); @@ -172,8 +185,8 @@ void clearExpiredTokensAndOrphanedAuthenticationHolder() { void clearExpiredDeviceCodes() throws DeviceCodeCreationException { assertThat(deviceCodeRepository.count(), equalTo(0L)); - DeviceCode dc = codeService.createNewDeviceCode(Set.of("openid"), - loadTestClient(DEVICE_CODE_CLIENT_ID), Map.of()); + DeviceCode dc = + codeService.createNew(Set.of("openid"), loadTestClient(DEVICE_CODE_CLIENT_ID), Map.of()); dc.setExpiration(yesterday()); deviceCodeRepository.save(dc); assertThat(deviceCodeRepository.count(), equalTo(1L)); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorTests.java index 1f160bdaf8..bbfc6da31e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/gc/GarbageCollectorTests.java @@ -24,15 +24,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.AuthorizationCodeEntity; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.repository.AuthorizationCodeRepository; -import org.mitre.oauth2.repository.impl.DeviceCodeRepository; -import org.mitre.openid.connect.service.ApprovedSiteService; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -40,8 +31,16 @@ import org.springframework.data.domain.PageImpl; import it.infn.mw.iam.api.common.OffsetPageable; +import it.infn.mw.iam.core.AuthenticationHolderService; import it.infn.mw.iam.core.gc.DefaultGarbageCollector; +import it.infn.mw.iam.core.oauth.approvedsite.ApprovedSiteService; +import it.infn.mw.iam.core.oauth.devicecode.DeviceCodeService; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.AuthorizationCodeEntity; import it.infn.mw.iam.persistence.model.IamRevokedAccessToken; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.repository.IamAuthorizationCodeRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; import it.infn.mw.iam.persistence.repository.IamRevokedAccessTokenRepository; @@ -55,13 +54,13 @@ class GarbageCollectorTests { @Mock private IamOAuthRefreshTokenRepository refreshTokenRepo; @Mock - private DeviceCodeRepository deviceCodeRepo; + private DeviceCodeService deviceCodeService; @Mock - private AuthenticationHolderRepository authenticationHolderRepository; + private AuthenticationHolderService authHolderService; @Mock private IamRevokedAccessTokenRepository revokedAccessTokenRepo; @Mock - private AuthorizationCodeRepository authzCodeRepo; + private IamAuthorizationCodeRepository authzCodeRepo; private DefaultGarbageCollector gc; @@ -70,7 +69,7 @@ void setup() { MockitoAnnotations.openMocks(this); gc = new DefaultGarbageCollector(approvedSiteService, accessTokenRepo, refreshTokenRepo, - deviceCodeRepo, authenticationHolderRepository, revokedAccessTokenRepo, authzCodeRepo); + deviceCodeService, authHolderService, revokedAccessTokenRepo, authzCodeRepo); } @Test @@ -83,21 +82,19 @@ void testClearExpiredApprovedSites() { @Test void testClearExpiredAuthorizationCodes() { AuthorizationCodeEntity code = mock(AuthorizationCodeEntity.class); - when(authzCodeRepo.getExpiredCodes()).thenReturn(Collections.singletonList(code)); + when(authzCodeRepo.findExpired()).thenReturn(Collections.singletonList(code)); gc.clearExpiredAuthorizationCodes(10); - verify(authzCodeRepo).remove(code); + verify(authzCodeRepo).delete(code); } @Test void testClearExpiredDeviceCodes() { - DeviceCode dc = mock(DeviceCode.class); - when(deviceCodeRepo.getExpiredCodes()).thenReturn(Collections.singletonList(dc)); + when(deviceCodeService.clearExpired()).thenReturn(1); gc.clearExpiredDeviceCodes(10); - - verify(deviceCodeRepo).remove(dc); + verify(deviceCodeService).clearExpired(); } @Test @@ -139,11 +136,11 @@ void testClearExpiredRefreshTokens() { @Test void testClearOrphanedAuthenticationHolder() { AuthenticationHolderEntity holder = mock(AuthenticationHolderEntity.class); - when(authenticationHolderRepository.getOrphanedAuthenticationHolders(ArgumentMatchers.any())) - .thenReturn(Collections.singletonList(holder)); + when(authHolderService.getOrphanedAuthenticationHolders(ArgumentMatchers.any())) + .thenReturn(new PageImpl<>(Collections.singletonList(holder))); gc.clearOrphanedAuthenticationHolder(10); - verify(authenticationHolderRepository).remove(holder); + verify(authHolderService).remove(holder); } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationInfoProcessorTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationInfoProcessorTests.java index 01fa6a0810..a27774b27e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationInfoProcessorTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationInfoProcessorTests.java @@ -24,12 +24,12 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.SavedUserAuthentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import com.google.common.collect.Maps; import it.infn.mw.iam.authn.DefaultExternalAuthenticationInfoProcessor; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; @SuppressWarnings("deprecation") class ExternalAuthenticationInfoProcessorTests { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationRegistrationInfoTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationRegistrationInfoTests.java index 9cc30c456a..fbc42cd00f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationRegistrationInfoTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/ExternalAuthenticationRegistrationInfoTests.java @@ -28,8 +28,6 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; -import org.mitre.openid.connect.model.UserInfo; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.AttributeStatement; @@ -47,9 +45,11 @@ import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo; import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo.ExternalAuthenticationType; import it.infn.mw.iam.authn.oidc.OidcExternalAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; import it.infn.mw.iam.authn.saml.SamlExternalAuthenticationToken; import it.infn.mw.iam.authn.saml.util.Saml2Attribute; import it.infn.mw.iam.persistence.model.IamSamlId; +import it.infn.mw.iam.persistence.model.IamUserInfo; import it.infn.mw.iam.test.ext_authn.saml.SamlAuthenticationTestSupport; import it.infn.mw.iam.test.ext_authn.saml.SamlTestConfig; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -90,7 +90,7 @@ void testOidcEmailAndNameReturnedIfPresent() { OIDCAuthenticationToken token = mock(OIDCAuthenticationToken.class); - UserInfo userinfo = mock(UserInfo.class); + IamUserInfo userinfo = mock(IamUserInfo.class); when(userinfo.getEmail()).thenReturn("test@test.org"); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingMultiProviderTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingMultiProviderTests.java index 172cc3e03b..cea53fa08d 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingMultiProviderTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingMultiProviderTests.java @@ -46,6 +46,7 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -54,12 +55,14 @@ import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.test.ext_authn.oidc.FullyMockedOidcClientConfiguration; import it.infn.mw.iam.test.ext_authn.oidc.OidcMultiProviderTestConfig; +import it.infn.mw.iam.test.util.JvmProfilesSupport; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; import it.infn.mw.iam.test.util.oidc.MockOIDCProvider; @IamMockMvcIntegrationTest @SpringBootTest(classes = {IamLoginService.class, OidcMultiProviderTestConfig.class, FullyMockedOidcClientConfiguration.class}, webEnvironment = WebEnvironment.MOCK) +//@ContextConfiguration(initializers = JvmProfilesSupport.OidcProfileInitializer.class) class OidcAccountLinkingMultiProviderTests { @Autowired diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingTests.java index a8a9cdba4a..1e3eff2dee 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/account_linking/OidcAccountLinkingTests.java @@ -47,7 +47,7 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.transaction.annotation.Transactional; @@ -62,8 +62,8 @@ @SpringBootTest(classes = {IamLoginService.class, OidcTestConfig.class, FullyMockedOidcClientConfiguration.class}, webEnvironment = WebEnvironment.MOCK) @AutoConfigureMockMvc(printOnlyOnFailure = true, print = MockMvcPrint.LOG_DEBUG) -@TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=true",}) @Transactional +@ActiveProfiles({"h2-test", "oidc"}) class OidcAccountLinkingTests { static final String TEST_100_USER = "test_100"; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/FullyMockedOidcClientConfiguration.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/FullyMockedOidcClientConfiguration.java index 30d68e1522..3e08455d00 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/FullyMockedOidcClientConfiguration.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/FullyMockedOidcClientConfiguration.java @@ -27,7 +27,7 @@ public class FullyMockedOidcClientConfiguration { @Bean @Primary - public OidcTokenRequestor tokenRequestor(MockOIDCProvider mockOidcProvider) { + OidcTokenRequestor tokenRequestor(MockOIDCProvider mockOidcProvider) { return mockOidcProvider; } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTests.java index 5802d90e57..6667a53d73 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTests.java @@ -35,6 +35,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; @@ -46,6 +47,7 @@ import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo; import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo.ExternalAuthenticationType; +import it.infn.mw.iam.test.util.JvmProfilesSupport; import it.infn.mw.iam.test.util.annotation.IamRandomPortIntegrationTest; import it.infn.mw.iam.test.util.oidc.CodeRequestHolder; import it.infn.mw.iam.test.util.oidc.MockRestTemplateFactory; @@ -53,6 +55,7 @@ @IamRandomPortIntegrationTest @SpringBootTest(classes = {IamLoginService.class, OidcTestConfig.class}, webEnvironment = WebEnvironment.RANDOM_PORT) +//@ContextConfiguration(initializers = JvmProfilesSupport.OidcProfileInitializer.class) class OidcExternalAuthenticationTests extends OidcExternalAuthenticationTestsSupport { @BeforeEach diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTestsSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTestsSupport.java index 1cac46341e..26d6c3c979 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTestsSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationTestsSupport.java @@ -28,7 +28,6 @@ import java.util.Map; -import org.mitre.openid.connect.client.UserInfoFetcher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; @@ -47,6 +46,7 @@ import org.springframework.web.util.UriComponentsBuilder; import it.infn.mw.iam.authn.oidc.RestTemplateFactory; +import it.infn.mw.iam.authn.oidc.userinfo.UserInfoFetcher; import it.infn.mw.iam.test.rcauth.RCAuthTestSupport; import it.infn.mw.iam.test.util.oidc.CodeRequestHolder; import it.infn.mw.iam.test.util.oidc.MockOIDCProvider; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationWithMfaProfileTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationWithMfaProfileTests.java index 1c85d6ca01..ccf3e9dffa 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationWithMfaProfileTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcExternalAuthenticationWithMfaProfileTests.java @@ -26,25 +26,26 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.test.util.JvmProfilesSupport; import it.infn.mw.iam.test.util.annotation.IamRandomPortIntegrationTest; @IamRandomPortIntegrationTest @SpringBootTest(classes = {IamLoginService.class, OidcTestConfig.class}, - webEnvironment = WebEnvironment.RANDOM_PORT) -@ActiveProfiles({"h2", "mfa"}) -class OidcExternalAuthenticationWithMfaProfileTests - extends OidcExternalAuthenticationTestsSupport { + webEnvironment = WebEnvironment.RANDOM_PORT) +//@ContextConfiguration(initializers = {JvmProfilesSupport.OidcProfileInitializer.class, +// JvmProfilesSupport.MfaProfileInitializer.class}) +class OidcExternalAuthenticationWithMfaProfileTests extends OidcExternalAuthenticationTestsSupport { @Test void testAcrValuesClaimIsAddedWhenMfaProfileIsActive() - throws RestClientException, UnsupportedEncodingException { + throws RestClientException, UnsupportedEncodingException { RestTemplate rt = noRedirectRestTemplate(); ResponseEntity response = rt.getForEntity(openidConnectLoginURL(), String.class); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcJitAccountProvisioningTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcJitAccountProvisioningTests.java index eec066204d..7ddd32d312 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcJitAccountProvisioningTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcJitAccountProvisioningTests.java @@ -30,17 +30,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; -import org.mitre.openid.connect.model.UserInfo; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.authentication.InternalAuthenticationServiceException; -import it.infn.mw.iam.authn.oidc.service.OidcAccountProvisioningService; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; +import it.infn.mw.iam.authn.oidc.provisioning.OidcAccountProvisioningService; import it.infn.mw.iam.core.user.IamAccountService; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.IamUserInfo; import it.infn.mw.iam.persistence.repository.IamAccountRepository; @ExtendWith(MockitoExtension.class) @@ -67,7 +67,7 @@ void setup() { @Test void provisionAccountWithValidTokenAndAvailableUsernameCreatesNewAccount() { OIDCAuthenticationToken token = mock(OIDCAuthenticationToken.class); - UserInfo userInfo = mock(UserInfo.class); + IamUserInfo userInfo = mock(IamUserInfo.class); when(token.getIssuer()).thenReturn("https://trusted-idp.com"); when(token.getSub()).thenReturn("sub123"); @@ -95,7 +95,7 @@ void provisionAccountWhenAccountNotFoundPerformsJustInTimeProvisioning() { when(token.getIssuer()).thenReturn("https://trusted-idp.com"); when(token.getSub()).thenReturn("sub123"); - UserInfo userInfo = mock(UserInfo.class); + IamUserInfo userInfo = mock(IamUserInfo.class); when(token.getUserInfo()).thenReturn(userInfo); when(userInfo.getGivenName()).thenReturn("John"); @@ -120,7 +120,7 @@ void provisionAccountUsesPreferredUsernameWhenAvailable() { when(token.getIssuer()).thenReturn("https://trusted-idp.com"); when(token.getSub()).thenReturn("sub123"); - UserInfo userInfo = mock(UserInfo.class); + IamUserInfo userInfo = mock(IamUserInfo.class); when(token.getUserInfo()).thenReturn(userInfo); when(userInfo.getGivenName()).thenReturn("John"); when(userInfo.getFamilyName()).thenReturn("Doe"); @@ -148,7 +148,7 @@ void provisionAccountUsesRandomUUIDWhenPreferredUsernameUnavailable() { when(token.getIssuer()).thenReturn("https://trusted-idp.com"); when(token.getSub()).thenReturn("sub123"); - UserInfo userInfo = mock(UserInfo.class); + IamUserInfo userInfo = mock(IamUserInfo.class); when(token.getUserInfo()).thenReturn(userInfo); when(userInfo.getGivenName()).thenReturn("John"); when(userInfo.getFamilyName()).thenReturn("Doe"); @@ -183,7 +183,7 @@ void provisionAccountThrowsExceptionWhenEmailIsAlreadyBound() { OIDCAuthenticationToken token = mock(OIDCAuthenticationToken.class); when(token.getIssuer()).thenReturn("https://trusted-idp.com"); - UserInfo userInfo = mock(UserInfo.class); + IamUserInfo userInfo = mock(IamUserInfo.class); when(userInfo.getGivenName()).thenReturn("Already"); when(userInfo.getFamilyName()).thenReturn("Bound"); when(userInfo.getEmail()).thenReturn("test@iam.test"); @@ -199,9 +199,9 @@ void provisionAccountThrowsExceptionWhenRequiredClaimsAreMissing() { OIDCAuthenticationToken token = mock(OIDCAuthenticationToken.class); when(token.getIssuer()).thenReturn("https://trusted-idp.com"); - when(token.getUserInfo()).thenReturn(mock(UserInfo.class)); + when(token.getUserInfo()).thenReturn(mock(IamUserInfo.class)); - UserInfo userInfo = token.getUserInfo(); + IamUserInfo userInfo = token.getUserInfo(); when(userInfo.getGivenName()).thenReturn(null); assertThrows(InternalAuthenticationServiceException.class, () -> service.provisionAccount(token)); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcMultiProviderTestConfig.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcMultiProviderTestConfig.java index 370cb7ef6c..9763e6ac9c 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcMultiProviderTestConfig.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcMultiProviderTestConfig.java @@ -19,19 +19,8 @@ import java.security.spec.InvalidKeySpecException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; -import org.mitre.oauth2.model.RegisteredClient; -import org.mitre.openid.connect.client.UserInfoFetcher; -import org.mitre.openid.connect.client.service.ClientConfigurationService; -import org.mitre.openid.connect.client.service.IssuerService; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService; -import org.mitre.openid.connect.client.service.impl.StaticServerConfigurationService; -import org.mitre.openid.connect.config.ServerConfiguration; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -39,11 +28,21 @@ import org.springframework.core.io.ClassPathResource; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; +import it.infn.mw.iam.authn.oidc.RegisteredClient; import it.infn.mw.iam.authn.oidc.RestTemplateFactory; -import it.infn.mw.iam.core.IamThirdPartyIssuerService; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.authn.oidc.configuration.ClientConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.StaticClientConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; +import it.infn.mw.iam.authn.oidc.userinfo.UserInfoFetcher; +import it.infn.mw.iam.core.jwt.IamJwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.core.oidc.service.IamThirdPartyIssuerService; +import it.infn.mw.iam.core.oidc.service.IssuerService; +import it.infn.mw.iam.persistence.model.AuthMethod; import it.infn.mw.iam.test.util.oidc.MockOIDCProvider; import it.infn.mw.iam.test.util.oidc.MockRestTemplateFactory; @@ -64,27 +63,27 @@ public class OidcMultiProviderTestConfig { @Bean @Primary - public UserInfoFetcher userInfoFetcher() { + UserInfoFetcher userInfoFetcher() { UserInfoFetcher fetcher = Mockito.mock(UserInfoFetcher.class); return fetcher; } @Bean @Primary - public RestTemplateFactory restTemplateFactory() { + RestTemplateFactory restTemplateFactory() { return new MockRestTemplateFactory(); } @Bean @Primary - public IssuerService oidcIssuerService() { + IssuerService oidcIssuerService() { return new IamThirdPartyIssuerService(); } @Bean @Primary - public ServerConfigurationService mockServerConfigurationService() { + ServerConfigurationService mockServerConfigurationService() { ServerConfiguration sc01 = new ServerConfiguration(); sc01.setIssuer(TEST_OIDC_01_ISSUER); @@ -102,40 +101,34 @@ public ServerConfigurationService mockServerConfigurationService() { servers.put(TEST_OIDC_01_ISSUER, sc01); servers.put(TEST_OIDC_02_ISSUER, sc02); - StaticServerConfigurationService service = new StaticServerConfigurationService(); - service.setServers(servers); - - return service; + return new StaticServerConfigurationService(servers); } @Bean @Primary - public ClientConfigurationService staticClientConfiguration() { + ClientConfigurationService staticClientConfiguration() { RegisteredClient rc = new RegisteredClient(); rc.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC); - rc.setScope(Sets.newHashSet("openid profile email")); + rc.setScope(Set.of("openid profile email")); rc.setClientId(TEST_OIDC_CLIENT_ID); Map clients = new LinkedHashMap(); clients.put(TEST_OIDC_01_ISSUER, rc); clients.put(TEST_OIDC_02_ISSUER, rc); - StaticClientConfigurationService config = new StaticClientConfigurationService(); - config.setClients(clients); - - return config; + return new StaticClientConfigurationService(clients); } @Bean @Primary - public JWKSetCacheService mockjwkSetCacheService() + JwkSetCacheService mockjwkSetCacheService() throws NoSuchAlgorithmException, InvalidKeySpecException { - JWTSigningAndValidationService signatureValidator = - new IamJWTSigningService(mockOidcProviderKeyStore()); + JwtSigningAndValidationService signatureValidator = + new IamJwtSigningAndValidationService(mockOidcProviderKeyStore()); - JWKSetCacheService mockCacheService = Mockito.mock(JWKSetCacheService.class); + JwkSetCacheService mockCacheService = Mockito.mock(JwkSetCacheService.class); Mockito.when(mockCacheService.getValidator(TEST_OIDC_01_JWKS_URI)) .thenReturn(signatureValidator); Mockito.when(mockCacheService.getValidator(TEST_OIDC_02_JWKS_URI)) @@ -146,14 +139,12 @@ public JWKSetCacheService mockjwkSetCacheService() @Bean @Primary - public JWKSetKeyStore mockOidcProviderKeyStore() { - JWKSetKeyStore ks = new JWKSetKeyStore(); - ks.setLocation(new ClassPathResource("/oidc/mock_op_keys.jks")); - return ks; + JwkSetKeyStore mockOidcProviderKeyStore() { + return new JwkSetKeyStore(new ClassPathResource("/oidc/mock_op_keys.jks")); } @Bean - public MockOIDCProvider mockOidcProvider(ObjectMapper mapper) { + MockOIDCProvider mockOidcProvider(ObjectMapper mapper) { return new MockOIDCProvider(mapper, mockOidcProviderKeyStore()); } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcTestConfig.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcTestConfig.java index 5c0cf15abf..b1e970dbd8 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcTestConfig.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/OidcTestConfig.java @@ -20,18 +20,6 @@ import java.util.LinkedHashMap; import java.util.Map; -import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; -import org.mitre.oauth2.model.RegisteredClient; -import org.mitre.openid.connect.client.UserInfoFetcher; -import org.mitre.openid.connect.client.service.ClientConfigurationService; -import org.mitre.openid.connect.client.service.IssuerService; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.client.service.impl.StaticClientConfigurationService; -import org.mitre.openid.connect.client.service.impl.StaticSingleIssuerService; -import org.mitre.openid.connect.config.ServerConfiguration; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -41,8 +29,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Sets; +import it.infn.mw.iam.authn.oidc.RegisteredClient; import it.infn.mw.iam.authn.oidc.RestTemplateFactory; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.authn.oidc.configuration.ClientConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.configuration.StaticClientConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; +import it.infn.mw.iam.authn.oidc.userinfo.UserInfoFetcher; +import it.infn.mw.iam.core.jwt.IamJwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; +import it.infn.mw.iam.core.oidc.service.IssuerService; +import it.infn.mw.iam.persistence.model.AuthMethod; import it.infn.mw.iam.test.util.oidc.MockOIDCProvider; import it.infn.mw.iam.test.util.oidc.MockRestTemplateFactory; @@ -57,7 +56,7 @@ public class OidcTestConfig { @Bean @Primary - public UserInfoFetcher userInfoFetcher() { + UserInfoFetcher userInfoFetcher() { UserInfoFetcher fetcher = Mockito.mock(UserInfoFetcher.class); return fetcher; @@ -65,13 +64,13 @@ public UserInfoFetcher userInfoFetcher() { @Bean @Primary - public RestTemplateFactory restTemplateFactory() { + RestTemplateFactory restTemplateFactory() { return new MockRestTemplateFactory(); } @Bean @Primary - public IssuerService oidcIssuerService() { + IssuerService oidcIssuerService() { StaticSingleIssuerService issuerService = new StaticSingleIssuerService(); issuerService.setIssuer(TEST_OIDC_ISSUER); @@ -82,7 +81,7 @@ public IssuerService oidcIssuerService() { @Bean @Primary - public ServerConfigurationService mockServerConfigurationService() { + ServerConfigurationService mockServerConfigurationService() { ServerConfiguration sc = new ServerConfiguration(); sc.setIssuer(TEST_OIDC_ISSUER); @@ -100,7 +99,7 @@ public ServerConfigurationService mockServerConfigurationService() { @Bean @Primary - public ClientConfigurationService staticClientConfiguration() { + ClientConfigurationService staticClientConfiguration() { RegisteredClient rc = new RegisteredClient(); rc.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC); @@ -111,21 +110,18 @@ public ClientConfigurationService staticClientConfiguration() { clients.put(TEST_OIDC_ISSUER, rc); - StaticClientConfigurationService config = new StaticClientConfigurationService(); - config.setClients(clients); - - return config; + return new StaticClientConfigurationService(clients); } @Bean @Primary - public JWKSetCacheService mockjwkSetCacheService() + JwkSetCacheService mockjwkSetCacheService() throws NoSuchAlgorithmException, InvalidKeySpecException { - JWTSigningAndValidationService signatureValidator = - new IamJWTSigningService(mockOidcProviderKeyStore()); + JwtSigningAndValidationService signatureValidator = + new IamJwtSigningAndValidationService(mockOidcProviderKeyStore()); - JWKSetCacheService mockCacheService = Mockito.mock(JWKSetCacheService.class); + JwkSetCacheService mockCacheService = Mockito.mock(JwkSetCacheService.class); Mockito.when(mockCacheService.getValidator(TEST_OIDC_JWKS_URI)).thenReturn(signatureValidator); return mockCacheService; @@ -134,14 +130,12 @@ public JWKSetCacheService mockjwkSetCacheService() @Bean @Primary - public JWKSetKeyStore mockOidcProviderKeyStore() { - JWKSetKeyStore ks = new JWKSetKeyStore(); - ks.setLocation(new ClassPathResource("/oidc/mock_op_keys.jks")); - return ks; + JwkSetKeyStore mockOidcProviderKeyStore() { + return new JwkSetKeyStore(new ClassPathResource("/oidc/mock_op_keys.jks")); } @Bean - public MockOIDCProvider mockOidcProvider(ObjectMapper mapper) { + MockOIDCProvider mockOidcProvider(ObjectMapper mapper) { return new MockOIDCProvider(mapper, mockOidcProviderKeyStore()); } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/StaticServerConfigurationService.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/StaticServerConfigurationService.java new file mode 100644 index 0000000000..0e0408cff7 --- /dev/null +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/StaticServerConfigurationService.java @@ -0,0 +1,50 @@ +/** + * 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.test.ext_authn.oidc; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; + +public class StaticServerConfigurationService implements ServerConfigurationService { + + private final Map serverConfigPerIssuer; + + public StaticServerConfigurationService(Map serverConfigPerIssuer) { + this.serverConfigPerIssuer = new HashMap<>(); + this.serverConfigPerIssuer.putAll(serverConfigPerIssuer); + } + + public Map getServers() { + return serverConfigPerIssuer; + } + + @Override + public ServerConfiguration getServerConfiguration(String issuer) { + return serverConfigPerIssuer.get(issuer); + } + + @PostConstruct + public void afterPropertiesSet() { + if (serverConfigPerIssuer == null || serverConfigPerIssuer.isEmpty()) { + throw new IllegalArgumentException("Servers map cannot be null or empty."); + } + } +} diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/StaticSingleIssuerService.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/StaticSingleIssuerService.java new file mode 100644 index 0000000000..c86e7dff7b --- /dev/null +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/StaticSingleIssuerService.java @@ -0,0 +1,51 @@ +/** + * 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.test.ext_authn.oidc; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; + +import com.google.common.base.Strings; + +import it.infn.mw.iam.core.oidc.service.IssuerService; +import it.infn.mw.iam.core.oidc.service.IssuerServiceResponse; + +public class StaticSingleIssuerService implements IssuerService { + + private String issuer; + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + @Override + public IssuerServiceResponse getIssuer(HttpServletRequest request) { + return new IssuerServiceResponse(getIssuer(), null, null); + } + + @PostConstruct + public void afterPropertiesSet() { + + if (Strings.isNullOrEmpty(issuer)) { + throw new IllegalArgumentException("Issuer must not be null or empty."); + } + } + +} diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java index 4d0aa55aa1..4cffc47ec9 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java @@ -34,6 +34,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; @@ -48,6 +49,7 @@ import it.infn.mw.iam.authn.common.ValidatorResolver; import it.infn.mw.iam.test.ext_authn.oidc.OidcExternalAuthenticationTestsSupport; import it.infn.mw.iam.test.ext_authn.oidc.OidcTestConfig; +import it.infn.mw.iam.test.util.JvmProfilesSupport; import it.infn.mw.iam.test.util.annotation.IamRandomPortIntegrationTest; import it.infn.mw.iam.test.util.oidc.CodeRequestHolder; import it.infn.mw.iam.test.util.oidc.MockRestTemplateFactory; @@ -55,6 +57,7 @@ @IamRandomPortIntegrationTest @SpringBootTest(classes = {IamLoginService.class, OidcTestConfig.class, OidcValidatorIntegrationTests.Config.class}, webEnvironment = WebEnvironment.RANDOM_PORT) +//@ContextConfiguration(initializers = JvmProfilesSupport.OidcProfileInitializer.class) class OidcValidatorIntegrationTests extends OidcExternalAuthenticationTestsSupport { @Configuration diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MfaAcrClaimIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MfaAcrClaimIntegrationTests.java index e85140d6ed..b31d91aa18 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MfaAcrClaimIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MfaAcrClaimIntegrationTests.java @@ -23,16 +23,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.util.List; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.SavedUserAuthentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -43,6 +39,9 @@ import com.nimbusds.jwt.JWTParser; import it.infn.mw.iam.core.oauth.introspection.model.TokenTypeHint; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; import it.infn.mw.iam.test.api.tokens.TestTokensUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -67,7 +66,7 @@ void testAcrClaimInTokensAndIntrospectionWhenMfaEnabled() throws Exception { SavedUserAuthentication savedAuth = new SavedUserAuthentication(); savedAuth.setName(TESTUSER_USERNAME); savedAuth.setAuthenticated(true); - savedAuth.setAuthorities(List.of(new SimpleGrantedAuthority("ROLE_USER"))); + savedAuth.setAuthorities(Set.of(new SimpleGrantedAuthority("ROLE_USER"))); savedAuth.getAdditionalInfo().put("acr", "https://refeds.org/profile/mfa"); ClientDetailsEntity client = loadTestClient(TEST_CLIENT_ID); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorTestSupport.java index f973022537..e8c22180c0 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorTestSupport.java @@ -19,7 +19,8 @@ import it.infn.mw.iam.persistence.model.IamTotpMfa; import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionUtil; -public class MultiFactorTestSupport extends IamTotpMfaCommons{ +public class MultiFactorTestSupport extends IamTotpMfaCommons { + public static final String TEST_USERNAME = "test-user"; public static final String TEST_UUID = "a23deabf-88a7-47af-84b5-1d535a1b267c"; public static final String TEST_EMAIL = "test@example.org"; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorVerificationFilterTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorVerificationFilterTests.java index 8ecc7e71a7..959ffcc86e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorVerificationFilterTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/MultiFactorVerificationFilterTests.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -47,6 +46,7 @@ import it.infn.mw.iam.authn.multi_factor_authentication.MultiFactorVerificationFilter; import it.infn.mw.iam.authn.oidc.OidcExternalAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; import it.infn.mw.iam.authn.saml.SamlExternalAuthenticationToken; import it.infn.mw.iam.core.ExtendedAuthenticationToken; import it.infn.mw.iam.persistence.model.IamSamlId; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/OidcExternalAuthenticationTokenTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/OidcExternalAuthenticationTokenTests.java index 0be22cb09e..196264ee0e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/OidcExternalAuthenticationTokenTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/OidcExternalAuthenticationTokenTests.java @@ -18,10 +18,10 @@ import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; import org.mockito.Mockito; import it.infn.mw.iam.authn.oidc.OidcExternalAuthenticationToken; +import it.infn.mw.iam.authn.oidc.model.OIDCAuthenticationToken; class OidcExternalAuthenticationTokenTests { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java index 0fcc3459f3..67893a8bb0 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/multi_factor_authentication/authenticator_app/AuthenticatorAppSettingsControllerTests.java @@ -38,6 +38,7 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -57,66 +58,73 @@ import it.infn.mw.iam.test.util.notification.MockNotificationDelivery; @SpringBootTest(classes = {IamLoginService.class, CoreControllerTestSupport.class, - NotificationTestConfig.class}, webEnvironment = WebEnvironment.MOCK) + NotificationTestConfig.class}, webEnvironment = WebEnvironment.MOCK) @IamMockMvcIntegrationTest @TestPropertySource(properties = {"notification.disable=false"}) +@ActiveProfiles({"h2-test", "mfa"}) class AuthenticatorAppSettingsControllerTests extends MultiFactorTestSupport { - private MockMvc mvc; - @Autowired - private WebApplicationContext context; - @Autowired - private MockNotificationDelivery notificationDelivery; - @Autowired - private IamEmailNotificationRepository notificationRepo; - @MockBean - private IamAccountRepository accountRepository; - @MockBean - private IamTotpMfaRepository totpMfaRepository; + + private MockMvc mvc; + + @Autowired + private WebApplicationContext context; + + @Autowired + private MockNotificationDelivery notificationDelivery; + + @Autowired + private IamEmailNotificationRepository notificationRepo; + + @MockBean + private IamAccountRepository accountRepository; + + @MockBean + private IamTotpMfaRepository totpMfaRepository; @BeforeEach void setup() { - when(accountRepository.findByUuid(TOTP_UUID)).thenReturn(Optional.of(TOTP_MFA_ACCOUNT)); - when(totpMfaRepository.findByAccount(TOTP_MFA_ACCOUNT)).thenReturn(Optional.of(TOTP_MFA)); + when(accountRepository.findByUuid(TOTP_UUID)).thenReturn(Optional.of(TOTP_MFA_ACCOUNT)); + when(totpMfaRepository.findByAccount(TOTP_MFA_ACCOUNT)).thenReturn(Optional.of(TOTP_MFA)); - mvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).alwaysDo(log()).build(); - } + mvc = + MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).alwaysDo(log()).build(); + } @AfterEach void tearDown() { - notificationDelivery.clearDeliveredNotifications(); - } + notificationDelivery.clearDeliveredNotifications(); + } @Test @WithAnonymousUser void testDisableAuthenticatorAppNoAuthenticationFails() throws Exception { - mvc.perform(delete(DISABLE_URL_FOR_ACCOUNT_ID, TOTP_UUID)) - .andExpect(status().isUnauthorized()); - } + mvc.perform(delete(DISABLE_URL_FOR_ACCOUNT_ID, TOTP_UUID)).andExpect(status().isUnauthorized()); + } @Test @WithMockUser(username = "admin", roles = "ADMIN") void testDisableAuthenticatorAppWorksForAdmin() throws Exception { - mvc.perform(delete(DISABLE_URL_FOR_ACCOUNT_ID, TOTP_UUID)) - .andExpect(status().isOk()); - } + mvc.perform(delete(DISABLE_URL_FOR_ACCOUNT_ID, TOTP_UUID)).andExpect(status().isOk()); + } @Test @WithMockUser(username = "admin", roles = "ADMIN") void testConfirmationEmailSentOnMfaDisable() throws Exception { - mvc.perform(delete(DISABLE_URL_FOR_ACCOUNT_ID, TOTP_UUID)) - .andExpect(status().isOk()); + mvc.perform(delete(DISABLE_URL_FOR_ACCOUNT_ID, TOTP_UUID)).andExpect(status().isOk()); - List notifications = notificationRepo - .findByNotificationType(IamNotificationType.MFA_DISABLE); + List notifications = + notificationRepo.findByNotificationType(IamNotificationType.MFA_DISABLE); - assertEquals(1, notifications.size()); - assertEquals("[indigo-dc IAM] Multi-factor authentication (MFA) disabled", notifications.get(0).getSubject()); + assertEquals(1, notifications.size()); + assertEquals("[indigo-dc IAM] Multi-factor authentication (MFA) disabled", + notifications.get(0).getSubject()); - notificationDelivery.sendPendingNotifications(); + notificationDelivery.sendPendingNotifications(); - assertThat(notificationDelivery.getDeliveredNotifications(), hasSize(1)); - IamEmailNotification message = notificationDelivery.getDeliveredNotifications().get(0); - assertThat(message.getSubject(), equalTo("[indigo-dc IAM] Multi-factor authentication (MFA) disabled")); - } + assertThat(notificationDelivery.getDeliveredNotifications(), hasSize(1)); + IamEmailNotification message = notificationDelivery.getDeliveredNotifications().get(0); + assertThat(message.getSubject(), + equalTo("[indigo-dc IAM] Multi-factor authentication (MFA) disabled")); + } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ClientRegistrationAuthzTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ClientRegistrationAuthzTests.java index 9649be0d8a..f6018020cf 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ClientRegistrationAuthzTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ClientRegistrationAuthzTests.java @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; @@ -31,10 +30,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.client_registration.ClientRegistrationTestSupport; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/OAuth2AuthenticationScopeResolverTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/OAuth2AuthenticationScopeResolverTests.java index 7c3f1c618a..45de8d806f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/OAuth2AuthenticationScopeResolverTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/OAuth2AuthenticationScopeResolverTests.java @@ -15,19 +15,19 @@ */ package it.infn.mw.iam.test.oauth; +import static it.infn.mw.iam.core.IamTokenService.sha256; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.lenient; +import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.repository.OAuth2TokenRepository; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -39,6 +39,8 @@ import it.infn.mw.iam.api.scim.exception.IllegalArgumentException; import it.infn.mw.iam.core.userinfo.DefaultOAuth2AuthenticationScopeResolver; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.test.util.oauth.MockOAuth2Request; @SuppressWarnings("deprecation") @@ -49,9 +51,6 @@ public class OAuth2AuthenticationScopeResolverTests { OAuth2Request oauthRequest = new MockOAuth2Request("test", new String[] {"openid", "profile"}); - @Mock - OAuth2TokenRepository repo; - @Mock OAuth2Authentication auth; @@ -61,6 +60,9 @@ public class OAuth2AuthenticationScopeResolverTests { @Mock OAuth2AuthenticationDetails authDetails; + @Mock + IamOAuthAccessTokenRepository repo; + @InjectMocks DefaultOAuth2AuthenticationScopeResolver scopeResolver; @@ -97,7 +99,8 @@ void tokenNotFoundInRepoRaisesIllegalArgumentException() { void tokenFound() { lenient().when(auth.getDetails()).thenReturn(authDetails); lenient().when(authDetails.getTokenValue()).thenReturn(TOKEN_VALUE); - lenient().when(repo.getAccessTokenByValue(TOKEN_VALUE)).thenReturn(tokenEntity); + lenient().when(repo.findByTokenValue(sha256(TOKEN_VALUE))) + .thenReturn(Optional.of(tokenEntity)); lenient().when(tokenEntity.getScope()).thenReturn(Sets.newHashSet("openid")); Set scopes = scopeResolver.resolveScope(auth); assertThat(scopes, hasSize(1)); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java index 8c09f00e47..2104c95845 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java @@ -27,10 +27,6 @@ import java.util.Date; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -49,7 +45,11 @@ import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.client.util.ClientSuppliers; import it.infn.mw.iam.audit.events.tokens.AccessTokenIssuedEvent; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAup; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamAupRepository; import it.infn.mw.iam.test.api.tokens.TestTokensUtils; @@ -214,7 +214,7 @@ void testRefreshFlowNotAllowedIfClientIsSuspended() throws Exception { .param("refresh_token", refreshToken)) .andExpect(status().isUnauthorized()) .andExpect(jsonPath("$.error").value("invalid_client")) - .andExpect(jsonPath("$.error_description").value("Suspended client '" + clientId + "'")); + .andExpect(jsonPath("$.error_description").value("Client " + clientId + " is not active")); // @formatter:on client.setActive(true); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java index a6967b5122..dedea7336b 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java @@ -28,7 +28,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -41,6 +40,7 @@ import com.nimbusds.jwt.JWTParser; import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.core.OAuth2TokenEntityService; import it.infn.mw.iam.core.user.IamAccountService; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAup; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RevocationEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RevocationEndpointTests.java index e5acce8dc9..70d1ded55d 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RevocationEndpointTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RevocationEndpointTests.java @@ -25,13 +25,14 @@ import java.util.Set; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.oauth.revocation.IamTokenRevocationService; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @IamMockMvcIntegrationTest @@ -46,6 +47,9 @@ class RevocationEndpointTests extends EndpointsTestUtils { @Autowired private IamTokenService iamTokenService; + @Autowired + private IamTokenRevocationService revocationService; + @Test void testRevocationEnpointRequiresClientAuth() throws Exception { mvc @@ -71,7 +75,7 @@ void accessTokenRevocationWorks() throws Exception { Set accessTokens = iamTokenService.getAllAccessTokensForUser("test"); // Start clean - accessTokens.forEach(iamTokenService::revokeAccessToken); + accessTokens.forEach(revocationService::revokeAccessToken); String accessToken = getPasswordToken().accessToken(); @@ -97,7 +101,7 @@ void accessTokenRevocationRevokesTheRightToken() throws Exception { Set accessTokens = iamTokenService.getAllAccessTokensForUser("test"); // Start clean - accessTokens.forEach(iamTokenService::revokeAccessToken); + accessTokens.forEach(revocationService::revokeAccessToken); String tokenOne = getPasswordToken().accessToken(); String tokenTwo = getPasswordToken().accessToken(); @@ -122,8 +126,8 @@ void accessTokenRevocationRevokesTheRightToken() throws Exception { void refreshTokenRevocationWorks() throws Exception { // Start clean - iamTokenService.getAllAccessTokensForUser("test").forEach(iamTokenService::revokeAccessToken); - iamTokenService.getAllRefreshTokensForUser("test").forEach(iamTokenService::revokeRefreshToken); + iamTokenService.getAllAccessTokensForUser("test").forEach(revocationService::revokeAccessToken); + iamTokenService.getAllRefreshTokensForUser("test").forEach(revocationService::revokeRefreshToken); String refreshToken = getPasswordToken("openid profile offline_access").refreshToken(); @@ -142,8 +146,8 @@ void refreshTokenRevocationWorks() throws Exception { @Test void refreshTokenRevocationRevokesTheRightToken() throws Exception { // Start clean - iamTokenService.getAllAccessTokensForUser("test").forEach(iamTokenService::revokeAccessToken); - iamTokenService.getAllRefreshTokensForUser("test").forEach(iamTokenService::revokeRefreshToken); + iamTokenService.getAllAccessTokensForUser("test").forEach(revocationService::revokeAccessToken); + iamTokenService.getAllRefreshTokensForUser("test").forEach(revocationService::revokeRefreshToken); final String SCOPES = "openid profile offline_access"; String rt1 = getPasswordToken(SCOPES).refreshToken(); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenLifetimeConfigurableTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenLifetimeConfigurableTests.java index 9c8778ed1a..95b4a45d57 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenLifetimeConfigurableTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenLifetimeConfigurableTests.java @@ -33,7 +33,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -47,7 +46,8 @@ import it.infn.mw.iam.api.client.management.service.ClientManagementService; import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; -import it.infn.mw.iam.core.oauth.profile.IamTokenEnhancer; +import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @SuppressWarnings("deprecation") @@ -66,7 +66,6 @@ public class TokenLifetimeConfigurableTests { private static final String SCOPE = "openid profile offline_access"; private static final String CUSTOM_LIFETIME = "300"; - private static final String INVALID_PARAMETER = IamTokenEnhancer.INVALID_PARAMETER; private static final long TOLERANCE = 5; private static final long DEFAULT_ACCESS_TOKEN_LIFETIME = 3600L; @@ -213,11 +212,12 @@ void testTokenLifetimeNotInteger() throws Exception { .param("client_secret", CLIENT_CRED_GRANT_CLIENT_SECRET) .param("expires_in", "test")) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error_description", equalTo(INVALID_PARAMETER))); + .andExpect(jsonPath("$.error_description", equalTo(IamTokenService.INVALID_PARAMETER))); } @Test void testParameterRequestedDuringAccessTokenRequest() throws Exception { + String configuredAccessTokenResponse = mvc .perform( post("/token").with(httpBasic(PASSWORD_GRANT_CLIENT_ID, PASSWORD_GRANT_CLIENT_SECRET)) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WellKnownConfigurationEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WellKnownConfigurationEndpointTests.java index e346e2bb9d..b09cf85fca 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WellKnownConfigurationEndpointTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WellKnownConfigurationEndpointTests.java @@ -31,8 +31,6 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.service.SystemScopeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -46,13 +44,15 @@ import com.google.common.collect.Sets; import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; import it.infn.mw.iam.core.web.wellknown.IamDiscoveryEndpoint; +import it.infn.mw.iam.persistence.model.SystemScope; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @IamMockMvcIntegrationTest @SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) @TestPropertySource(properties = "task.wellKnownCacheCleanupPeriodSecs=1") -@ActiveProfiles({"h2-test", "dev"}) +@ActiveProfiles({"h2-test"}) class WellKnownConfigurationEndpointTests { private String endpoint = "/" + IamDiscoveryEndpoint.OPENID_CONFIGURATION_URL; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WhitelistedSiteTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WhitelistedSiteTests.java index a0b41ac00d..113cf1be73 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WhitelistedSiteTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/WhitelistedSiteTests.java @@ -29,20 +29,20 @@ import org.assertj.core.util.Sets; import org.junit.jupiter.api.Test; -import org.mitre.openid.connect.model.WhitelistedSite; -import org.mitre.openid.connect.service.impl.DefaultWhitelistedSiteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpSession; import com.fasterxml.jackson.databind.JsonNode; +import it.infn.mw.iam.core.oauth.IamWhitelistedSiteService; +import it.infn.mw.iam.persistence.model.WhitelistedSite; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @IamMockMvcIntegrationTest class WhitelistedSiteTests extends EndpointsTestUtils { @Autowired - DefaultWhitelistedSiteService whitelistedSiteService; + IamWhitelistedSiteService whitelistedSiteService; protected WhitelistedSite getApprovedSiteFor(String creator, String clientId, Set scopes) { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTestSupport.java index 33725c549b..8102523a1f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTestSupport.java @@ -19,8 +19,6 @@ import java.util.function.Consumer; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -31,6 +29,9 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + public interface IAMJWTBearerAuthenticationProviderTestSupport { String JWT_AUTH_NAME = "jwt-bearer-client"; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTests.java index c617813ba2..d1b423522e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/IAMJWTBearerAuthenticationProviderTests.java @@ -15,12 +15,10 @@ */ package it.infn.mw.iam.test.oauth.assertion; -import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.lenient; @@ -28,17 +26,12 @@ import java.time.Instant; import java.time.ZoneId; import java.util.Date; +import java.util.List; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.openid.connect.assertion.JWTBearerAssertionAuthenticationToken; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -52,8 +45,14 @@ import com.nimbusds.jwt.PlainJWT; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.authn.jwt.JwtBearerAssertionAuthenticationToken; import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.core.jwt.ClientKeyCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; import it.infn.mw.iam.core.oauth.assertion.IAMJWTBearerAuthenticationProvider; +import it.infn.mw.iam.persistence.model.AuthMethod; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; @ExtendWith(MockitoExtension.class) class IAMJWTBearerAuthenticationProviderTests @@ -62,7 +61,7 @@ class IAMJWTBearerAuthenticationProviderTests static final Instant NOW = Instant.parse("2021-01-01T00:00:00.00Z"); @Mock - ClientDetailsEntityService clientService; + IamClientDetailsService clientService; @Mock ClientKeyCacheService validators; @@ -71,10 +70,10 @@ class IAMJWTBearerAuthenticationProviderTests IamProperties iamProperties; @Mock - JWTBearerAssertionAuthenticationToken authentication; + JwtBearerAssertionAuthenticationToken authentication; @Mock - JWTSigningAndValidationService validator; + JwtSigningAndValidationService validator; @Mock ClientDetailsEntity client; @@ -389,7 +388,7 @@ void testInvalidAudience() { JWTClaimsSet claimSet = new JWTClaimsSet.Builder().issuer(JWT_AUTH_NAME) .subject(JWT_AUTH_NAME) .expirationTime(Date.from(clock.instant().plusSeconds(1800))) - .audience(singletonList("invalid-audience")) + .audience(List.of("invalid-audience")) .build(); SignedJWT jws = new SignedJWT(header, claimSet); lenient().when(authentication.getJwt()).thenReturn(jws); @@ -413,7 +412,7 @@ void testJTIRequired() { JWTClaimsSet claimSet = new JWTClaimsSet.Builder().issuer(JWT_AUTH_NAME) .subject(JWT_AUTH_NAME) .expirationTime(Date.from(clock.instant().plusSeconds(1800))) - .audience(singletonList(ISSUER_TOKEN_ENDPOINT)) + .audience(List.of(ISSUER_TOKEN_ENDPOINT)) .build(); SignedJWT jws = new SignedJWT(header, claimSet); lenient().when(authentication.getJwt()).thenReturn(jws); @@ -437,19 +436,19 @@ void testValidAssertion() { JWTClaimsSet claimSet = new JWTClaimsSet.Builder().issuer(JWT_AUTH_NAME) .subject(JWT_AUTH_NAME) .expirationTime(Date.from(clock.instant().plusSeconds(1800))) - .audience(singletonList(ISSUER_TOKEN_ENDPOINT)) + .audience(List.of(ISSUER_TOKEN_ENDPOINT)) .jwtID(UUID.randomUUID().toString()) .build(); SignedJWT jws = new SignedJWT(header, claimSet); lenient().when(authentication.getJwt()).thenReturn(jws); - JWTBearerAssertionAuthenticationToken authToken = - (JWTBearerAssertionAuthenticationToken) provider.authenticate(authentication); + JwtBearerAssertionAuthenticationToken authToken = + (JwtBearerAssertionAuthenticationToken) provider.authenticate(authentication); assertThat(authToken.isAuthenticated(), is(true)); assertThat(authToken.getName(), is(JWT_AUTH_NAME)); assertThat(authToken.getAuthorities(), hasItem(ROLE_CLIENT_AUTHORITY)); - assertThat(authToken.getAuthorities(), hasSize(1)); + assertThat(authToken.getAuthorities().size(), is(1)); }); } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java index de26d64797..3906ea2383 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java @@ -23,7 +23,6 @@ import java.util.Date; import java.util.UUID; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; @@ -35,7 +34,8 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.core.jwt.IamJwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.util.JWKKeystoreLoader; @@ -73,13 +73,13 @@ public SignedJWT createSymmetricClientAuthToken(String clientId, Instant expirat return signedJWT; } - public JWTSigningAndValidationService loadSignerService() + public JwtSigningAndValidationService loadSignerService() throws NoSuchAlgorithmException, InvalidKeySpecException { JWKKeystoreLoader keystoreLoader = new JWKKeystoreLoader(loader); - JWTSigningAndValidationService svc = - new IamJWTSigningService(keystoreLoader.loadKeystoreFromLocation(TEST_KEYSTORE_LOCATION), + JwtSigningAndValidationService svc = + new IamJwtSigningAndValidationService(keystoreLoader.loadKeystoreFromLocation(TEST_KEYSTORE_LOCATION), "rsa1", JWSAlgorithm.RS256.getName()); return svc; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTests.java index 4d777ad0ef..fbfef4d10d 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTests.java @@ -26,7 +26,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.springframework.test.context.junit.jupiter.SpringExtension; import com.nimbusds.jose.JWSAlgorithm; @@ -35,6 +34,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @ExtendWith(SpringExtension.class) @@ -60,7 +60,7 @@ void testSymmetricJwtAuth() throws Exception { @Test void testAsymmetricJwtAuth() throws Exception { - JWTSigningAndValidationService signer = loadSignerService(); + JwtSigningAndValidationService signer = loadSignerService(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject(CLIENT_ID_PRIVATE_KEY_JWT) .issuer(CLIENT_ID_PRIVATE_KEY_JWT) .expirationTime(Date.from(Instant.now().plusSeconds(600))) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeIntegrationTests.java index 03b0b3f393..6c7d454d48 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeIntegrationTests.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Pageable; @@ -53,6 +52,7 @@ import io.restassured.response.ValidatableResponse; import io.restassured.specification.RequestSpecification; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; import it.infn.mw.iam.test.TestUtils; @@ -62,7 +62,7 @@ @ExtendWith(SpringExtension.class) @IamRandomPortIntegrationTest @TestPropertySource(properties = {"iam.access_token.include_scope=true"}) -@ActiveProfiles({"h2-test", "h2", "wlcg-scopes"}) +@ActiveProfiles({"h2-test", "wlcg-scopes"}) public class AuthorizationCodeIntegrationTests extends ScopePolicyTestUtils { public static final String TEST_CLIENT_ID = "client"; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java index 5a2d9523eb..4695482b5b 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java @@ -35,8 +35,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.mock.web.MockHttpSession; @@ -49,10 +47,12 @@ import org.springframework.web.util.UriComponentsBuilder; import it.infn.mw.iam.api.client.service.ClientService; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAup; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamAupRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @ExtendWith(SpringExtension.class) @@ -90,7 +90,7 @@ public class AuthorizationCodeTests { private ClientService clientService; @Autowired - private ClientDetailsEntityService clientDetailsService; + private IamClientDetailsService clientDetailsService; @Autowired IamAccountRepository accountRepo; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeWithPKCEIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeWithPKCEIntegrationTests.java index 07a073d44c..60a67499ee 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeWithPKCEIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeWithPKCEIntegrationTests.java @@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.PKCEAlgorithm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -39,6 +38,7 @@ import io.restassured.RestAssured; import io.restassured.response.ValidatableResponse; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.test.TestUtils; import it.infn.mw.iam.test.repository.ScopePolicyTestUtils; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationAuthzTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationAuthzTests.java index b40bed5561..218fedd450 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationAuthzTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationAuthzTests.java @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; @@ -31,10 +30,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @ExtendWith(SpringExtension.class) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationScopeFilteringTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationScopeFilteringTests.java index c1dfe0f74b..32a7651eb3 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationScopeFilteringTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationScopeFilteringTests.java @@ -33,7 +33,7 @@ @ExtendWith(SpringExtension.class) @IamMockMvcIntegrationTest -@ActiveProfiles({"h2", "wlcg-scopes"}) +@ActiveProfiles({"h2-test", "wlcg-scopes"}) class ClientRegistrationScopeFilteringTests extends ClientRegistrationTestSupport { @Autowired diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTestSupport.java index 9fc1de16bf..f33634929a 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTestSupport.java @@ -15,27 +15,25 @@ */ package it.infn.mw.iam.test.oauth.client_registration; -import static com.google.common.collect.Sets.newHashSet; -import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS; -import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID; -import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME; -import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS; -import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES; -import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS; -import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS; -import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES; -import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE; -import static org.mitre.util.JsonUtils.getAsArray; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.CLIENT_ID; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.CLIENT_NAME; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.CONTACTS; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.GRANT_TYPES; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.REDIRECT_URIS; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.REQUEST_URIS; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.RESPONSE_TYPES; +import static it.infn.mw.iam.persistence.model.RegisteredClientFields.SCOPE; import java.util.Set; -import org.mitre.oauth2.model.RegisteredClientFields; - import com.google.common.base.Joiner; import com.google.common.collect.Sets; import com.google.gson.JsonObject; import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; +import it.infn.mw.iam.persistence.model.RegisteredClientFields; +import it.infn.mw.iam.util.JsonUtils; public class ClientRegistrationTestSupport { @@ -107,12 +105,12 @@ public String build() { json.addProperty(CLIENT_ID, clientId); json.addProperty(CLIENT_NAME, name); json.addProperty(SCOPE, JOINER.join(scopes)); - json.add(REDIRECT_URIS, getAsArray(redirectUris)); - json.add(GRANT_TYPES, getAsArray(grantTypes)); - json.add(RESPONSE_TYPES, getAsArray(responseTypes, true)); - json.add(CLAIMS_REDIRECT_URIS, getAsArray(newHashSet(), true)); - json.add(REQUEST_URIS, getAsArray(newHashSet(), true)); - json.add(CONTACTS, getAsArray(newHashSet("test@iam.test"))); + json.add(REDIRECT_URIS, JsonUtils.getAsArray(redirectUris)); + json.add(GRANT_TYPES, JsonUtils.getAsArray(grantTypes)); + json.add(RESPONSE_TYPES, JsonUtils.getAsArray(responseTypes, true)); + json.add(CLAIMS_REDIRECT_URIS, JsonUtils.getAsArray(Sets.newHashSet(), true)); + json.add(REQUEST_URIS, JsonUtils.getAsArray(Sets.newHashSet(), true)); + json.add(CONTACTS, JsonUtils.getAsArray(Sets.newHashSet("test@iam.test"))); json.addProperty("access_token_validity_seconds", accessTokenValiditySeconds); json.addProperty("refresh_token_validity_seconds", refreshTokenValiditySeconds); return json.toString(); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTests.java index 2499cb9961..e147ae96b0 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ClientRegistrationTests.java @@ -36,11 +36,8 @@ import java.io.UnsupportedEncodingException; +import org.assertj.core.util.Strings; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.repository.OAuth2ClientRepository; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -50,8 +47,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.api.common.client.RegisteredClientDTO; +import it.infn.mw.iam.core.client.IamClientDetailsService; import it.infn.mw.iam.core.oauth.granters.IamDeviceCodeTokenGranter; import it.infn.mw.iam.core.oauth.granters.TokenExchangeTokenGranter; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @IamMockMvcIntegrationTest @@ -62,10 +63,10 @@ class ClientRegistrationTests extends ClientRegistrationTestSupport { private ObjectMapper mapper; @Autowired - private ClientDetailsEntityService clientService; + private IamClientDetailsService clientService; @Autowired - private OAuth2ClientRepository clientRepo; + private IamClientRepository clientRepo; @Autowired private MockMvc mvc; @@ -145,7 +146,6 @@ void testCreateClientWithRegistrationReservedScopes() throws Exception { String jsonInString = ClientJsonStringBuilder.builder().scopes(scopes).grantTypes("authorization_code").build(); - // @formatter:off String response = mvc.perform(post(REGISTER_ENDPOINT) .contentType(APPLICATION_JSON) @@ -155,9 +155,8 @@ void testCreateClientWithRegistrationReservedScopes() throws Exception { .andReturn() .getResponse() .getContentAsString(); - // @formatter:on - ClientDetailsEntity saved = ClientDetailsEntityJsonProcessor.parse(response); + RegisteredClientDTO saved = mapper.readValue(response, RegisteredClientDTO.class); assertNotNull(saved); for (String reservedScope : scopes) { @@ -173,7 +172,6 @@ void testGetTokenWithScimReservedScopesFailure() throws Exception { String jsonInString = ClientJsonStringBuilder.builder().scopes(scopes).grantTypes("authorization_code").build(); - // @formatter:off String response = mvc.perform(post(REGISTER_ENDPOINT) .contentType(APPLICATION_JSON) @@ -183,20 +181,17 @@ void testGetTokenWithScimReservedScopesFailure() throws Exception { .andReturn() .getResponse() .getContentAsString(); - // @formatter:on - ClientDetailsEntity saved = ClientDetailsEntityJsonProcessor.parse(response); + RegisteredClientDTO saved = mapper.readValue(response, RegisteredClientDTO.class); assertNotNull(saved); - // @formatter:off mvc.perform(post("/token") .param("grant_type", "client_credentials") .param("client_id", saved.getClientId()) .param("client_secret", saved.getClientSecret()) - .param("scope", ClientJsonStringBuilder.JOINER.join(scopes))) + .param("scope", Strings.join(scopes).with(" "))) .andExpect(status().isBadRequest()); - // @formatter:on } @Test @@ -254,7 +249,7 @@ void additionalGrantTypesAreNotLostWhenUpdatingClient() throws Exception { clientModel.getGrantTypes().add("password"); clientModel.getGrantTypes().add(TokenExchangeTokenGranter.TOKEN_EXCHANGE_GRANT_TYPE); - clientRepo.saveClient(clientModel); + clientRepo.save(clientModel); clientJson = mvc .perform(get(registrationUri).contentType(APPLICATION_JSON) @@ -280,7 +275,7 @@ void additionalGrantTypesAreNotLostWhenUpdatingClient() throws Exception { clientModel = clientService.loadClientByClientId(clientId); clientModel.getGrantTypes().remove("password"); clientModel.getGrantTypes().remove(TokenExchangeTokenGranter.TOKEN_EXCHANGE_GRANT_TYPE); - clientRepo.saveClient(clientModel); + clientRepo.save(clientModel); mvc .perform(get(registrationUri).contentType(APPLICATION_JSON) @@ -298,7 +293,7 @@ void additionalGrantTypesAreNotLostWhenUpdatingClient() throws Exception { @Test void deviceCodeTimeoutNotAffectedWhenCreatingAndUpdatingClient() - throws UnsupportedEncodingException, Exception { + throws UnsupportedEncodingException, Exception { String jsonInString = ClientJsonStringBuilder.builder().grantTypes("authorization_code").scopes("openid").build(); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ProtectedResourceAndTokenGrantTypesTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ProtectedResourceAndTokenGrantTypesTests.java index 94db9fbfe3..d781e06872 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ProtectedResourceAndTokenGrantTypesTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/client_registration/ProtectedResourceAndTokenGrantTypesTests.java @@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.test.context.support.WithMockUser; @@ -36,6 +35,7 @@ import org.springframework.web.util.UriComponentsBuilder; import it.infn.mw.iam.api.client.service.ClientService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeApprovalTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeApprovalTests.java index 5a3d1f76b2..11d5422e05 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeApprovalTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeApprovalTests.java @@ -39,11 +39,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.DeviceCode; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; -import org.mitre.openid.connect.model.ApprovedSite; -import org.mitre.openid.connect.service.ApprovedSiteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -54,7 +49,14 @@ import com.nimbusds.oauth2.sdk.GrantType; import it.infn.mw.iam.IamLoginService; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.core.oauth.approvedsite.ApprovedSiteService; +import it.infn.mw.iam.persistence.model.ApprovedSite; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.DeviceCode; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -66,7 +68,10 @@ class DeviceCodeApprovalTests extends EndpointsTestUtils { private IamClientRepository clientRepo; @Autowired - private ConfigurationPropertiesBean config; + private IamAccountRepository accountRepo; + + @Autowired + private IamProperties config; @Autowired private ApprovedSiteService approvedSiteService; @@ -104,7 +109,7 @@ void testParenthesisInRequestedScopesDoesNotMatchAllowedScopes() throws Exceptio @Test void testDeviceCodeNotReturnCompleteUri() throws Exception { - config.setAllowCompleteDeviceCodeUri(false); + config.getDeviceCode().setAllowCompleteVerificationUri(false); mvc .perform(post(DEVICE_CODE_ENDPOINT).contentType(APPLICATION_FORM_URLENCODED) @@ -116,8 +121,7 @@ void testDeviceCodeNotReturnCompleteUri() throws Exception { .andExpect(jsonPath("$.verification_uri_complete").doesNotExist()) .andExpect(jsonPath("$.verification_uri", equalTo(DEVICE_USER_URL))); - config.setAllowCompleteDeviceCodeUri(true); - + config.getDeviceCode().setAllowCompleteVerificationUri(true); } @Test @@ -772,8 +776,11 @@ void testNoneRememberParameterDoesNotAddAnApprovedSite() throws Exception { .andExpect(status().isOk()) .andExpect(view().name("deviceApproved")); + ClientDetailsEntity client = clientRepo.findByClientId(DEVICE_CODE_CLIENT_ID).orElseThrow(); + IamAccount account = accountRepo.findByUsername(TEST_USERNAME).orElseThrow(); + Collection approvedSites = - approvedSiteService.getByClientIdAndUserId(DEVICE_CODE_CLIENT_ID, TEST_USERNAME); + approvedSiteService.getByClientAndUser(client, account); assertTrue(approvedSites.isEmpty()); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java index 64a5ad2d4b..de7b82c54b 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java @@ -40,8 +40,6 @@ import java.util.Set; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.openid.connect.web.ApprovedSiteAPI; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -59,7 +57,8 @@ import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.core.oauth.introspection.model.TokenTypeHint; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.oauth.client_registration.ClientRegistrationTestSupport.ClientJsonStringBuilder; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -473,7 +472,7 @@ void testDeviceCodeApprovalFlowWorks() throws Exception { .getRequest() .getSession(); - mvc.perform(get("/" + ApprovedSiteAPI.URL).session(session)) + mvc.perform(get("/api/approved").session(session)) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].clientId", equalTo(DEVICE_CODE_CLIENT_ID))) .andExpect(jsonPath("$[0].userId", equalTo(TEST_USERNAME))); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointAuthenticationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointAuthenticationTests.java index 5d8f17fb89..e3c9cb1a31 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointAuthenticationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointAuthenticationTests.java @@ -24,18 +24,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.transaction.annotation.Transactional; import it.infn.mw.iam.IamLoginService; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @IamMockMvcIntegrationTest @SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) +@Transactional class IntrospectionEndpointAuthenticationTests extends EndpointsTestUtils { private String accessToken; @@ -98,9 +100,6 @@ void testTokenIntrospectionEndpointWithDisabledClient() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.active", equalTo(false))); // @formatter:on - - c.setActive(true); - clientRepo.save(c); } @Test @@ -118,8 +117,5 @@ void testTokenIntrospectionEndpointWithClientNotAllowedIntrospection() throws Ex .andExpect(status().isOk()) .andExpect(jsonPath("$.active", equalTo(false))); // @formatter:on - - c.setAllowIntrospection(true); - clientRepo.save(c); } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointTests.java index 070bc3f605..7bc378dec7 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/introspection/IntrospectionEndpointTests.java @@ -29,10 +29,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.Test; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; @@ -44,11 +40,15 @@ import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; import it.infn.mw.iam.core.oauth.introspection.model.TokenTypeHint; import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.api.tokens.TestTokensUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -75,7 +75,7 @@ class IntrospectionEndpointTests extends TestTokensUtils { IamTokenService tokenService; @Autowired - JWTSigningAndValidationService signService; + JwtSigningAndValidationService signService; @Autowired ObjectMapper mapper; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKCacheSetServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKCacheSetServiceTests.java index 5cc067efaa..88090521e3 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKCacheSetServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKCacheSetServiceTests.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -39,6 +38,7 @@ import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.authn.oidc.RestTemplateFactory; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; import it.infn.mw.iam.test.util.oidc.MockRestTemplateFactory; @@ -60,7 +60,7 @@ RestTemplateFactory mockRestTemplateFactory() { } @Autowired - JWKSetCacheService service; + JwkSetCacheService service; @Autowired ObjectMapper mapper; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKTestSupport.java index 8878f907be..785400cd4f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWKTestSupport.java @@ -21,10 +21,9 @@ import java.nio.charset.Charset; import java.text.ParseException; -import org.mitre.jose.keystore.JWKSetKeyStore; - import com.nimbusds.jose.jwk.JWKSet; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; import it.infn.mw.iam.core.web.jwk.IamJWKSetPublishingEndpoint; public interface JWKTestSupport { @@ -48,8 +47,8 @@ default JWKSet loadJWKSet(String location) throws IOException, ParseException { } } - default JWKSetKeyStore loadKeystore(String location) throws IOException, ParseException { - return new JWKSetKeyStore(loadJWKSet(location)); + default JwkSetKeyStore loadKeystore(String location) throws IOException, ParseException { + return new JwkSetKeyStore(loadJWKSet(location)); } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWTSigningServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWTSigningServiceTests.java index f1e8887b3d..4201055119 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWTSigningServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/jwk/JWTSigningServiceTests.java @@ -30,7 +30,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.jose.keystore.JWKSetKeyStore; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.Mockito; @@ -44,7 +43,9 @@ import com.nimbusds.jwt.SignedJWT; import it.infn.mw.iam.config.IamProperties.JWKProperties; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.core.jwt.IamJwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; @ExtendWith(MockitoExtension.class) class JWTSigningServiceTests implements JWKTestSupport { @@ -53,15 +54,15 @@ class JWTSigningServiceTests implements JWKTestSupport { JWKProperties properties; @Mock - JWKSetKeyStore mockKeystore; + JwkSetKeyStore mockKeystore; - IamJWTSigningService service; + JwtSigningAndValidationService service; @Test void nullKeystoreIsNotAccepted() { - NullPointerException e = - assertThrows(NullPointerException.class, () -> service = new IamJWTSigningService(null)); + NullPointerException e = assertThrows(NullPointerException.class, + () -> service = new IamJwtSigningAndValidationService(null)); assertThat(e.getMessage(), is("null keystore")); } @@ -69,8 +70,8 @@ void nullKeystoreIsNotAccepted() { void emptyKeystoreIsNotAccepted() { lenient().when(mockKeystore.getKeys()).thenReturn(Collections.emptyList()); - IllegalArgumentException e = - assertThrows(IllegalArgumentException.class, () -> service = new IamJWTSigningService(mockKeystore)); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> service = new IamJwtSigningAndValidationService(mockKeystore)); assertThat(e.getMessage(), is("Please provide a non-empty keystore")); } @@ -78,15 +79,15 @@ void emptyKeystoreIsNotAccepted() { void emptyKeystoreIsNotAcceptedWhenProvidingProperties() { lenient().when(mockKeystore.getKeys()).thenReturn(Collections.emptyList()); - IllegalArgumentException e = - assertThrows(IllegalArgumentException.class, () -> service = new IamJWTSigningService(properties, mockKeystore)); - assertThat(e.getMessage(), is("empty keystore")); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> service = new IamJwtSigningAndValidationService(properties, mockKeystore)); + assertThat(e.getMessage(), is("Please provide a non-empty keystore")); } @Test void rsaSignatureAndVerificationWorks() throws ParseException, IOException { - service = new IamJWTSigningService(loadKeystore(KS1_LOCATION)); + service = new IamJwtSigningAndValidationService(loadKeystore(KS1_LOCATION)); assertThat(service.getDefaultSignerKeyId(), is("iam1")); assertThat(service.getDefaultSigningAlgorithm(), nullValue()); @@ -117,8 +118,8 @@ void rsaSignatureAndVerificationWorks() throws ParseException, IOException { @Test void signerReportsUnknownKey() throws ParseException, IOException { - JWKSetKeyStore ks = loadKeystore(KS1_LOCATION); - service = new IamJWTSigningService(ks); + JwkSetKeyStore ks = loadKeystore(KS1_LOCATION); + service = new IamJwtSigningAndValidationService(ks); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("unknown").build(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject("sub").build(); @@ -132,8 +133,8 @@ void signerReportsUnknownKey() throws ParseException, IOException { @Test void verifyFailsForUnknownKey() throws ParseException, IOException { - JWKSetKeyStore ks = loadKeystore(KS1_LOCATION); - service = new IamJWTSigningService(ks); + JwkSetKeyStore ks = loadKeystore(KS1_LOCATION); + service = new IamJwtSigningAndValidationService(ks); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("unknown").build(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject("sub").build(); @@ -147,8 +148,10 @@ void verifyFailsForUnknownKey() throws ParseException, IOException { @Test void verifyFailsForInvalidKey() throws ParseException, IOException { - IamJWTSigningService signer1 = new IamJWTSigningService(loadKeystore(KS1_LOCATION)); - IamJWTSigningService signer2 = new IamJWTSigningService(loadKeystore(KS2_LOCATION)); + JwtSigningAndValidationService signer1 = + new IamJwtSigningAndValidationService(loadKeystore(KS1_LOCATION)); + JwtSigningAndValidationService signer2 = + new IamJwtSigningAndValidationService(loadKeystore(KS2_LOCATION)); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("iam1").build(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject("sub").build(); @@ -156,13 +159,13 @@ void verifyFailsForInvalidKey() throws ParseException, IOException { signer1.signJwt(signedJwt); assertThat(signer2.validateSignature(signedJwt), is(false)); - } @Test void exceptionDuringVerifyIsHandled() throws ParseException, IOException, JOSEException { - IamJWTSigningService signer = new IamJWTSigningService(loadKeystore(KS1_LOCATION)); + JwtSigningAndValidationService signer = + new IamJwtSigningAndValidationService(loadKeystore(KS1_LOCATION)); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("iam1").build(); @@ -172,14 +175,14 @@ void exceptionDuringVerifyIsHandled() throws ParseException, IOException, JOSEEx Mockito.when(mockSignedJwt.verify(ArgumentMatchers.any())) .thenThrow(new JOSEException("jose!")); - assertThat(signer.validateSignature(mockSignedJwt), is(false)); - } @Test void exceptionDuringSignIsHandled() throws ParseException, IOException, JOSEException { - IamJWTSigningService signer = new IamJWTSigningService(loadKeystore(KS1_LOCATION)); + + JwtSigningAndValidationService signer = + new IamJwtSigningAndValidationService(loadKeystore(KS1_LOCATION)); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("iam1").build(); @@ -193,22 +196,22 @@ void exceptionDuringSignIsHandled() throws ParseException, IOException, JOSEExce @Test void propertiesParsedCorrectly() throws IOException, ParseException { + lenient().when(properties.getDefaultJwsAlgorithm()).thenReturn(JWSAlgorithm.RS384.getName()); lenient().when(properties.getDefaultKeyId()).thenReturn("iam2"); - JWKSetKeyStore ks = new JWKSetKeyStore(loadJWKSet(KS1_LOCATION)); - service = new IamJWTSigningService(properties, ks); + JwkSetKeyStore ks = new JwkSetKeyStore(loadJWKSet(KS1_LOCATION)); + service = new IamJwtSigningAndValidationService(properties, ks); assertThat(service.getDefaultSignerKeyId(), is("iam2")); assertThat(service.getDefaultSigningAlgorithm(), is(JWSAlgorithm.RS384)); - } @Test void signWithAlgoWorksAsExpected() throws IOException, ParseException { - JWKSetKeyStore ks = new JWKSetKeyStore(loadJWKSet(KS1_LOCATION)); - service = new IamJWTSigningService(ks); + JwkSetKeyStore ks = new JwkSetKeyStore(loadJWKSet(KS1_LOCATION)); + service = new IamJwtSigningAndValidationService(ks); JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("iam1").build(); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject("sub").build(); SignedJWT signedJwt = new SignedJWT(header, claimsSet); @@ -223,11 +226,9 @@ void signWithAlgoWorksAsExpected() throws IOException, ParseException { @Test void getAllAlgosWorkAsExpected() throws IOException, ParseException { - JWKSetKeyStore ks = new JWKSetKeyStore(loadJWKSet(KS1_LOCATION)); - service = new IamJWTSigningService(ks); + JwkSetKeyStore ks = new JwkSetKeyStore(loadJWKSet(KS1_LOCATION)); + service = new IamJwtSigningAndValidationService(ks); assertThat(service.getAllSigningAlgsSupported().containsAll(JWSAlgorithm.Family.RSA), is(true)); - } - } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/IamAccessTokenBuilderTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/IamAccessTokenBuilderTests.java index 7754f3358f..70337c5a07 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/IamAccessTokenBuilderTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/IamAccessTokenBuilderTests.java @@ -29,9 +29,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; @@ -43,9 +40,12 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.iam.IamAccessTokenBuilder; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; import it.infn.mw.iam.test.util.oauth.MockOAuth2Request; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/KeycloakAccessTokenBuilderTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/KeycloakAccessTokenBuilderTests.java index 78dd9ddf62..805239910e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/KeycloakAccessTokenBuilderTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/KeycloakAccessTokenBuilderTests.java @@ -29,9 +29,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; @@ -43,9 +40,12 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.oauth.profile.ClaimValueHelper; +import it.infn.mw.iam.core.oauth.profile.ScopeClaimTranslationService; import it.infn.mw.iam.core.oauth.profile.keycloak.KeycloakAccessTokenBuilder; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; import it.infn.mw.iam.test.util.oauth.MockOAuth2Request; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGGroupHelperTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGGroupHelperTests.java index 67cb1ae93a..aa287e2f65 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGGroupHelperTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGGroupHelperTests.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; @@ -46,6 +45,7 @@ import it.infn.mw.iam.core.oauth.profile.wlcg.WlcgGroupHelper; import it.infn.mw.iam.persistence.model.IamGroup; import it.infn.mw.iam.persistence.model.IamUserInfo; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; @SuppressWarnings("deprecation") @ExtendWith(MockitoExtension.class) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java index 2bca57b8aa..751a7dfd7f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/profile/WLCGProfileIntegrationTests.java @@ -87,7 +87,7 @@ @SuppressWarnings("deprecation") @ExtendWith(SpringExtension.class) @IamMockMvcIntegrationTest -@ActiveProfiles({"h2", "wlcg-scopes"}) +@ActiveProfiles({"h2-test", "wlcg-scopes"}) @TestPropertySource(properties = { // @formatter:off "iam.jwt-profile.default-profile=wlcg", diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/revocation/TokenRevocationServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/revocation/TokenRevocationServiceTests.java index d82b69c22b..634791d81e 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/revocation/TokenRevocationServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/revocation/TokenRevocationServiceTests.java @@ -23,8 +23,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -36,8 +34,10 @@ import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.core.IamTokenService; import it.infn.mw.iam.core.oauth.revocation.TokenRevocationService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.oauth.client_registration.ClientRegistrationTestSupport.ClientJsonStringBuilder; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/DynClientScopeValidationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/DynClientScopeValidationTests.java deleted file mode 100644 index edbde16792..0000000000 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/DynClientScopeValidationTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * 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.test.oauth.scope; - -import static com.google.common.collect.Sets.newHashSet; -import static it.infn.mw.iam.core.oauth.scope.matchers.StringEqualsScopeMatcher.stringEqualsMatcher; -import static it.infn.mw.iam.core.oauth.scope.matchers.StructuredPathScopeMatcher.structuredPathMatcher; -import static java.util.Collections.emptySet; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC; -import static org.mockito.Mockito.lenient; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; -import org.mitre.openid.connect.exception.ValidationException; -import org.mitre.openid.connect.service.BlacklistedSiteService; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.google.common.collect.Sets; - -import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcherRegistry; -import it.infn.mw.iam.core.oidc.IamClientValidationService; - -@ExtendWith(MockitoExtension.class) -class DynClientScopeValidationTests { - - @Mock - SystemScopeService scopeService; - - @Mock - ScopeMatcherRegistry registry; - - @Spy - ClientDetailsEntity client = new ClientDetailsEntity(); - - @Mock - ConfigurationPropertiesBean config; - - @Mock - BlacklistedSiteService blacklistService; - - @Mock - ClientDetailsEntityService clientService; - - @InjectMocks - IamClientValidationService clientValidationService; - - @BeforeEach - void setup() { - - client.setScope(newHashSet("openid", "profile")); - client.setGrantTypes(newHashSet("authorization_code")); - client.setRedirectUris(newHashSet("https://test.example/cb")); - client.setTokenEndpointAuthMethod(SECRET_BASIC); - lenient().when(scopeService.getRestricted()).thenReturn(emptySet()); - lenient().when(registry.findMatcherForScope("restricted")) - .thenReturn(stringEqualsMatcher("restricted")); - lenient().when(clientService.generateClientSecret(Mockito.any())) - .thenAnswer(i -> i.getArguments()[0]); - } - - @Test - void noFilterTest() throws ValidationException { - - client = clientValidationService.validateClient(client); - assertThat(client.getScope(), hasSize(2)); - assertThat(client.getScope(), hasItem("openid")); - assertThat(client.getScope(), hasItem("profile")); - } - - @Test - void staticRestrictedScopeFilterTest() throws ValidationException { - - client.setScope(newHashSet("openid", "profile", "restricted")); - lenient().when(scopeService.getRestricted()) - .thenReturn(Sets.newHashSet(new SystemScope("restricted"))); - client = clientValidationService.validateClient(client); - assertThat(client.getScope(), hasSize(2)); - assertThat(client.getScope(), hasItem("openid")); - assertThat(client.getScope(), hasItem("profile")); - } - - @Test - void staticStructuredScopeFilterTest() throws ValidationException { - - client.setScope(newHashSet("openid", "profile", "read", "read:/", "read:/sub/path")); - lenient().when(scopeService.getRestricted()).thenReturn(newHashSet(new SystemScope("read:/"))); - lenient().when(registry.findMatcherForScope("read:/")) - .thenReturn(structuredPathMatcher("read", "/")); - client = clientValidationService.validateClient(client); - assertThat(client.getScope(), hasSize(3)); - assertThat(client.getScope(), hasItem("openid")); - assertThat(client.getScope(), hasItem("profile")); - assertThat(client.getScope(), hasItem("read")); - } -} diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/ScopeRegistryTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/ScopeRegistryTests.java index 5b4c1c8afc..683f0c78f5 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/ScopeRegistryTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/ScopeRegistryTests.java @@ -32,16 +32,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.repository.SystemScopeRepository; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.oauth2.provider.ClientDetails; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import it.infn.mw.iam.core.oauth.scope.matchers.DefaultScopeMatcherRegistry; import it.infn.mw.iam.core.oauth.scope.matchers.ScopeMatcher; +import it.infn.mw.iam.persistence.model.SystemScope; +import it.infn.mw.iam.persistence.repository.IamSystemScopeRepository; @SuppressWarnings("deprecation") @ExtendWith(MockitoExtension.class) @@ -51,12 +52,12 @@ class ScopeRegistryTests { ClientDetails client; @Mock - SystemScopeRepository scopeRepo; + IamSystemScopeRepository scopeRepo; @BeforeEach void setup() { SystemScope testScope = new SystemScope("test:/whatever"); - when(scopeRepo.getAll()).thenReturn(Sets.newHashSet(testScope)); + when(scopeRepo.findAll()).thenReturn(Lists.newArrayList(testScope)); } @Test @@ -92,11 +93,12 @@ void testNonMatchingScope() { @Test void testMatchingScope() { - DefaultScopeMatcherRegistry matcherRegistry = - new DefaultScopeMatcherRegistry(newHashSet(regexpMatcher("^test:/.*$"), structuredPathMatcher("storage.create", "/")), scopeRepo); + DefaultScopeMatcherRegistry matcherRegistry = new DefaultScopeMatcherRegistry( + newHashSet(regexpMatcher("^test:/.*$"), structuredPathMatcher("storage.create", "/")), + scopeRepo); - when(client.getScope()) - .thenReturn(Sets.newHashSet("openid", "profile", "test", "test:/whatever", "storage.create:/whatever")); + when(client.getScope()).thenReturn( + Sets.newHashSet("openid", "profile", "test", "test:/whatever", "storage.create:/whatever")); Set matchers = matcherRegistry.findMatchersForClient(client); assertThat(matchers, not(nullValue())); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/StructuredScopeRequestIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/StructuredScopeRequestIntegrationTests.java index 472f8b604e..70fbad8a3c 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/StructuredScopeRequestIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/StructuredScopeRequestIntegrationTests.java @@ -33,8 +33,6 @@ import java.util.Set; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.SystemScopeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrint; @@ -51,7 +49,9 @@ import com.nimbusds.oauth2.sdk.GrantType; import it.infn.mw.iam.IamLoginService; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; @SuppressWarnings("deprecation") @@ -60,7 +60,7 @@ @TestPropertySource(properties = { "spring.main.allow-bean-definition-overriding=true", }) -@ActiveProfiles({"h2", "wlcg-scopes", "registration"}) +@ActiveProfiles({"h2-test", "wlcg-scopes"}) class StructuredScopeRequestIntegrationTests extends EndpointsTestUtils implements StructuredScopeTestSupportConstants { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java index f7d0ee69ec..cb34a889dc 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java @@ -25,7 +25,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -38,7 +37,8 @@ import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.config.CacheProperties; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java index fe39fe2112..69395f978a 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java @@ -15,16 +15,13 @@ */ package it.infn.mw.iam.test.oauth.scope.matchers; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.in; -import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -33,11 +30,13 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @@ -66,9 +65,9 @@ private String getAccessTokenForClient(String scopes) throws Exception { @Test void ensureRedisCacheIsDisabled() { - assertThat(cacheManager, instanceOf(NoOpCacheManager.class)); - assertThat(cacheManager, not(instanceOf(ConcurrentMapCacheManager.class))); - assertThat(cacheManager, not(instanceOf(RedisCacheManager.class))); + assertTrue(cacheManager instanceof NoOpCacheManager); + assertFalse(cacheManager instanceof ConcurrentMapCacheManager); + assertFalse(cacheManager instanceof RedisCacheManager); } @Test @@ -83,12 +82,11 @@ void updatingClientScopesWithNoCache() throws Exception { try { JWT token = JWTParser.parse(getAccessTokenForClient("openid profile email")); - assertThat("scim:read", - not(in(token.getJWTClaimsSet().getClaim("scope").toString().split(" ")))); + assertFalse(Lists.newArrayList(token.getJWTClaimsSet().getClaim("scope").toString().split(" ")).contains("scim:read")); client.setScope(Sets.newHashSet("openid", "profile", "email", "scim:read")); clientRepo.save(client); token = JWTParser.parse(getAccessTokenForClient("openid profile email scim:read")); - assertThat("scim:read", in(token.getJWTClaimsSet().getClaim("scope").toString().split(" "))); + assertTrue(Lists.newArrayList(token.getJWTClaimsSet().getClaim("scope").toString().split(" ")).contains("scim:read")); } finally { clientRepo.delete(client); } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyFilteringDeviceCodeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyFilteringDeviceCodeTests.java index 270314ca41..00e3078abd 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyFilteringDeviceCodeTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyFilteringDeviceCodeTests.java @@ -49,7 +49,7 @@ import it.infn.mw.iam.test.repository.ScopePolicyTestUtils; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; -@ActiveProfiles({"h2-test", "h2", "wlcg-scopes"}) +//@ActiveProfiles({"h2-test", "h2", "wlcg-scopes"}) @ExtendWith(SpringExtension.class) @IamMockMvcIntegrationTest class ScopePolicyFilteringDeviceCodeTests extends ScopePolicyTestUtils { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyPdpTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyPdpTests.java index 1501efee12..d77ee4acd0 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyPdpTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/ScopePolicyPdpTests.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.service.SystemScopeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -42,6 +41,7 @@ import com.google.common.collect.Sets; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; import it.infn.mw.iam.core.oauth.scope.pdp.ScopeFilter; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountGroupMembership; @@ -53,7 +53,7 @@ import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; @ExtendWith(SpringExtension.class) -@ActiveProfiles({"h2-test", "h2", "saml", "registration", "wlcg-scopes"}) +//@ActiveProfiles({"h2-test", "h2", "saml", "registration", "wlcg-scopes"}) @IamMockMvcIntegrationTest class ScopePolicyPdpTests extends ScopePolicyTestUtils { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/AuthZCodeIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/AuthZCodeIntegrationTests.java index 849ba9b99a..4592134b18 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/AuthZCodeIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/AuthZCodeIntegrationTests.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; @@ -47,18 +46,19 @@ import io.restassured.RestAssured; import io.restassured.response.ValidatableResponse; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamScopePolicy; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.IamScopePolicyRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.TestUtils; import it.infn.mw.iam.test.repository.ScopePolicyTestUtils; import it.infn.mw.iam.test.util.annotation.IamRandomPortIntegrationTest; @IamRandomPortIntegrationTest @TestPropertySource(properties = {"iam.access_token.include_scope=true"}) -@ActiveProfiles({"h2", "wlcg-scopes", "registration"}) +@ActiveProfiles({"h2-test", "wlcg-scopes"}) class AuthZCodeIntegrationTests extends ScopePolicyTestUtils { @Autowired diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/OtherFlowsIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/OtherFlowsIntegrationTests.java index df20ea1c2d..e6fa837f00 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/OtherFlowsIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/pdp/wlcg/OtherFlowsIntegrationTests.java @@ -64,7 +64,7 @@ @ExtendWith(SpringExtension.class) @TestPropertySource(properties = {"iam.access_token.include_scope=true"}) @IamMockMvcIntegrationTest -@ActiveProfiles({"h2-test", "h2", "wlcg-scopes"}) +//@ActiveProfiles({"h2-test", "h2", "wlcg-scopes"}) class OtherFlowsIntegrationTests extends ScopePolicyTestUtils { @Autowired diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/FederationRegistrationControllerTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/FederationRegistrationControllerTests.java index aaefa8bb53..9cec71067f 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/FederationRegistrationControllerTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/FederationRegistrationControllerTests.java @@ -35,14 +35,10 @@ import java.util.Set; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientRelyingPartyEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -51,16 +47,20 @@ import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement; import com.nimbusds.openid.connect.sdk.federation.trust.TrustChain; +import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; -import it.infn.mw.iam.config.TaskConfig; import it.infn.mw.iam.core.oidc.TrustChainService; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.ClientRelyingPartyEntity; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.test.util.WithMockOAuthUser; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; -@ActiveProfiles({"h2-test", "dev", "openid-federation"}) -@ExtendWith(SpringExtension.class) @IamMockMvcIntegrationTest +@TestPropertySource(properties = { + "openid-federation.enabled=true", +}) +//@ContextConfiguration(initializers = JvmProfilesSupport.OpenIDFederationProfileInitializer.class) class FederationRegistrationControllerTests { private static final String IAM_OIDFED_CLIENT_REGISTRATION_ENDPOINT = @@ -78,7 +78,7 @@ class FederationRegistrationControllerTests { private IamClientRepository clientRepo; @Autowired - private TaskConfig taskConfig; + private ClientService clientService; @Value("${iam.issuer}") private String issuer; @@ -235,7 +235,7 @@ void testClientDisabledWhenExpired() throws Exception { assertEquals(0, countInactiveClients()); - taskConfig.disableExpiredClients(); + clientService.disableExpiredClients(); client = clientRepo.findByClientId("client-cred").orElseThrow(); Date lastUpdate = client.getStatusChangedOn(); @@ -248,9 +248,9 @@ void testClientDisabledWhenExpired() throws Exception { .param("client_secret", "secret")) .andExpect(status().isUnauthorized()) .andExpect(jsonPath("$.error", equalTo("invalid_client"))) - .andExpect(jsonPath("$.error_description", equalTo("Client is suspended: client-cred"))); + .andExpect(jsonPath("$.error_description", equalTo("Client client-cred is not active"))); - taskConfig.disableExpiredClients(); + clientService.disableExpiredClients(); client = clientRepo.findByClientId("client-cred").orElseThrow(); // check that the client has been disabled only once diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/TrustChainServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/TrustChainServiceTests.java index f4c4327ec2..e92dc09ae0 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/TrustChainServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/openid_federation/TrustChainServiceTests.java @@ -49,7 +49,7 @@ import it.infn.mw.iam.core.oidc.TrustChainService; import it.infn.mw.iam.core.oidc.TrustChainValidator; -@ActiveProfiles({"h2-test", "dev", "openid-federation"}) +@ActiveProfiles({"h2-test", "openid-federation"}) @ExtendWith(MockitoExtension.class) class TrustChainServiceTests { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestConfig.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestConfig.java index 895ffad2e4..42b04d9b27 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestConfig.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestConfig.java @@ -21,21 +21,22 @@ import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; -import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.config.ServerConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; +import it.infn.mw.iam.core.jwt.IamJwtSigningAndValidationService; +import it.infn.mw.iam.core.jwt.JwkSetCacheService; +import it.infn.mw.iam.core.jwt.JwtSigningAndValidationService; @Configuration -public class RCAuthTestConfig extends RCAuthTestSupport { +class RCAuthTestConfig extends RCAuthTestSupport { @Bean @Primary - public ServerConfigurationService serverConfigService() { + ServerConfigurationService serverConfigService() { ServerConfigurationService scs = mock(ServerConfigurationService.class); ServerConfiguration sc = mock(ServerConfiguration.class); @@ -49,12 +50,12 @@ public ServerConfigurationService serverConfigService() { @Bean @Primary - public JWKSetCacheService mockjwkSetCacheService() + JwkSetCacheService mockjwkSetCacheService() throws NoSuchAlgorithmException, InvalidKeySpecException { - IamJWTSigningService signatureValidator = new IamJWTSigningService(rcAuthKeyStore()); + JwtSigningAndValidationService signatureValidator = new IamJwtSigningAndValidationService(rcAuthKeyStore()); - JWKSetCacheService mockCacheService = mock(JWKSetCacheService.class); + JwkSetCacheService mockCacheService = mock(JwkSetCacheService.class); when(mockCacheService.getValidator(JWK_URI)).thenReturn(signatureValidator); return mockCacheService; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestSupport.java index 1fcfbfc145..a60079efb2 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RCAuthTestSupport.java @@ -15,12 +15,12 @@ */ package it.infn.mw.iam.test.rcauth; -import org.mitre.jose.keystore.JWKSetKeyStore; import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; import com.nimbusds.jose.JWSAlgorithm; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; import it.infn.mw.iam.test.ext_authn.x509.X509TestSupport; import it.infn.mw.iam.test.util.oidc.IdTokenBuilder; @@ -63,15 +63,13 @@ public class RCAuthTestSupport extends X509TestSupport { public static final MediaType APPLICATION_FORM_URLENCODED_UTF8 = MediaType.valueOf(APPLICATION_FORM_URLENCODED_UTF8_VALUE); - protected JWKSetKeyStore rcAuthKeyStore = rcAuthKeyStore(); + protected JwkSetKeyStore rcAuthKeyStore = rcAuthKeyStore(); protected JWSAlgorithm jwsAlgo = JWSAlgorithm.RS256; protected IdTokenBuilder tokenBuilder = new IdTokenBuilder(rcAuthKeyStore, jwsAlgo); - public JWKSetKeyStore rcAuthKeyStore() { - JWKSetKeyStore ks = new JWKSetKeyStore(); - ks.setLocation(new ClassPathResource("/oidc/mock_op_keys.jks")); - return ks; + public JwkSetKeyStore rcAuthKeyStore() { + return new JwkSetKeyStore(new ClassPathResource("/oidc/mock_op_keys.jks")); } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RequestServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RequestServiceTests.java index d56c9c1f10..6232c7c836 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RequestServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/rcauth/RequestServiceTests.java @@ -44,8 +44,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.openid.connect.client.service.ServerConfigurationService; -import org.mitre.openid.connect.config.ServerConfiguration; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -56,6 +54,8 @@ import com.nimbusds.jose.JOSEException; +import it.infn.mw.iam.authn.oidc.configuration.ServerConfigurationService; +import it.infn.mw.iam.authn.oidc.model.ServerConfiguration; import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.rcauth.DefaultRcAuthRequestService; import it.infn.mw.iam.rcauth.RCAuthAuthorizationResponse; diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamTokenRepositoryTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamTokenRepositoryTests.java index 5c59db2b98..4783916bc6 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamTokenRepositoryTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamTokenRepositoryTests.java @@ -15,10 +15,9 @@ */ package it.infn.mw.iam.test.repository; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -31,19 +30,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; import it.infn.mw.iam.core.IamTokenService; +import it.infn.mw.iam.core.client.IamClientDetailsService; +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; +import it.infn.mw.iam.persistence.repository.IamAuthenticationHolderRepository; import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository; import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository; import it.infn.mw.iam.test.util.annotation.IamNoMvcTest; @@ -52,6 +52,7 @@ @SuppressWarnings("deprecation") @ExtendWith(SpringExtension.class) @IamNoMvcTest +@Transactional public class IamTokenRepositoryTests { public static final String TEST_347_USER = "test_347"; @@ -63,24 +64,24 @@ public class IamTokenRepositoryTests { public static final String[] SCOPES = {"openid", "profile", "offline_access", "iam:admin.read"}; @Autowired - private IamOAuthAccessTokenRepository accessTokenRepo; + private IamOAuthAccessTokenRepository atRepo; @Autowired - private IamOAuthRefreshTokenRepository refreshTokenRepo; + private IamOAuthRefreshTokenRepository rtRepo; @Autowired - private AuthenticationHolderRepository authenticationHolderRepo; + private IamAuthenticationHolderRepository ahRepo; @Autowired - private ClientDetailsEntityService clientDetailsService; + private IamClientDetailsService clientDetailsService; @Autowired private IamTokenService tokenService; @BeforeEach void setup() { - accessTokenRepo.deleteAll(); - refreshTokenRepo.deleteAll(); + atRepo.deleteAll(); + rtRepo.deleteAll(); } private OAuth2Authentication oauth2Authentication(ClientDetailsEntity client, String username) { @@ -119,16 +120,10 @@ void testTokenResolutionCorrectlyEnforcesUsernameChecks() { buildAccessToken(loadTestClient(), TEST_347_USER); Date currentTimestamp = new Date(); - assertThat(accessTokenRepo.findValidAccessTokensForUser(TEST_346_USER, currentTimestamp), - hasSize(0)); - assertThat(refreshTokenRepo.findValidRefreshTokensForUser(TEST_346_USER, currentTimestamp), - hasSize(0)); - - assertThat(accessTokenRepo.findValidAccessTokensForUser(TEST_347_USER, currentTimestamp), - hasSize(1)); - - assertThat(refreshTokenRepo.findValidRefreshTokensForUser(TEST_347_USER, currentTimestamp), - hasSize(1)); + assertThat(atRepo.findValidAccessTokensForUser(TEST_346_USER, currentTimestamp), hasSize(0)); + assertThat(rtRepo.findValidRefreshTokensForUser(TEST_346_USER, currentTimestamp), hasSize(0)); + assertThat(atRepo.findValidAccessTokensForUser(TEST_347_USER, currentTimestamp), hasSize(1)); + assertThat(rtRepo.findValidRefreshTokensForUser(TEST_347_USER, currentTimestamp), hasSize(1)); } @Test @@ -145,16 +140,14 @@ void testExpiredTokensAreNotReturned() { at.setExpiration(yesterday); at.getRefreshToken().setExpiration(yesterday); - - tokenService.saveAccessToken(at); - tokenService.saveRefreshToken(at.getRefreshToken()); + + atRepo.save(at); + rtRepo.save(at.getRefreshToken()); Date currentTimestamp = new Date(); - assertThat(accessTokenRepo.findValidAccessTokensForUser(TEST_347_USER, currentTimestamp), - hasSize(0)); - assertThat(refreshTokenRepo.findValidRefreshTokensForUser(TEST_347_USER, currentTimestamp), - hasSize(0)); + assertThat(atRepo.findValidAccessTokensForUser(TEST_347_USER, currentTimestamp), hasSize(0)); + assertThat(rtRepo.findValidRefreshTokensForUser(TEST_347_USER, currentTimestamp), hasSize(0)); } @Test @@ -162,10 +155,8 @@ void testClientTokensNotBoundToUsersAreIgnored() { buildAccessToken(loadTestClient()); Date currentTimestamp = new Date(); - assertThat(accessTokenRepo.findValidAccessTokensForUser(TEST_347_USER, currentTimestamp), - hasSize(0)); - assertThat(refreshTokenRepo.findValidRefreshTokensForUser(TEST_347_USER, currentTimestamp), - hasSize(0)); + assertThat(atRepo.findValidAccessTokensForUser(TEST_347_USER, currentTimestamp), hasSize(0)); + assertThat(rtRepo.findValidRefreshTokensForUser(TEST_347_USER, currentTimestamp), hasSize(0)); } @Test @@ -178,61 +169,88 @@ void testRepositoryDoesntRelyOnDbTime() { at.setExpiration(exp); at.getRefreshToken().setExpiration(exp); - assertThat(accessTokenRepo.findValidAccessTokensForUser(TEST_347_USER, now), hasSize(1)); - assertThat(refreshTokenRepo.findValidRefreshTokensForUser(TEST_347_USER, now), hasSize(1)); + assertThat(atRepo.findValidAccessTokensForUser(TEST_347_USER, now), hasSize(1)); + assertThat(rtRepo.findValidRefreshTokensForUser(TEST_347_USER, now), hasSize(1)); } @Test void testTokenNoCascadeDeletion() { + OAuth2AccessTokenEntity at = buildAccessToken(loadTestClient(), TEST_347_USER); OAuth2RefreshTokenEntity rt = at.getRefreshToken(); AuthenticationHolderEntity ah = at.getAuthenticationHolder(); - accessTokenRepo.delete(at); - assertThat(refreshTokenRepo.findById(rt.getId()).isEmpty(), is(false)); - assertThat(authenticationHolderRepo.getById(ah.getId()) != null, is(true)); - refreshTokenRepo.delete(rt); - assertThat(refreshTokenRepo.findById(rt.getId()).isEmpty(), is(true)); - assertThat(authenticationHolderRepo.getById(ah.getId()) != null, is(true)); - authenticationHolderRepo.remove(ah); - assertThat(authenticationHolderRepo.getById(ah.getId()) != null, is(false)); + + Long atId = at.getId(); + Long rtId = rt.getId(); + Long ahId = ah.getId(); + + assertTrue(atRepo.findById(atId).isPresent()); + + atRepo.delete(at); + + assertTrue(atRepo.findById(atId).isEmpty()); + assertTrue(rtRepo.findById(rtId).isPresent()); + assertTrue(ahRepo.findById(ahId).isPresent()); + + rtRepo.deleteById(rtId); + + assertTrue(rtRepo.findById(rtId).isEmpty()); + assertTrue(ahRepo.findById(ahId).isPresent()); + + ahRepo.deleteById(ahId); + + assertTrue(ahRepo.findById(ah.getId()).isEmpty()); } @Test void testTokenCascadeDeletion() { + OAuth2AccessTokenEntity at = buildAccessToken(loadTestClient(), TEST_347_USER); - accessTokenRepo.save(at); OAuth2RefreshTokenEntity rt = at.getRefreshToken(); - refreshTokenRepo.save(rt); - AuthenticationHolderEntity ah = at.getAuthenticationHolder(); - authenticationHolderRepo.save(ah); - assertThat(accessTokenRepo.findAll()).hasSize(1); - assertThat(refreshTokenRepo.findAll()).hasSize(1); - assertThat(authenticationHolderRepo.getById(ah.getId()) != null, is(true)); - authenticationHolderRepo.remove(ah); - assertThat(accessTokenRepo.findAll()).isEmpty(); - assertThat(refreshTokenRepo.findAll()).isEmpty(); - assertThat(authenticationHolderRepo.getById(ah.getId()) != null, is(false)); + AuthenticationHolderEntity aht = at.getAuthenticationHolder(); + AuthenticationHolderEntity ahr = at.getAuthenticationHolder(); + + rtRepo.save(rt); // on cascade also AuthenticationHolders and Access Token are saved + + assertTrue(atRepo.findById(at.getId()).isPresent()); + assertTrue(rtRepo.findById(rt.getId()).isPresent()); + assertTrue(ahRepo.findById(aht.getId()).isPresent()); + assertTrue(ahRepo.findById(ahr.getId()).isPresent()); + assertEquals(aht.getId(), ahr.getId()); + + rtRepo.deleteById(rt.getId()); + rtRepo.flush(); + + assertTrue(atRepo.findById(at.getId()).isEmpty()); + assertTrue(rtRepo.findById(rt.getId()).isEmpty()); + assertTrue(ahRepo.findById(aht.getId()).isPresent()); + assertTrue(ahRepo.findById(ahr.getId()).isPresent()); + + ahRepo.delete(ahr); + + assertTrue(rtRepo.findById(rt.getId()).isEmpty()); + assertTrue(atRepo.findById(at.getId()).isEmpty()); + } @Test void testAuthenticationHolderScopesLinkedToAccessAndRefreshTokens() { + OAuth2AccessTokenEntity at = buildAccessToken(loadTestClient(), TEST_347_USER); - accessTokenRepo.save(at); OAuth2RefreshTokenEntity rt = at.getRefreshToken(); - refreshTokenRepo.save(rt); AuthenticationHolderEntity aht = at.getAuthenticationHolder(); - authenticationHolderRepo.save(aht); - assertTrue(authenticationHolderRepo.getById(aht.getId()).getScope().contains("openid")); - assertTrue(authenticationHolderRepo.getById(aht.getId()).getScope().contains("profile")); - assertTrue(authenticationHolderRepo.getById(aht.getId()).getScope().contains("offline_access")); - assertFalse( - authenticationHolderRepo.getById(aht.getId()).getScope().contains("iam:admin.read")); AuthenticationHolderEntity ahr = rt.getAuthenticationHolder(); - authenticationHolderRepo.save(ahr); - assertTrue(authenticationHolderRepo.getById(ahr.getId()).getScope().contains("openid")); - assertTrue(authenticationHolderRepo.getById(ahr.getId()).getScope().contains("profile")); - assertTrue(authenticationHolderRepo.getById(ahr.getId()).getScope().contains("offline_access")); - assertFalse( - authenticationHolderRepo.getById(ahr.getId()).getScope().contains("iam:admin.read")); + + ahRepo.save(aht); // on cascade also Access Tokens and Refresh Tokens are saved + + assertTrue(ahRepo.getById(aht.getId()).getScope().contains("openid")); + assertTrue(ahRepo.getById(aht.getId()).getScope().contains("profile")); + assertTrue(ahRepo.getById(aht.getId()).getScope().contains("offline_access")); + assertFalse(ahRepo.getById(aht.getId()).getScope().contains("iam:admin.read")); + + assertTrue(ahRepo.getById(ahr.getId()).getScope().contains("openid")); + assertTrue(ahRepo.getById(ahr.getId()).getScope().contains("profile")); + assertTrue(ahRepo.getById(ahr.getId()).getScope().contains("offline_access")); + assertFalse(ahRepo.getById(ahr.getId()).getScope().contains("iam:admin.read")); } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/client/ClientRepositoryTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/client/ClientRepositoryTests.java index d3aa71cf41..c0bc8b4f35 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/client/ClientRepositoryTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/client/ClientRepositoryTests.java @@ -37,8 +37,6 @@ import javax.persistence.EntityManager; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.repository.OAuth2ClientRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -47,18 +45,22 @@ import com.google.common.collect.Sets; import it.infn.mw.iam.api.client.service.ClientDefaultsService; +import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.core.user.IamAccountService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; import it.infn.mw.iam.persistence.repository.client.ClientSpecs; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.util.annotation.IamNoMvcTest; @IamNoMvcTest class ClientRepositoryTests extends ClientRepositoryTestsSupport { + @Autowired + private ClientService clientService; @Autowired private IamClientRepository clientRepo; @@ -72,9 +74,6 @@ class ClientRepositoryTests extends ClientRepositoryTestsSupport { @Autowired private IamAccountService accountService; - @Autowired - private OAuth2ClientRepository mitreClientRepo; - @Autowired private Clock clock; @@ -263,7 +262,7 @@ void clientDeletionHandledGracefully() { assertThat(test001Clients.getSize(), is(1)); assertThat(test001Clients.getContent(), hasItem(testClient)); - mitreClientRepo.deleteClient(testClient); + clientService.deleteClient(testClient); assertThat(accountClientRepo.findClientByAccount(testAccount, Pageable.unpaged()).isEmpty(), is(true)); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java index eae04eb07c..9e9beb64b9 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java @@ -76,12 +76,12 @@ import it.infn.mw.iam.persistence.model.IamSamlId; import it.infn.mw.iam.persistence.model.IamSshKey; import it.infn.mw.iam.persistence.model.IamX509Certificate; +import it.infn.mw.iam.persistence.repository.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamAupSignatureRepository; import it.infn.mw.iam.persistence.repository.IamAuthoritiesRepository; import it.infn.mw.iam.persistence.repository.IamGroupRepository; import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository; -import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; import it.infn.mw.iam.registration.TokenGenerator; @ExtendWith(MockitoExtension.class) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java index 2dde17b2bf..3a1de9cba6 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java @@ -42,8 +42,8 @@ import javax.validation.ConstraintViolationException; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -53,6 +53,7 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.transaction.annotation.Transactional; import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.api.client.management.service.ClientManagementService; @@ -64,6 +65,7 @@ import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.scim.model.ScimUser; import it.infn.mw.iam.authn.util.Authorities; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.test.util.annotation.IamNoMvcTest; @@ -71,6 +73,7 @@ @IamNoMvcTest @SpringBootTest(classes = {IamLoginService.class, ClientTestConfig.class}, webEnvironment = WebEnvironment.NONE) +@Transactional class ClientManagementServiceTests { @Autowired @@ -90,6 +93,11 @@ class ClientManagementServiceTests { private Authentication userAuth; + @BeforeAll + static void cleanupTokens() { + + } + @Test void testPagedClientLookup() { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientRegistrationServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientRegistrationServiceTests.java index 3263dc7845..e8189034a5 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientRegistrationServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientRegistrationServiceTests.java @@ -49,9 +49,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.service.BlacklistedSiteService; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -65,7 +62,7 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; import com.mercateo.test.clock.TestClock; @@ -81,17 +78,21 @@ import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties; import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientDefaultsProperties; import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy; +import it.infn.mw.iam.core.oauth.BlacklistedSiteService; import it.infn.mw.iam.core.oauth.granters.TokenExchangeTokenGranter; +import it.infn.mw.iam.core.oauth.scope.SystemScopeService; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamAccountRepository; -import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.persistence.repository.IamClientRepository; +import it.infn.mw.iam.test.util.JvmProfilesSupport; import it.infn.mw.iam.test.util.annotation.IamNoMvcTest; @SuppressWarnings("deprecation") @IamNoMvcTest @SpringBootTest(classes = {IamLoginService.class, ClientTestConfig.class}, webEnvironment = WebEnvironment.NONE) -@ActiveProfiles({"h2", "wlcg-scopes"}) +//@ContextConfiguration(initializers = JvmProfilesSupport.WlcgScopesProfileInitializer.class) class ClientRegistrationServiceTests { @Autowired diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/ApplicationStartupSuccessTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/ApplicationStartupSuccessTests.java index f5d0c50a82..24c257af1a 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/ApplicationStartupSuccessTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/ApplicationStartupSuccessTests.java @@ -28,7 +28,6 @@ import it.infn.mw.iam.IamLoginService; @SuppressWarnings("deprecation") -@ActiveProfiles({"h2"}) @SpringBootTest(classes = {IamLoginService.class}) @TestPropertySource(properties = {"spring.main.allow-bean-definition-overriding=false"}) class ApplicationStartupSuccessTests { diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/DuplicateBeanConfig.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/DuplicateBeanConfig.java index 88558b01f0..ff5235509a 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/DuplicateBeanConfig.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/startup/DuplicateBeanConfig.java @@ -15,15 +15,45 @@ */ package it.infn.mw.iam.test.startup; -import org.mitre.openid.connect.token.TofuUserApprovalHandler; +import java.util.Map; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; @SuppressWarnings("deprecation") @Configuration public class DuplicateBeanConfig { + class TofuUserApprovalHandler implements UserApprovalHandler { + + @Override + public boolean isApproved(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return true; + } + + @Override + public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return null; + } + + @Override + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return null; + } + + @Override + public Map getUserApprovalRequest(AuthorizationRequest authorizationRequest, + Authentication userAuthentication) { + return null; + } + } + @Bean UserApprovalHandler userApprovalHandler2() { return new TofuUserApprovalHandler(); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/JvmProfilesSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/JvmProfilesSupport.java new file mode 100644 index 0000000000..cb240f4a1c --- /dev/null +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/JvmProfilesSupport.java @@ -0,0 +1,88 @@ +/** + * 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.test.util; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; + +public class JvmProfilesSupport { +// +// public static class OidcProfileInitializer implements ApplicationContextInitializer { +// @Override +// public void initialize(ConfigurableApplicationContext context) { +// String[] existingProfiles = context.getEnvironment().getActiveProfiles(); +// String[] newProfiles = Arrays.copyOf(existingProfiles, existingProfiles.length + 1); +// newProfiles[newProfiles.length - 1] = "oidc"; +// context.getEnvironment().setActiveProfiles(newProfiles); +// } +// } +// +// public static class MfaProfileInitializer implements ApplicationContextInitializer { +// @Override +// public void initialize(ConfigurableApplicationContext context) { +// String[] existingProfiles = context.getEnvironment().getActiveProfiles(); +// String[] newProfiles = Arrays.copyOf(existingProfiles, existingProfiles.length + 1); +// newProfiles[newProfiles.length - 1] = "mfa"; +// context.getEnvironment().setActiveProfiles(newProfiles); +// } +// } +// +// public static class WlcgScopesProfileInitializer implements ApplicationContextInitializer { +// @Override +// public void initialize(ConfigurableApplicationContext context) { +// String[] existingProfiles = context.getEnvironment().getActiveProfiles(); +// String[] newProfiles = Arrays.copyOf(existingProfiles, existingProfiles.length + 1); +// newProfiles[newProfiles.length - 1] = "wlcg-scopes"; +// context.getEnvironment().setActiveProfiles(newProfiles); +// } +// } +// +// public static class SamlProfileInitializer implements ApplicationContextInitializer { +// @Override +// public void initialize(ConfigurableApplicationContext context) { +// String[] existingProfiles = context.getEnvironment().getActiveProfiles(); +// String[] newProfiles = Arrays.copyOf(existingProfiles, existingProfiles.length + 1); +// newProfiles[newProfiles.length - 1] = "saml"; +// context.getEnvironment().setActiveProfiles(newProfiles); +// } +// } +// +// public static class RegistrationProfileInitializer implements ApplicationContextInitializer { +// @Override +// public void initialize(ConfigurableApplicationContext context) { +// String[] existingProfiles = context.getEnvironment().getActiveProfiles(); +// String[] newProfiles = Arrays.copyOf(existingProfiles, existingProfiles.length + 1); +// newProfiles[newProfiles.length - 1] = "registration"; +// context.getEnvironment().setActiveProfiles(newProfiles); +// } +// } +// +// public static class OpenIDFederationProfileInitializer implements ApplicationContextInitializer { +// @Override +// public void initialize(ConfigurableApplicationContext context) { +// String[] currentProfiles = Stream +// .concat(Arrays.stream(context.getEnvironment().getDefaultProfiles()), Arrays.stream(context.getEnvironment().getActiveProfiles())) +// .toArray(String[]::new); +//// String[] existingProfiles = context.getEnvironment().getDefaultProfiles(); +// String[] newProfiles = Arrays.copyOf(currentProfiles, currentProfiles.length + 1); +// newProfiles[newProfiles.length - 1] = "openid-federation"; +// context.getEnvironment().setActiveProfiles(newProfiles); +// } +// } +} diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oauth/WithMockOAuth2SecurityContextFactory.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oauth/WithMockOAuth2SecurityContextFactory.java index a625c40760..f3fa120748 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oauth/WithMockOAuth2SecurityContextFactory.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oauth/WithMockOAuth2SecurityContextFactory.java @@ -15,9 +15,9 @@ */ package it.infn.mw.iam.test.util.oauth; +import java.util.HashSet; import java.util.Map; -import org.mitre.oauth2.model.SavedUserAuthentication; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -34,6 +34,7 @@ import it.infn.mw.iam.authn.ExternalAuthenticationRegistrationInfo.ExternalAuthenticationType; import it.infn.mw.iam.authn.oidc.OidcExternalAuthenticationToken; import it.infn.mw.iam.authn.saml.SamlExternalAuthenticationToken; +import it.infn.mw.iam.persistence.model.SavedUserAuthentication; import it.infn.mw.iam.test.util.WithMockOAuthUser; @SuppressWarnings("deprecation") @@ -52,7 +53,8 @@ private Authentication buildExternalUserAuthentication(WithMockOAuthUser annotat SavedUserAuthentication userAuth = new SavedUserAuthentication(); userAuth.setAuthenticated(true); - userAuth.setAuthorities(AuthorityUtils.createAuthorityList(annotation.authorities())); + userAuth + .setAuthorities(new HashSet<>(AuthorityUtils.createAuthorityList(annotation.authorities()))); userAuth.setName(annotation.user()); Map additionalInfo = Maps.newHashMap(); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/IdTokenBuilder.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/IdTokenBuilder.java index 3927aff440..2c2e1d71a0 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/IdTokenBuilder.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/IdTokenBuilder.java @@ -24,8 +24,6 @@ import java.util.Map; import java.util.UUID; -import org.mitre.jose.keystore.JWKSetKeyStore; - import com.google.common.base.Strings; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; @@ -37,6 +35,8 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; + public class IdTokenBuilder { String issuer; @@ -50,13 +50,13 @@ public class IdTokenBuilder { String jwtId; String nonce; - final JWKSetKeyStore keyStore; + final JwkSetKeyStore keyStore; final JWSAlgorithm signingAlgo; JWSSigner signer; Map customClaims = newHashMap(); - public IdTokenBuilder(JWKSetKeyStore keyStore, JWSAlgorithm algo) { + public IdTokenBuilder(JwkSetKeyStore keyStore, JWSAlgorithm algo) { Calendar cal = Calendar.getInstance(); issueTime = cal.getTime(); cal.add(Calendar.HOUR, 1); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/MockOIDCProvider.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/MockOIDCProvider.java index 2721fee71b..81ceb98774 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/MockOIDCProvider.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/util/oidc/MockOIDCProvider.java @@ -20,7 +20,6 @@ import java.util.Map; import java.util.UUID; -import org.mitre.jose.keystore.JWKSetKeyStore; import org.springframework.util.MultiValueMap; import com.fasterxml.jackson.core.JsonProcessingException; @@ -29,14 +28,15 @@ import com.nimbusds.jose.JWSAlgorithm; import it.infn.mw.iam.authn.oidc.OidcClientError; -import it.infn.mw.iam.authn.oidc.OidcClientFilter.OidcProviderConfiguration; +import it.infn.mw.iam.authn.oidc.OidcProviderConfiguration; import it.infn.mw.iam.authn.oidc.OidcTokenRequestor; import it.infn.mw.iam.authn.oidc.model.TokenEndpointErrorResponse; +import it.infn.mw.iam.core.jwt.JwkSetKeyStore; import it.infn.mw.iam.test.ext_authn.oidc.OidcTestConfig; public class MockOIDCProvider implements OidcTokenRequestor { - private JWKSetKeyStore keyStore; + private JwkSetKeyStore keyStore; private JWSAlgorithm signingAlgo = JWSAlgorithm.RS256; private final ObjectMapper mapper; @@ -46,7 +46,7 @@ public class MockOIDCProvider implements OidcTokenRequestor { private OidcClientError clientError; - public MockOIDCProvider(ObjectMapper mapper, JWKSetKeyStore keyStore) { + public MockOIDCProvider(ObjectMapper mapper, JwkSetKeyStore keyStore) { this.keyStore = keyStore; this.mapper = mapper; } diff --git a/iam-persistence/pom.xml b/iam-persistence/pom.xml index c29bc15064..b8d082ce9e 100644 --- a/iam-persistence/pom.xml +++ b/iam-persistence/pom.xml @@ -54,6 +54,11 @@ spring-boot-starter-security + + org.springframework.security.oauth + spring-security-oauth2 + + org.springframework.boot spring-boot-starter-test @@ -113,6 +118,12 @@ jackson-datatype-joda + + + com.nimbusds + nimbus-jose-jwt + + diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AppType.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AppType.java new file mode 100644 index 0000000000..d80ccb8947 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AppType.java @@ -0,0 +1,46 @@ +/** + * 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.persistence.model; + +import java.util.HashMap; +import java.util.Map; + +public enum AppType { + + WEB("web"), + NATIVE("native"); + + private final String value; + + private static final Map lookup = new HashMap<>(); + static { + for (AppType a : AppType.values()) { + lookup.put(a.getValue(), a); + } + } + + AppType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static AppType getByValue(String value) { + return lookup.get(value); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ApprovedSite.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ApprovedSite.java new file mode 100644 index 0000000000..6f7196d45d --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ApprovedSite.java @@ -0,0 +1,142 @@ +/** + * 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.persistence.model; + +import java.io.Serializable; +import java.util.Date; +import java.util.Set; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; + +@Entity +@Table(name = "approved_site") +public class ApprovedSite implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", referencedColumnName = "username") + private IamAccount account; + + @ManyToOne + @JoinColumn(name = "client_id", referencedColumnName = "client_id") + private ClientDetailsEntity client; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "creation_date") + private Date creationDate; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "access_date") + private Date accessDate; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "timeout_date") + private Date timeoutDate; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "approved_site_scope", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "scope") + private Set allowedScopes; + + public ApprovedSite() { + + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public IamAccount getAccount() { + return account; + } + + public void setAccount(IamAccount account) { + this.account = account; + } + + public ClientDetailsEntity getClient() { + return client; + } + + public void setClient(ClientDetailsEntity client) { + this.client = client; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Date getAccessDate() { + return accessDate; + } + + public void setAccessDate(Date accessDate) { + this.accessDate = accessDate; + } + + public Set getAllowedScopes() { + return allowedScopes; + } + + public void setAllowedScopes(Set allowedScopes) { + this.allowedScopes = allowedScopes; + } + + public Date getTimeoutDate() { + return timeoutDate; + } + + public void setTimeoutDate(Date timeoutDate) { + this.timeoutDate = timeoutDate; + } + + @Transient + public boolean isExpired() { + if (timeoutDate != null) { + return timeoutDate.before(new Date()); + } + return false; + } + +} + diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthMethod.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthMethod.java new file mode 100644 index 0000000000..0ebb54b3e0 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthMethod.java @@ -0,0 +1,49 @@ +/** + * 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.persistence.model; + +import java.util.HashMap; +import java.util.Map; + +public enum AuthMethod { + + SECRET_POST("client_secret_post"), + SECRET_BASIC("client_secret_basic"), + SECRET_JWT("client_secret_jwt"), + PRIVATE_KEY("private_key_jwt"), + NONE("none"); + + private final String value; + + private static final Map lookup = new HashMap<>(); + static { + for (AuthMethod a : AuthMethod.values()) { + lookup.put(a.getValue(), a); + } + } + + AuthMethod(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static AuthMethod getByValue(String value) { + return lookup.get(value); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationExtensionId.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationExtensionId.java new file mode 100644 index 0000000000..6bbc843cc0 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationExtensionId.java @@ -0,0 +1,56 @@ +/** + * 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.persistence.model; + +import java.io.Serializable; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Embeddable; + +@Embeddable +public class AuthenticationExtensionId implements Serializable { + + private static final long serialVersionUID = 1L; + + @Column(name = "owner_id") + private Long ownerId; + + @Column(name = "extension") + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof AuthenticationExtensionId that)) + return false; + return Objects.equals(ownerId, that.ownerId) && Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(ownerId, key); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationHolderEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationHolderEntity.java new file mode 100644 index 0000000000..6668c4b737 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationHolderEntity.java @@ -0,0 +1,314 @@ +/** + * 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.persistence.model; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import it.infn.mw.iam.persistence.model.convert.SimpleGrantedAuthorityStringConverter; + +@SuppressWarnings("deprecation") +@Entity +@Table(name = "authentication_holder") +public class AuthenticationHolderEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "user_auth_id") + private SavedUserAuthentication userAuth; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "authentication_holder_authority", + joinColumns = @JoinColumn(name = "owner_id")) + @Convert(converter = SimpleGrantedAuthorityStringConverter.class) + @Column(name = "authority") + private Collection authorities; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "authentication_holder_resource_id", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "resource_id") + private Set resourceIds; + + @Column(name = "approved") + private boolean approved; + + @Column(name = "redirect_uri") + private String redirectUri; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "authentication_holder_response_type", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "response_type") + private Set responseTypes; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "authentication_holder_scope", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "scope") + private Set scope; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "authentication_holder_request_parameter", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "val") + @MapKeyColumn(name = "param") + private Map requestParameters; + + @ManyToOne(fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "client_id", referencedColumnName = "client_id", nullable = false) + private ClientDetailsEntity client; + + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true) + private Set extensions; + + @OneToMany(mappedBy = "authenticationHolder", cascade = CascadeType.ALL, orphanRemoval = true) + private Set accessTokens = new HashSet<>(); + + @OneToMany(mappedBy = "authenticationHolder", cascade = CascadeType.ALL, orphanRemoval = true) + private Set refreshTokens = new HashSet<>(); + + @OneToMany(mappedBy = "authenticationHolder", cascade = CascadeType.ALL, orphanRemoval = true) + private Set authorizationCodes = new HashSet<>(); + + @OneToMany(mappedBy = "authenticationHolder", cascade = CascadeType.ALL, orphanRemoval = true) + private Set deviceCodes = new HashSet<>(); + + protected AuthenticationHolderEntity() { + + } + + public AuthenticationHolderEntity(ClientDetailsEntity client, + OAuth2Authentication authentication) { + + this.client = client; + this.accessTokens = new HashSet<>(); + this.refreshTokens = new HashSet<>(); + + OAuth2Request o2Request = authentication.getOAuth2Request(); + if (o2Request.getAuthorities() != null) { + this.authorities = new HashSet<>(o2Request.getAuthorities()); + } + + this.extensions = new HashSet<>(); + if (o2Request.getExtensions() != null) { + o2Request.getExtensions().forEach((k, v) -> { + AuthenticationHolderExtension ext = new AuthenticationHolderExtension(); + AuthenticationExtensionId id = new AuthenticationExtensionId(); + id.setKey(k); + ext.setId(id); + ext.setValue(v.toString()); + ext.setOwner(this); + this.extensions.add(ext); + }); + } + this.redirectUri = o2Request.getRedirectUri(); + this.requestParameters = new HashMap<>(); + if (o2Request.getRequestParameters() != null) { + this.requestParameters = retainValidParameters(o2Request.getRequestParameters()); + } + this.resourceIds = new HashSet<>(); + if (o2Request.getResourceIds() != null) { + this.resourceIds.addAll(o2Request.getResourceIds()); + } + this.responseTypes = new HashSet<>(); + if (o2Request.getResponseTypes() != null) { + this.responseTypes.addAll(o2Request.getResponseTypes()); + } + this.scope = new HashSet<>(); + if (o2Request.getScope() != null) { + this.scope.addAll(o2Request.getScope()); + } + this.approved = o2Request.isApproved(); + + if (authentication.getUserAuthentication() != null) { + this.userAuth = new SavedUserAuthentication(authentication.getUserAuthentication()); + } else { + this.userAuth = null; + } + } + + public Long getId() { + return id; + } + + @Transient + public OAuth2Authentication getAuthentication() { + return new OAuth2Authentication(createOAuth2Request(), getUserAuth()); + } + + private OAuth2Request createOAuth2Request() { + return new OAuth2Request(requestParameters, client.getClientId(), authorities, approved, scope, + resourceIds, redirectUri, responseTypes, getExtensionsMap()); + } + + public SavedUserAuthentication getUserAuth() { + return userAuth; + } + + public void setUserAuth(SavedUserAuthentication userAuth) { + this.userAuth = userAuth; + } + + public Collection getAuthorities() { + return authorities; + } + + public void setAuthorities(Collection authorities) { + this.authorities = authorities; + } + + public Set getResourceIds() { + return resourceIds; + } + + public void setResourceIds(Set resourceIds) { + this.resourceIds = resourceIds; + } + + public boolean isApproved() { + return approved; + } + + public void setApproved(boolean approved) { + this.approved = approved; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public Set getResponseTypes() { + return responseTypes; + } + + public void setResponseTypes(Set responseTypes) { + this.responseTypes = responseTypes; + } + + public Set getExtensions() { + return extensions; + } + + private Map getExtensionsMap() { + return extensions.stream() + .filter(e -> e.getValue() instanceof Serializable) + .collect(Collectors.toMap(AuthenticationHolderExtension::getKey, + AuthenticationHolderExtension::getValue)); + } + + public void setExtensions(Set extensions) { + this.extensions = extensions; + } + + public Set getScope() { + return scope; + } + + public void setScope(Set scope) { + this.scope = scope; + } + + public Map getRequestParameters() { + return requestParameters; + } + + private Map retainValidParameters(Map parameters) { + return parameters.entrySet() + .stream() + .filter(e -> e.getValue() != null && e.getValue().length() <= 2048) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + public void setRequestParameters(Map requestParameters) { + this.requestParameters = retainValidParameters(requestParameters); + } + + public ClientDetailsEntity getClient() { + return client; + } + + public void setClient(ClientDetailsEntity client) { + this.client = client; + } + + public Set getAccessTokens() { + return accessTokens; + } + + public void addAccessToken(OAuth2AccessTokenEntity accessToken) { + accessTokens.add(accessToken); + } + + public Set getRefreshTokens() { + return refreshTokens; + } + + public void addRefreshToken(OAuth2RefreshTokenEntity refreshToken) { + refreshTokens.add(refreshToken); + } + + public Set getAuthorizationCodes() { + return authorizationCodes; + } + + public void addAuthorizationCode(AuthorizationCodeEntity code) { + authorizationCodes.add(code); + } + + public Set getDeviceCodes() { + return deviceCodes; + } + + public void addDeviceCode(DeviceCode code) { + deviceCodes.add(code); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationHolderExtension.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationHolderExtension.java new file mode 100644 index 0000000000..6b1cbb56fc --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthenticationHolderExtension.java @@ -0,0 +1,74 @@ +/** + * 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.persistence.model; + +import javax.persistence.Column; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.Table; + +@Entity +@Table(name = "authentication_holder_extension") +public class AuthenticationHolderExtension { + + @EmbeddedId + private AuthenticationExtensionId id = new AuthenticationExtensionId(); + + @Column(name = "val", nullable = false) + private String value; + + @MapsId("ownerId") + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "owner_id", nullable = false) + private AuthenticationHolderEntity owner; + + public String getKey() { + return id.getKey(); + } + + public void setKey(String key) { + this.id.setKey(key); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public AuthenticationHolderEntity getOwner() { + return owner; + } + + public void setOwner(AuthenticationHolderEntity owner) { + this.owner = owner; + } + + public AuthenticationExtensionId getId() { + return id; + } + + public void setId(AuthenticationExtensionId id) { + this.id = id; + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthorizationCodeEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthorizationCodeEntity.java new file mode 100644 index 0000000000..294089c4bc --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/AuthorizationCodeEntity.java @@ -0,0 +1,94 @@ +/** + * 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.persistence.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@Table(name = "authorization_code") +public class AuthorizationCodeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "code") + private String code; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "auth_holder_id") + private AuthenticationHolderEntity authenticationHolder; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "expiration") + private Date expiration; + + public AuthorizationCodeEntity() { + + } + + public AuthorizationCodeEntity(String code, AuthenticationHolderEntity authenticationHolder, + Date expiration) { + this.code = code; + this.authenticationHolder = authenticationHolder; + this.expiration = expiration; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public AuthenticationHolderEntity getAuthenticationHolder() { + return authenticationHolder; + } + + public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHolder) { + this.authenticationHolder = authenticationHolder; + } + + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/BlacklistedSite.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/BlacklistedSite.java new file mode 100644 index 0000000000..1f9767525f --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/BlacklistedSite.java @@ -0,0 +1,59 @@ +/** + * 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.persistence.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "blacklisted_site") +public class BlacklistedSite { + + private Long id; + + private String uri; + + public BlacklistedSite() { + + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Basic + @Column(name = "uri") + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } +} + diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientDetailsEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientDetailsEntity.java new file mode 100644 index 0000000000..4f02ec39d9 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientDetailsEntity.java @@ -0,0 +1,884 @@ +/** + * 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.persistence.model; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.provider.ClientDetails; + +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; + +import it.infn.mw.iam.persistence.model.convert.JWEAlgorithmStringConverter; +import it.infn.mw.iam.persistence.model.convert.JWEEncryptionMethodStringConverter; +import it.infn.mw.iam.persistence.model.convert.JWKSetStringConverter; +import it.infn.mw.iam.persistence.model.convert.JWSAlgorithmStringConverter; +import it.infn.mw.iam.persistence.model.convert.JWTStringConverter; +import it.infn.mw.iam.persistence.model.convert.PKCEAlgorithmStringConverter; +import it.infn.mw.iam.persistence.model.convert.SimpleGrantedAuthorityStringConverter; + +@SuppressWarnings("deprecation") +@Entity +@Table(name = "client_details", uniqueConstraints = @UniqueConstraint(columnNames = "client_id")) +public class ClientDetailsEntity implements ClientDetails { + + private static final long serialVersionUID = -1617727085733786296L; + + private static final int DEFAULT_ID_TOKEN_VALIDITY_SECONDS = 600; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + /* + * Fields from the OAuth2 Dynamic Registration Specification + */ + @Column(name = "client_id") + private String clientId; + + @Column(name = "client_secret") + private String clientSecret; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_redirect_uri", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "redirect_uri") + private Set redirectUris; + + @Column(name = "client_name") + private String clientName; + + @Column(name = "client_uri") + private String clientUri; + + @Column(name = "logo_uri") + private String logoUri; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_contact", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "contact") + private Set contacts; + + @Column(name = "tos_uri") + private String tosUri; + + @Enumerated(EnumType.STRING) + @Column(name = "token_endpoint_auth_method") + private AuthMethod tokenEndpointAuthMethod; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_scope", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "scope") + private Set scope; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_grant_type", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "grant_type") + private Set grantTypes; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_response_type", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "response_type") + private Set responseTypes; + + @Column(name = "policy_uri") + private String policyUri; + + @Column(name = "jwks_uri") + private String jwksUri; + + @Column(name = "jwks") + @Convert(converter = JWKSetStringConverter.class) + private JWKSet jwks; + + @Column(name = "software_id") + private String softwareId; + + @Column(name = "software_version") + private String softwareVersion; + + /* + * Fields from OIDC Client Registration Specification + */ + @Enumerated(EnumType.STRING) + @Column(name = "application_type") + private AppType applicationType; + + @Column(name = "sector_identifier_uri") + private String sectorIdentifierUri; + + @Enumerated(EnumType.STRING) + @Column(name = "subject_type") + private SubjectType subjectType; + + @Column(name = "request_object_signing_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) + private JWSAlgorithm requestObjectSigningAlg; + + @Column(name = "user_info_signed_response_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) + private JWSAlgorithm userInfoSignedResponseAlg; + + @Column(name = "user_info_encrypted_response_alg") + @Convert(converter = JWEAlgorithmStringConverter.class) + private JWEAlgorithm userInfoEncryptedResponseAlg; + + @Column(name = "user_info_encrypted_response_enc") + @Convert(converter = JWEEncryptionMethodStringConverter.class) + private EncryptionMethod userInfoEncryptedResponseEnc; + + @Column(name = "id_token_signed_response_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) + private JWSAlgorithm idTokenSignedResponseAlg; + + @Column(name = "id_token_encrypted_response_alg") + @Convert(converter = JWEAlgorithmStringConverter.class) + private JWEAlgorithm idTokenEncryptedResponseAlg; + + @Column(name = "id_token_encrypted_response_enc") + @Convert(converter = JWEEncryptionMethodStringConverter.class) + private EncryptionMethod idTokenEncryptedResponseEnc; + + @Column(name = "token_endpoint_auth_signing_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) + private JWSAlgorithm tokenEndpointAuthSigningAlg; + + @Column(name = "default_max_age") + private Integer defaultMaxAge; + + @Column(name = "require_auth_time") + private Boolean requireAuthTime; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_default_acr_value", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "default_acr_value") + private Set defaultACRvalues; + + @Column(name = "initiate_login_uri") + private String initiateLoginUri; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_post_logout_redirect_uri", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "post_logout_redirect_uri") + private Set postLogoutRedirectUris; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_request_uri", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "request_uri") + private Set requestUris; + + /* + * Fields to support the ClientDetails interface + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_authority", joinColumns = @JoinColumn(name = "owner_id")) + @Convert(converter = SimpleGrantedAuthorityStringConverter.class) + @Column(name = "authority") + private Set authorities; + + @Column(name = "access_token_validity_seconds") + private Integer accessTokenValiditySeconds; + + @Column(name = "refresh_token_validity_seconds") + private Integer refreshTokenValiditySeconds; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_resource", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "resource_id") + private Set resourceIds; + + @Transient + private Map additionalInformation; + + /* + * Internal fields + */ + @Column(name = "client_description") + private String clientDescription; + + @Column(name = "reuse_refresh_tokens") + private boolean reuseRefreshToken; + + @Column(name = "dynamically_registered") + private boolean dynamicallyRegistered; + + @Column(name = "allow_introspection") + private boolean allowIntrospection; + + @Column(name = "id_token_validity_seconds") + private Integer idTokenValiditySeconds; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "clear_access_tokens_on_refresh") + private boolean clearAccessTokensOnRefresh; + + @Column(name = "device_code_validity_seconds") + private Integer deviceCodeValiditySeconds; + + @OneToOne(mappedBy = "client", cascade = CascadeType.ALL) + @PrimaryKeyJoinColumn + private ClientLastUsedEntity clientLastUsed; + + @OneToOne(mappedBy = "client", cascade = CascadeType.ALL) + @PrimaryKeyJoinColumn + private ClientRelyingPartyEntity clientRelyingParty; + + @Column(name = "active") + private boolean active; + + @Column(name = "status_changed_on") + private Date statusChangedOn; + + @Column(name = "status_changed_by") + private String statusChangedBy; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "client_claims_redirect_uri", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "redirect_uri") + private Set claimsRedirectUris; + + @Column(name = "software_statement") + @Convert(converter = JWTStringConverter.class) + private JWT softwareStatement; + + @Column(name = "code_challenge_method") + @Convert(converter = PKCEAlgorithmStringConverter.class) + private PKCEAlgorithm codeChallengeMethod; + + @OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true) + private Set authenticationHolders; + + @OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true) + private Set accessTokens; + + @OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true) + private Set refreshTokens; + + @OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true) + private Set deviceCodes; + + @Column(name = "up_scoping_enabled") + private boolean upScopingEnabled; + + public ClientDetailsEntity() { + + accessTokens = new HashSet<>(); + accessTokenValiditySeconds = 0; + active = true; + additionalInformation = new HashMap<>(); + allowIntrospection = true; + authenticationHolders = new HashSet<>(); + authorities = new HashSet<>(); + clearAccessTokensOnRefresh = false; + clientDescription = ""; + deviceCodes = new HashSet<>(); + dynamicallyRegistered = false; + grantTypes = new HashSet<>(); + redirectUris = new HashSet<>(); + refreshTokenValiditySeconds = 0; + refreshTokens = new HashSet<>(); + resourceIds = new HashSet<>(); + responseTypes = new HashSet<>(); + reuseRefreshToken = true; + scope = new HashSet<>(); + tokenEndpointAuthMethod = AuthMethod.SECRET_BASIC; + upScopingEnabled = true; + } + + @PrePersist + @PreUpdate + private void prePersist() { + // make sure that ID tokens always time out, default to 5 minutes + if (getIdTokenValiditySeconds() == null) { + setIdTokenValiditySeconds(DEFAULT_ID_TOKEN_VALIDITY_SECONDS); + } + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getClientDescription() { + return clientDescription; + } + + public void setClientDescription(String clientDescription) { + this.clientDescription = clientDescription; + } + + @Transient + public boolean isAllowRefresh() { + if (grantTypes != null && !grantTypes.isEmpty()) { + return getAuthorizedGrantTypes().contains("refresh_token"); + } + return false; + } + + public boolean isReuseRefreshToken() { + return reuseRefreshToken; + } + + public void setReuseRefreshToken(boolean reuseRefreshToken) { + this.reuseRefreshToken = reuseRefreshToken; + } + + public Integer getIdTokenValiditySeconds() { + return idTokenValiditySeconds; + } + + public void setIdTokenValiditySeconds(Integer idTokenValiditySeconds) { + this.idTokenValiditySeconds = idTokenValiditySeconds; + } + + public boolean isDynamicallyRegistered() { + return dynamicallyRegistered; + } + + public void setDynamicallyRegistered(boolean dynamicallyRegistered) { + this.dynamicallyRegistered = dynamicallyRegistered; + } + + public boolean isAllowIntrospection() { + return allowIntrospection; + } + + public void setAllowIntrospection(boolean allowIntrospection) { + this.allowIntrospection = allowIntrospection; + } + + @Override + @Transient + public boolean isSecretRequired() { + if (tokenEndpointAuthMethod == null) { + return false; + } + return AuthMethod.SECRET_BASIC.equals(tokenEndpointAuthMethod) + || AuthMethod.SECRET_POST.equals(tokenEndpointAuthMethod) + || AuthMethod.SECRET_JWT.equals(tokenEndpointAuthMethod); + } + + @Override + @Transient + public boolean isScoped() { + return scope != null && !scope.isEmpty(); + } + + @Override + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + @Override + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + @Override + public Set getScope() { + return scope; + } + + public void setScope(Set scope) { + this.scope = scope; + } + + public Set getGrantTypes() { + return grantTypes; + } + + public void setGrantTypes(Set grantTypes) { + this.grantTypes = grantTypes; + } + + @Override + @Transient + public Set getAuthorizedGrantTypes() { + return grantTypes; + } + + @Override + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public Integer getAccessTokenValiditySeconds() { + return accessTokenValiditySeconds; + } + + public void setAccessTokenValiditySeconds(Integer accessTokenValiditySeconds) { + this.accessTokenValiditySeconds = accessTokenValiditySeconds; + } + + @Override + public Integer getRefreshTokenValiditySeconds() { + return refreshTokenValiditySeconds; + } + + public void setRefreshTokenValiditySeconds(Integer refreshTokenValiditySeconds) { + this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; + } + + public Set getRedirectUris() { + return redirectUris; + } + + public void setRedirectUris(Set redirectUris) { + this.redirectUris = redirectUris; + } + + @Override + @Transient + public Set getRegisteredRedirectUri() { + return redirectUris; + } + + @Override + public Set getResourceIds() { + return resourceIds; + } + + public void setResourceIds(Set resourceIds) { + this.resourceIds = resourceIds; + } + + @Override + @Transient + public Map getAdditionalInformation() { + return this.additionalInformation; + } + + public AppType getApplicationType() { + return applicationType; + } + + public void setApplicationType(AppType applicationType) { + this.applicationType = applicationType; + } + + public String getClientName() { + return clientName; + } + + public void setClientName(String clientName) { + this.clientName = clientName; + } + + public AuthMethod getTokenEndpointAuthMethod() { + return tokenEndpointAuthMethod; + } + + public void setTokenEndpointAuthMethod(AuthMethod tokenEndpointAuthMethod) { + this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; + } + + public SubjectType getSubjectType() { + return subjectType; + } + + public void setSubjectType(SubjectType subjectType) { + this.subjectType = subjectType; + } + + public Set getContacts() { + return contacts; + } + + public void setContacts(Set contacts) { + this.contacts = contacts; + } + + public String getLogoUri() { + return logoUri; + } + + public void setLogoUri(String logoUri) { + this.logoUri = logoUri; + } + + public String getPolicyUri() { + return policyUri; + } + + public void setPolicyUri(String policyUri) { + this.policyUri = policyUri; + } + + public String getClientUri() { + return clientUri; + } + + public void setClientUri(String clientUri) { + this.clientUri = clientUri; + } + + public String getTosUri() { + return tosUri; + } + + public void setTosUri(String tosUri) { + this.tosUri = tosUri; + } + + public String getJwksUri() { + return jwksUri; + } + + public void setJwksUri(String jwksUri) { + this.jwksUri = jwksUri; + } + + public JWKSet getJwks() { + return jwks; + } + + public void setJwks(JWKSet jwks) { + this.jwks = jwks; + } + + public String getSectorIdentifierUri() { + return sectorIdentifierUri; + } + + public void setSectorIdentifierUri(String sectorIdentifierUri) { + this.sectorIdentifierUri = sectorIdentifierUri; + } + + public JWSAlgorithm getRequestObjectSigningAlg() { + return requestObjectSigningAlg; + } + + public void setRequestObjectSigningAlg(JWSAlgorithm requestObjectSigningAlg) { + this.requestObjectSigningAlg = requestObjectSigningAlg; + } + + public JWSAlgorithm getUserInfoSignedResponseAlg() { + return userInfoSignedResponseAlg; + } + + public void setUserInfoSignedResponseAlg(JWSAlgorithm userInfoSignedResponseAlg) { + this.userInfoSignedResponseAlg = userInfoSignedResponseAlg; + } + + public JWEAlgorithm getUserInfoEncryptedResponseAlg() { + return userInfoEncryptedResponseAlg; + } + + public void setUserInfoEncryptedResponseAlg(JWEAlgorithm userInfoEncryptedResponseAlg) { + this.userInfoEncryptedResponseAlg = userInfoEncryptedResponseAlg; + } + + public EncryptionMethod getUserInfoEncryptedResponseEnc() { + return userInfoEncryptedResponseEnc; + } + + public void setUserInfoEncryptedResponseEnc(EncryptionMethod userInfoEncryptedResponseEnc) { + this.userInfoEncryptedResponseEnc = userInfoEncryptedResponseEnc; + } + + public JWSAlgorithm getIdTokenSignedResponseAlg() { + return idTokenSignedResponseAlg; + } + + public void setIdTokenSignedResponseAlg(JWSAlgorithm idTokenSignedResponseAlg) { + this.idTokenSignedResponseAlg = idTokenSignedResponseAlg; + } + + public JWEAlgorithm getIdTokenEncryptedResponseAlg() { + return idTokenEncryptedResponseAlg; + } + + public void setIdTokenEncryptedResponseAlg(JWEAlgorithm idTokenEncryptedResponseAlg) { + this.idTokenEncryptedResponseAlg = idTokenEncryptedResponseAlg; + } + + public EncryptionMethod getIdTokenEncryptedResponseEnc() { + return idTokenEncryptedResponseEnc; + } + + public void setIdTokenEncryptedResponseEnc(EncryptionMethod idTokenEncryptedResponseEnc) { + this.idTokenEncryptedResponseEnc = idTokenEncryptedResponseEnc; + } + + public JWSAlgorithm getTokenEndpointAuthSigningAlg() { + return tokenEndpointAuthSigningAlg; + } + + public void setTokenEndpointAuthSigningAlg(JWSAlgorithm tokenEndpointAuthSigningAlg) { + this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlg; + } + + public Integer getDefaultMaxAge() { + return defaultMaxAge; + } + + public void setDefaultMaxAge(Integer defaultMaxAge) { + this.defaultMaxAge = defaultMaxAge; + } + + public Boolean getRequireAuthTime() { + return requireAuthTime; + } + + public void setRequireAuthTime(Boolean requireAuthTime) { + this.requireAuthTime = requireAuthTime; + } + + public Set getResponseTypes() { + return responseTypes; + } + + public void setResponseTypes(Set responseTypes) { + this.responseTypes = responseTypes; + } + + public Set getDefaultACRvalues() { + return defaultACRvalues; + } + + public void setDefaultACRvalues(Set defaultACRvalues) { + this.defaultACRvalues = defaultACRvalues; + } + + public String getInitiateLoginUri() { + return initiateLoginUri; + } + + public void setInitiateLoginUri(String initiateLoginUri) { + this.initiateLoginUri = initiateLoginUri; + } + + public Set getPostLogoutRedirectUris() { + return postLogoutRedirectUris; + } + + public void setPostLogoutRedirectUris(Set postLogoutRedirectUri) { + this.postLogoutRedirectUris = postLogoutRedirectUri; + } + + public Set getRequestUris() { + return requestUris; + } + + public void setRequestUris(Set requestUris) { + this.requestUris = requestUris; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + @Override + public boolean isAutoApprove(String scope) { + return false; + } + + public boolean isClearAccessTokensOnRefresh() { + return clearAccessTokensOnRefresh; + } + + public void setClearAccessTokensOnRefresh(boolean clearAccessTokensOnRefresh) { + this.clearAccessTokensOnRefresh = clearAccessTokensOnRefresh; + } + + public ClientLastUsedEntity getClientLastUsed() { + return clientLastUsed; + } + + public void setClientLastUsed(ClientLastUsedEntity clientLastUsed) { + this.clientLastUsed = clientLastUsed; + } + + public ClientRelyingPartyEntity getClientRelyingParty() { + return clientRelyingParty; + } + + public void setClientRelyingParty(ClientRelyingPartyEntity clientRelyingParty) { + this.clientRelyingParty = clientRelyingParty; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public Date getStatusChangedOn() { + return statusChangedOn; + } + + public void setStatusChangedOn(Date statusChangedOn) { + this.statusChangedOn = statusChangedOn; + } + + public String getStatusChangedBy() { + return statusChangedBy; + } + + public void setStatusChangedBy(String statusChangedBy) { + this.statusChangedBy = statusChangedBy; + } + + public Set getClaimsRedirectUris() { + return claimsRedirectUris; + } + + public void setClaimsRedirectUris(Set claimsRedirectUris) { + this.claimsRedirectUris = claimsRedirectUris; + } + + public JWT getSoftwareStatement() { + return softwareStatement; + } + + public void setSoftwareStatement(JWT softwareStatement) { + this.softwareStatement = softwareStatement; + } + + public PKCEAlgorithm getCodeChallengeMethod() { + return codeChallengeMethod; + } + + public void setCodeChallengeMethod(PKCEAlgorithm codeChallengeMethod) { + this.codeChallengeMethod = codeChallengeMethod; + } + + public Integer getDeviceCodeValiditySeconds() { + return deviceCodeValiditySeconds; + } + + public void setDeviceCodeValiditySeconds(Integer deviceCodeValiditySeconds) { + this.deviceCodeValiditySeconds = deviceCodeValiditySeconds; + } + + public String getSoftwareId() { + return softwareId; + } + + public void setSoftwareId(String softwareId) { + this.softwareId = softwareId; + } + + public String getSoftwareVersion() { + return softwareVersion; + } + + public void setSoftwareVersion(String softwareVersion) { + this.softwareVersion = softwareVersion; + } + + public Set getAuthenticationHolders() { + return authenticationHolders; + } + + public Set getAccessTokens() { + return accessTokens; + } + + public Set getRefreshTokens() { + return refreshTokens; + } + + public Set getDeviceCodes() { + return deviceCodes; + } + + public boolean isUpScopingEnabled() { + return upScopingEnabled; + } + + public void setUpScopingEnabled(boolean upScopingEnabled) { + this.upScopingEnabled = upScopingEnabled; + } + + @Override + public int hashCode() { + return Objects.hash(clientId, id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ClientDetailsEntity other = (ClientDetailsEntity) obj; + return Objects.equals(clientId, other.clientId) && Objects.equals(id, other.id); + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientLastUsedEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientLastUsedEntity.java new file mode 100644 index 0000000000..b4bd5de0fe --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientLastUsedEntity.java @@ -0,0 +1,76 @@ +/** + * 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.persistence.model; + +import java.time.LocalDate; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "client_last_used") +public class ClientLastUsedEntity { + + @Id + @Column(name = "client_details_id") + private Long id; + + @OneToOne + @MapsId + @JoinColumn(name = "client_details_id") + private ClientDetailsEntity client; + + @Column(name = "last_used", nullable = false) + private LocalDate lastUsed; + + public ClientLastUsedEntity() { + // empty constructor + } + + public ClientLastUsedEntity(ClientDetailsEntity client, LocalDate lastUsed) { + this.client = client; + this.lastUsed = lastUsed; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ClientDetailsEntity getClient() { + return client; + } + + public void setClient(ClientDetailsEntity client) { + this.client = client; + } + + public LocalDate getLastUsed() { + return lastUsed; + } + + public void setLastUsed(LocalDate lastUsed) { + this.lastUsed = lastUsed; + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientRelyingPartyEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientRelyingPartyEntity.java new file mode 100644 index 0000000000..07055f9562 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/ClientRelyingPartyEntity.java @@ -0,0 +1,88 @@ +/** + * 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.persistence.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "client_relying_party") +public class ClientRelyingPartyEntity { + + @Id + @Column(name = "client_details_id") + private Long id; + + @OneToOne + @MapsId + @JoinColumn(name = "client_details_id") + private ClientDetailsEntity client; + + @Column(name = "expiration", nullable = false) + private Date expiration; + + @Column(name = "entity_id", nullable = false) + private String entityId; + + public ClientRelyingPartyEntity() { + // empty constructor + } + + public ClientRelyingPartyEntity(ClientDetailsEntity client, Date expiration, String entityId) { + this.client = client; + this.expiration = expiration; + this.entityId = entityId; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ClientDetailsEntity getClient() { + return client; + } + + public void setClient(ClientDetailsEntity client) { + this.client = client; + } + + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + public String getEntityId() { + return entityId; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/DeviceCode.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/DeviceCode.java new file mode 100644 index 0000000000..9f5b5c1638 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/DeviceCode.java @@ -0,0 +1,175 @@ +/** + * 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.persistence.model; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@Table(name = "device_code") +public class DeviceCode implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "device_code") + private String deviceCode; + + @Column(name = "user_code") + private String userCode; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "device_code_scope", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "scope") + private Set scope; + + @Basic + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "expiration") + private Date expiration; + + @ManyToOne + @JoinColumn(name = "client_id", referencedColumnName = "client_id") + private ClientDetailsEntity client; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "device_code_request_parameter", + joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "val") + @MapKeyColumn(name = "param") + private Map requestParameters; + + @Column(name = "approved") + private boolean approved; + + @ManyToOne(optional = true) + @JoinColumn(name = "auth_holder_id") + private AuthenticationHolderEntity authenticationHolder; + + public DeviceCode() { + + } + + public DeviceCode(String deviceCode, String userCode, Set scope, + ClientDetailsEntity client, Map params) { + + this.deviceCode = deviceCode; + this.userCode = userCode; + this.scope = scope; + this.client = client; + this.requestParameters = params; + if (client.getDeviceCodeValiditySeconds() != null) { + this.expiration = + new Date(System.currentTimeMillis() + client.getDeviceCodeValiditySeconds() * 1000L); + } + this.approved = false; + } + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDeviceCode() { + return deviceCode; + } + + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } + + public String getUserCode() { + return userCode; + } + + public void setUserCode(String userCode) { + this.userCode = userCode; + } + + public Set getScope() { + return scope; + } + + public void setScope(Set scope) { + this.scope = scope; + } + + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + public ClientDetailsEntity getClient() { + return client; + } + + public void setClient(ClientDetailsEntity client) { + this.client = client; + } + + public Map getRequestParameters() { + return requestParameters; + } + + public void setRequestParameters(Map params) { + this.requestParameters = params; + } + + public boolean isApproved() { + return approved; + } + + public void approve() { + this.approved = true; + } + + public AuthenticationHolderEntity getAuthenticationHolder() { + return authenticationHolder; + } + + public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHolder) { + this.authenticationHolder = authenticationHolder; + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccount.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccount.java index e9a061ad30..acb69e893c 100644 --- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccount.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccount.java @@ -52,6 +52,7 @@ import org.joda.time.DateTimeComparator; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.gson.JsonObject; @Entity @Table(name = "iam_account") @@ -613,7 +614,8 @@ public void setEndTime(Date endTime) { } public boolean isValid() { - return logicalOr(isNull(endTime), DateTimeComparator.getInstance().compare(endTime, new Date()) > 0); + return logicalOr(isNull(endTime), + DateTimeComparator.getInstance().compare(endTime, new Date()) > 0); } public boolean isServiceAccount() { @@ -631,4 +633,44 @@ public boolean hasAffiliation() { public String getAffiliation() { return this.userInfo.getAffiliation(); } + + public JsonObject toJson() { + + JsonObject obj = new JsonObject(); + obj.addProperty("sub", uuid); + obj.addProperty("name", userInfo.getName()); + obj.addProperty("preferred_username", userInfo.getPreferredUsername()); + obj.addProperty("given_name", userInfo.getGivenName()); + obj.addProperty("family_name", userInfo.getFamilyName()); + obj.addProperty("middle_name", userInfo.getMiddleName()); + obj.addProperty("nickname", userInfo.getNickname()); + obj.addProperty("profile", userInfo.getProfile()); + obj.addProperty("picture", userInfo.getPicture()); + obj.addProperty("website", userInfo.getWebsite()); + obj.addProperty("gender", userInfo.getGender()); + obj.addProperty("zoneinfo", userInfo.getZoneinfo()); + obj.addProperty("locale", userInfo.getLocale()); + obj.addProperty("updated_at", userInfo.getUpdatedTime()); + obj.addProperty("birthdate", userInfo.getBirthdate()); + + obj.addProperty("email", userInfo.getEmail()); + obj.addProperty("email_verified", userInfo.getEmailVerified()); + + obj.addProperty("phone_number", userInfo.getPhoneNumber()); + obj.addProperty("phone_number_verified", userInfo.getPhoneNumberVerified()); + + if (userInfo.getAddress() != null) { + + JsonObject addr = new JsonObject(); + addr.addProperty("formatted", userInfo.getAddress().getFormatted()); + addr.addProperty("street_address", userInfo.getAddress().getStreetAddress()); + addr.addProperty("locality", userInfo.getAddress().getLocality()); + addr.addProperty("region", userInfo.getAddress().getRegion()); + addr.addProperty("postal_code", userInfo.getAddress().getPostalCode()); + addr.addProperty("country", userInfo.getAddress().getCountry()); + + obj.add("address", addr); + } + return obj; + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/model/IamAccountClient.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccountClient.java similarity index 98% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/model/IamAccountClient.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccountClient.java index 865c1b0a95..8fd5f6816a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/model/IamAccountClient.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAccountClient.java @@ -29,8 +29,6 @@ import javax.persistence.TemporalType; import javax.persistence.UniqueConstraint; -import org.mitre.oauth2.model.ClientDetailsEntity; - /** * * This entity is the login-service component as it needs to "see" the ClientDetailsEntity mitreid diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamUserInfo.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamUserInfo.java index 5bdb3f3672..a787fcf2e3 100644 --- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamUserInfo.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamUserInfo.java @@ -52,13 +52,13 @@ public class IamUserInfo implements Serializable { @OneToOne(mappedBy = "userInfo") private IamAccount iamAccount; - @Column(name="givenname", nullable = false, length = 64) + @Column(name = "givenname", nullable = false, length = 64) private String givenName; - @Column(name="familyname", nullable = false, length = 64) + @Column(name = "familyname", nullable = false, length = 64) private String familyName; - @Column(name="middlename", length = 64) + @Column(name = "middlename", length = 64) private String middleName; private String nickname; @@ -72,21 +72,21 @@ public class IamUserInfo implements Serializable { @Column(nullable = false, length = 128) private String email; - @Column(name="emailverified") + @Column(name = "emailverified") private Boolean emailVerified; private String gender; private String zoneinfo; private String locale; - - @Column(name="phonenumber") + + @Column(name = "phonenumber") private String phoneNumber; - @Column(name="phonenumberverified") + @Column(name = "phonenumberverified") private Boolean phoneNumberVerified; - - @OneToOne(optional=true, cascade=CascadeType.ALL) - @JoinColumn(name="address_id") + + @OneToOne(optional = true, cascade = CascadeType.ALL) + @JoinColumn(name = "address_id") private IamAddress address; private String birthdate; @@ -399,22 +399,17 @@ public JsonObject toJson() { } return obj; - } else { - return src; } - + return src; } - - + public String getName() { return getFormatted(this.givenName, this.middleName, this.familyName); } - - public void setName(String name) { + public void setName(String name) { // NO-OP - } public String getAffiliation() { @@ -432,4 +427,49 @@ public String toString() { + "]"; } + + public static IamUserInfo fromJson(JsonObject obj) { + + IamUserInfo ui = new IamUserInfo(); + ui.setSrc(obj); + ui.setSub(nullSafeGetString(obj, "sub")); + ui.setName(nullSafeGetString(obj, "name")); + ui.setPreferredUsername(nullSafeGetString(obj, "preferred_username")); + ui.setGivenName(nullSafeGetString(obj, "given_name")); + ui.setFamilyName(nullSafeGetString(obj, "family_name")); + ui.setMiddleName(nullSafeGetString(obj, "middle_name")); + ui.setNickname(nullSafeGetString(obj, "nickname")); + ui.setProfile(nullSafeGetString(obj, "profile")); + ui.setPicture(nullSafeGetString(obj, "picture")); + ui.setWebsite(nullSafeGetString(obj, "website")); + ui.setGender(nullSafeGetString(obj, "gender")); + ui.setZoneinfo(nullSafeGetString(obj, "zoneinfo")); + ui.setLocale(nullSafeGetString(obj, "locale")); + ui.setUpdatedTime(nullSafeGetString(obj, "updated_at")); + ui.setBirthdate(nullSafeGetString(obj, "birthdate")); + ui.setEmail(nullSafeGetString(obj, "email")); + ui.setEmailVerified(obj.has("email_verified") && obj.get("email_verified").isJsonPrimitive() + ? obj.get("email_verified").getAsBoolean() + : null); + ui.setPhoneNumber(nullSafeGetString(obj, "phone_number")); + ui.setPhoneNumberVerified( + obj.has("phone_number_verified") && obj.get("phone_number_verified").isJsonPrimitive() + ? obj.get("phone_number_verified").getAsBoolean() + : null); + if (obj.has("address") && obj.get("address").isJsonObject()) { + JsonObject addr = obj.get("address").getAsJsonObject(); + ui.setAddress(new IamAddress()); + ui.getAddress().setFormatted(nullSafeGetString(addr, "formatted")); + ui.getAddress().setStreetAddress(nullSafeGetString(addr, "street_address")); + ui.getAddress().setLocality(nullSafeGetString(addr, "locality")); + ui.getAddress().setRegion(nullSafeGetString(addr, "region")); + ui.getAddress().setPostalCode(nullSafeGetString(addr, "postal_code")); + ui.getAddress().setCountry(nullSafeGetString(addr, "country")); + } + return ui; + } + + private static String nullSafeGetString(JsonObject obj, String field) { + return obj.has(field) && obj.get(field).isJsonPrimitive() ? obj.get(field).getAsString() : null; +} } diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/OAuth2AccessTokenEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/OAuth2AccessTokenEntity.java new file mode 100644 index 0000000000..59326aa840 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/OAuth2AccessTokenEntity.java @@ -0,0 +1,328 @@ +/** + * 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.persistence.model; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; + +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer; +import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +@SuppressWarnings("deprecation") +@Entity +@Table(name = "access_token") +@JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class) +@JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) +public class OAuth2AccessTokenEntity implements OAuth2AccessToken { + + public static final String ID_TOKEN_FIELD_NAME = "id_token"; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "client_id") + private ClientDetailsEntity client; + + @ManyToOne + @JoinColumn(name = "auth_holder_id") + private AuthenticationHolderEntity authenticationHolder; + + @Column(name = "token_value", length = 4096) + private String tokenValue; + + @Transient + private JWT jwtValue; + + @Column(name = "token_value_hash", length = 64) + private String tokenValueHash; + + @Column(name = "expiration") + @Temporal(TemporalType.TIMESTAMP) + private Date expiration; + + @Column(name = "token_type", length = 256) + private String tokenType; + + @ManyToOne(optional = false) + @JoinColumn(name = "refresh_token_id") + private OAuth2RefreshTokenEntity refreshToken; + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(joinColumns = @JoinColumn(name = "owner_id"), name = "token_scope") + private Set scope; + + @ManyToOne + @JoinColumn(name = "approved_site_id") + private ApprovedSite approvedSite; + + @Transient + private Map additionalInformation; + + @Transient + private JWT idToken; + + public OAuth2AccessTokenEntity() { + + } + + public OAuth2AccessTokenEntity(ClientDetailsEntity client, + AuthenticationHolderEntity authenticationHolder) { + + this.client = client; + this.authenticationHolder = authenticationHolder; + this.additionalInformation = new HashMap<>(); + this.tokenType = OAuth2AccessToken.BEARER_TYPE; + } + + public Long getId() { + + return id; + } + + public void setId(Long id) { + + this.id = id; + } + + @Override + public Map getAdditionalInformation() { + + return additionalInformation; + } + + public AuthenticationHolderEntity getAuthenticationHolder() { + + return authenticationHolder; + } + + public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHolder) { + + this.authenticationHolder = authenticationHolder; + } + + public ClientDetailsEntity getClient() { + + return client; + } + + public void setClient(ClientDetailsEntity client) { + + this.client = client; + } + + @Override + @Transient + public String getValue() { + + return getTokenValue(); + } + + public String getTokenValue() { + + return this.tokenValue; + } + + public void setTokenValue(String tokenValue) { + + Objects.nonNull(tokenValue); + this.tokenValue = tokenValue; + this.tokenValueHash = sha256(tokenValue); + try { + this.jwtValue = JWTParser.parse(tokenValue); + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid token value: " + e.getMessage()); + } + } + + public void setTokenJwtValue(JWT jwtTokenValue) { + + Objects.nonNull(jwtTokenValue); + this.jwtValue = jwtTokenValue; + this.tokenValue = jwtTokenValue.serialize(); + this.tokenValueHash = sha256(tokenValue); + } + + public String getTokenValueHash() { + + return tokenValueHash; + } + + @Override + public Date getExpiration() { + + return expiration; + } + + public void setExpiration(Date expiration) { + + this.expiration = expiration; + } + + @Override + public String getTokenType() { + + return tokenType; + } + + public void setTokenType(String tokenType) { + + this.tokenType = tokenType; + } + + @Override + public OAuth2RefreshTokenEntity getRefreshToken() { + + return refreshToken; + } + + public void setRefreshToken(OAuth2RefreshTokenEntity refreshToken) { + + this.refreshToken = refreshToken; + } + + public void setRefreshToken(OAuth2RefreshToken refreshToken) { + + if (refreshToken instanceof OAuth2RefreshTokenEntity rt) { + setRefreshToken(rt); + } + } + + @Override + public Set getScope() { + + return scope; + } + + public void setScope(Set scope) { + + this.scope = scope; + } + + @Override + @Transient + public boolean isExpired() { + + return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime(); + } + + @Transient + public JWT getJwt() { + + if (jwtValue == null && tokenValue != null) { + try { + jwtValue = JWTParser.parse(tokenValue); + } catch (ParseException e) { + throw new IllegalStateException(e); + } + } + return jwtValue; + } + + @Override + @Transient + public int getExpiresIn() { + + if (getExpiration() == null) { + return -1; // no expiration time + } else { + int secondsRemaining = + (int) ((getExpiration().getTime() - System.currentTimeMillis()) / 1000); + if (isExpired()) { + return 0; // has an expiration time and expired + } else { // has an expiration time and not expired + return secondsRemaining; + } + } + } + + public ApprovedSite getApprovedSite() { + + return approvedSite; + } + + public void setApprovedSite(ApprovedSite approvedSite) { + + this.approvedSite = approvedSite; + } + + @Transient + public String getIdToken() { + + if (additionalInformation.containsKey(ID_TOKEN_FIELD_NAME)) { + return additionalInformation.get(ID_TOKEN_FIELD_NAME).toString(); + } + return null; + } + + public void setIdToken(JWT idToken) { + + if (idToken == null) { + return; + } + additionalInformation.put(ID_TOKEN_FIELD_NAME, idToken.serialize()); + } + + public static String sha256(String tokenString) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(tokenString.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} + diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/OAuth2RefreshTokenEntity.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/OAuth2RefreshTokenEntity.java new file mode 100644 index 0000000000..01946b6fae --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/OAuth2RefreshTokenEntity.java @@ -0,0 +1,195 @@ +/** + * 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.persistence.model; + +import java.text.ParseException; +import java.util.Date; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; + +import org.springframework.security.oauth2.common.OAuth2RefreshToken; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +@SuppressWarnings("deprecation") +@Entity +@Table(name = "refresh_token") +public class OAuth2RefreshTokenEntity implements OAuth2RefreshToken { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "token_value", length = 4096) + private String tokenValue; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "expiration") + private Date expiration; + + @ManyToOne(fetch = FetchType.EAGER, optional = false) + @JoinColumn(name = "auth_holder_id", nullable = false) + private AuthenticationHolderEntity authenticationHolder; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "client_id") + private ClientDetailsEntity client; + + @OneToMany(mappedBy = "refreshToken", cascade = CascadeType.ALL, orphanRemoval = true) + private Set accessTokens = new HashSet<>(); + + @Transient + private JWT jwt; + + protected OAuth2RefreshTokenEntity() { + } + + protected OAuth2RefreshTokenEntity(ClientDetailsEntity client, + AuthenticationHolderEntity authenticationHolder, OAuth2AccessTokenEntity accessToken) { + + Objects.nonNull(client); + Objects.nonNull(authenticationHolder); + Objects.nonNull(accessToken); + setAuthenticationHolder(authenticationHolder); + setClient(client); + addAccessToken(accessToken); + } + + public OAuth2RefreshTokenEntity(ClientDetailsEntity client, + AuthenticationHolderEntity authenticationHolder, OAuth2AccessTokenEntity accessToken, + JWT jwtTokenValue) { + + this(client, authenticationHolder, accessToken); + Objects.nonNull(jwtTokenValue); + this.tokenValue = jwtTokenValue.serialize(); + this.jwt = jwtTokenValue; + } + + public OAuth2RefreshTokenEntity(ClientDetailsEntity client, + AuthenticationHolderEntity authenticationHolder, OAuth2AccessTokenEntity accessToken, + String tokenValue) { + + this(client, authenticationHolder, accessToken); + Objects.nonNull(tokenValue); + try { + this.jwt = JWTParser.parse(tokenValue); + } catch (ParseException e) { + throw new IllegalArgumentException("Invalid token value: " + e.getMessage()); + } + this.tokenValue = tokenValue; + } + + public Long getId() { + + return id; + } + + public AuthenticationHolderEntity getAuthenticationHolder() { + + return authenticationHolder; + } + + public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHolder) { + + this.authenticationHolder = authenticationHolder; + } + + // public void setTokenValue(String tokenValue) { + // + // Objects.nonNull(tokenValue); + // this.tokenValue = tokenValue; + // try { + // this.jwt = JWTParser.parse(tokenValue); + // } catch (ParseException e) { + // throw new IllegalArgumentException("Invalid token value: " + e.getMessage()); + // } + // } + + @Override + @Transient + public String getValue() { + + return tokenValue; + } + + public Date getExpiration() { + + return expiration; + } + + public void setExpiration(Date expiration) { + + this.expiration = expiration; + } + + @Transient + public boolean isExpired() { + + return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime(); + } + + public ClientDetailsEntity getClient() { + + return client; + } + + public void setClient(ClientDetailsEntity client) { + + this.client = client; + } + + @Transient + public JWT getJwt() { + + if (jwt == null && tokenValue != null) { + try { + jwt = JWTParser.parse(tokenValue); + } catch (ParseException e) { + throw new IllegalStateException(e); + } + } + return jwt; + } + + @Transient + public Set getAccessTokens() { + return accessTokens; + } + + public void addAccessToken(OAuth2AccessTokenEntity accessToken) { + + accessTokens.add(accessToken); + accessToken.setRefreshToken(this); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/PKCEAlgorithm.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/PKCEAlgorithm.java new file mode 100644 index 0000000000..1d8eef553b --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/PKCEAlgorithm.java @@ -0,0 +1,45 @@ +/** + * 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.persistence.model; + +import com.nimbusds.jose.Algorithm; +import com.nimbusds.jose.Requirement; + +public final class PKCEAlgorithm extends Algorithm { + + private static final long serialVersionUID = 7752852583210088925L; + + public static final PKCEAlgorithm plain = new PKCEAlgorithm("plain", Requirement.REQUIRED); + public static final PKCEAlgorithm S256 = new PKCEAlgorithm("S256", Requirement.OPTIONAL); + + public PKCEAlgorithm(String name, Requirement req) { + super(name, req); + } + + public PKCEAlgorithm(String name) { + super(name, null); + } + + public static PKCEAlgorithm parse(final String s) { + if (s.equals(plain.getName())) { + return plain; + } + if (s.equals(S256.getName())) { + return S256; + } + return new PKCEAlgorithm(s); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/RegisteredClientFields.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/RegisteredClientFields.java new file mode 100644 index 0000000000..38f929b1a7 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/RegisteredClientFields.java @@ -0,0 +1,62 @@ +/** + * 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.persistence.model; + +public interface RegisteredClientFields { + + public String SOFTWARE_ID = "software_id"; + public String SOFTWARE_VERSION = "software_version"; + public String SOFTWARE_STATEMENT = "software_statement"; + public String CLAIMS_REDIRECT_URIS = "claims_redirect_uris"; + public String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at"; + public String CLIENT_ID_ISSUED_AT = "client_id_issued_at"; + public String REGISTRATION_CLIENT_URI = "registration_client_uri"; + public String REGISTRATION_ACCESS_TOKEN = "registration_access_token"; + public String REQUEST_URIS = "request_uris"; + public String POST_LOGOUT_REDIRECT_URIS = "post_logout_redirect_uris"; + public String INITIATE_LOGIN_URI = "initiate_login_uri"; + public String DEFAULT_ACR_VALUES = "default_acr_values"; + public String REQUIRE_AUTH_TIME = "require_auth_time"; + public String DEFAULT_MAX_AGE = "default_max_age"; + public String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "token_endpoint_auth_signing_alg"; + public String ID_TOKEN_ENCRYPTED_RESPONSE_ENC = "id_token_encrypted_response_enc"; + public String ID_TOKEN_ENCRYPTED_RESPONSE_ALG = "id_token_encrypted_response_alg"; + public String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg"; + public String USERINFO_ENCRYPTED_RESPONSE_ENC = "userinfo_encrypted_response_enc"; + public String USERINFO_ENCRYPTED_RESPONSE_ALG = "userinfo_encrypted_response_alg"; + public String USERINFO_SIGNED_RESPONSE_ALG = "userinfo_signed_response_alg"; + public String REQUEST_OBJECT_SIGNING_ALG = "request_object_signing_alg"; + public String SUBJECT_TYPE = "subject_type"; + public String SECTOR_IDENTIFIER_URI = "sector_identifier_uri"; + public String APPLICATION_TYPE = "application_type"; + public String JWKS_URI = "jwks_uri"; + public String JWKS = "jwks"; + public String SCOPE_SEPARATOR = " "; + public String POLICY_URI = "policy_uri"; + public String RESPONSE_TYPES = "response_types"; + public String GRANT_TYPES = "grant_types"; + public String SCOPE = "scope"; + public String TOKEN_ENDPOINT_AUTH_METHOD = "token_endpoint_auth_method"; + public String TOS_URI = "tos_uri"; + public String CONTACTS = "contacts"; + public String LOGO_URI = "logo_uri"; + public String CLIENT_URI = "client_uri"; + public String CLIENT_NAME = "client_name"; + public String REDIRECT_URIS = "redirect_uris"; + public String CLIENT_SECRET = "client_secret"; + public String CLIENT_ID = "client_id"; + public String CODE_CHALLENGE_METHOD = "code_challenge_method"; +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SavedUserAuthentication.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SavedUserAuthentication.java new file mode 100644 index 0000000000..59b88e47b3 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SavedUserAuthentication.java @@ -0,0 +1,178 @@ +/** + * 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.persistence.model; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; +import javax.persistence.Transient; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import it.infn.mw.iam.persistence.model.convert.SimpleGrantedAuthorityStringConverter; + +@Entity +@Table(name = "saved_user_auth") +public class SavedUserAuthentication implements Authentication { + + private static final long serialVersionUID = -1804249963940323488L; + + private Long id; + + private String name; + + private Set authorities = new HashSet<>(); + + private boolean authenticated; + + private String sourceClass; + + private Map additionalInfo = new HashMap<>(); + + public SavedUserAuthentication(Authentication src) { + setName(src.getName()); + setAuthorities(new HashSet<>(src.getAuthorities())); + setAuthenticated(src.isAuthenticated()); + + if (src instanceof SavedUserAuthentication) { + // if we're copying in a saved auth, carry over the original class name + setSourceClass(((SavedUserAuthentication) src).getSourceClass()); + additionalInfo.putAll(((SavedUserAuthentication) src).getAdditionalInfo()); + + } else { + setSourceClass(src.getClass().getName()); + } + + if (src.getDetails() instanceof Map) { + Map details = (Map) src.getDetails(); + Object acr = details.get("acr"); + if (acr != null) { + additionalInfo.put("acr", acr.toString()); + } + } + } + + public SavedUserAuthentication() { + + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + @Basic + @Column(name = "name") + public String getName() { + return name; + } + + @Override + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "saved_user_auth_authority", joinColumns = @JoinColumn(name = "owner_id")) + @Convert(converter = SimpleGrantedAuthorityStringConverter.class) + @Column(name = "authority") + public Set getAuthorities() { + return authorities; + } + + @Override + @Transient + public Object getCredentials() { + return ""; + } + + @Override + @Transient + public Object getDetails() { + return null; + } + + @Override + @Transient + public Object getPrincipal() { + return getName(); + } + + @Override + @Basic + @Column(name = "authenticated") + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + this.authenticated = isAuthenticated; + } + + @Basic + @Column(name = "source_class") + public String getSourceClass() { + return sourceClass; + } + + public void setSourceClass(String sourceClass) { + this.sourceClass = sourceClass; + } + + public void setName(String name) { + this.name = name; + } + + public void setAuthorities(Set authorities) { + Objects.nonNull(authorities); + this.authorities.clear(); + this.authorities = new HashSet<>(); + this.authorities.addAll(authorities); + } + + @ElementCollection(fetch = FetchType.EAGER) + @MapKeyColumn(name = "info_key") + @Column(name = "info_val", length = 256) + @CollectionTable(name = "saved_user_auth_info", joinColumns = @JoinColumn(name = "owner_id")) + public Map getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(Map additionalInfo) { + this.additionalInfo = additionalInfo; + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SubjectType.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SubjectType.java new file mode 100644 index 0000000000..c6592fcdf1 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SubjectType.java @@ -0,0 +1,46 @@ +/** + * 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.persistence.model; + +import java.util.HashMap; +import java.util.Map; + +public enum SubjectType { + + PAIRWISE("pairwise"), + PUBLIC("public"); + + private final String value; + + private static final Map lookup = new HashMap<>(); + static { + for (SubjectType u : SubjectType.values()) { + lookup.put(u.getValue(), u); + } + } + + SubjectType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static SubjectType getByValue(String value) { + return lookup.get(value); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SystemScope.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SystemScope.java new file mode 100644 index 0000000000..e1f25738c8 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/SystemScope.java @@ -0,0 +1,185 @@ +/** + * 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.persistence.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "system_scope") +public class SystemScope { + + public static final String PARAM_VALUE = "value"; + + private Long id; + + private String value; + + private String description; + + private String icon; + + private boolean defaultScope = false; + + private boolean restricted = false; + + + public SystemScope() {} + + public SystemScope(String value) { + this.value = value; + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Basic + @Column(name = "scope") + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Basic + @Column(name = "description") + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Basic + @Column(name = "icon") + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + @Basic + @Column(name = "default_scope") + public boolean isDefaultScope() { + return defaultScope; + } + + public void setDefaultScope(boolean defaultScope) { + this.defaultScope = defaultScope; + } + + @Basic + @Column(name = "restricted") + public boolean isRestricted() { + return restricted; + } + + public void setRestricted(boolean restricted) { + this.restricted = restricted; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (defaultScope ? 1231 : 1237); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((icon == null) ? 0 : icon.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + (restricted ? 1231 : 1237); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SystemScope other = (SystemScope) obj; + if (defaultScope != other.defaultScope) { + return false; + } + if (description == null) { + if (other.description != null) { + return false; + } + } else if (!description.equals(other.description)) { + return false; + } + if (icon == null) { + if (other.icon != null) { + return false; + } + } else if (!icon.equals(other.icon)) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (restricted != other.restricted) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "SystemScope [id=" + id + ", value=" + value + ", description=" + description + ", icon=" + + icon + ", defaultScope=" + defaultScope + ", restricted=" + restricted + "]"; + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/WhitelistedSite.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/WhitelistedSite.java new file mode 100644 index 0000000000..f094947672 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/WhitelistedSite.java @@ -0,0 +1,90 @@ +/** + * 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.persistence.model; + +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +@Entity +@Table(name = "whitelisted_site") +public class WhitelistedSite { + + private Long id; + + private String creatorUserId; + + private String clientId; + + private Set allowedScopes; + + public WhitelistedSite() { + + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Basic + @Column(name = "client_id") + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "whitelisted_site_scope", joinColumns = @JoinColumn(name = "owner_id")) + @Column(name = "scope") + public Set getAllowedScopes() { + return allowedScopes; + } + + public void setAllowedScopes(Set allowedScopes) { + this.allowedScopes = allowedScopes; + } + + @Basic + @Column(name = "creator_user_id") + public String getCreatorUserId() { + return creatorUserId; + } + + public void setCreatorUserId(String creatorUserId) { + this.creatorUserId = creatorUserId; + } +} + diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWEAlgorithmStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWEAlgorithmStringConverter.java new file mode 100644 index 0000000000..637d081c74 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWEAlgorithmStringConverter.java @@ -0,0 +1,43 @@ +/** + * 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.persistence.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.nimbusds.jose.JWEAlgorithm; + +@Converter +public class JWEAlgorithmStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(JWEAlgorithm attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + @Override + public JWEAlgorithm convertToEntityAttribute(String dbData) { + if (dbData != null) { + return JWEAlgorithm.parse(dbData); + } else { + return null; + } + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWEEncryptionMethodStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWEEncryptionMethodStringConverter.java new file mode 100644 index 0000000000..3e7a745a66 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWEEncryptionMethodStringConverter.java @@ -0,0 +1,44 @@ +/** + * 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.persistence.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.nimbusds.jose.EncryptionMethod; + +@Converter +public class JWEEncryptionMethodStringConverter + implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(EncryptionMethod attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + @Override + public EncryptionMethod convertToEntityAttribute(String dbData) { + if (dbData != null) { + return EncryptionMethod.parse(dbData); + } else { + return null; + } + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWKSetStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWKSetStringConverter.java new file mode 100644 index 0000000000..b9ec382acc --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWKSetStringConverter.java @@ -0,0 +1,58 @@ +/** + * 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.persistence.model.convert; + +import java.text.ParseException; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.nimbusds.jose.jwk.JWKSet; + +@Converter +public class JWKSetStringConverter implements AttributeConverter { + + private static Logger logger = LoggerFactory.getLogger(JWKSetStringConverter.class); + + @Override + public String convertToDatabaseColumn(JWKSet attribute) { + if (attribute != null) { + return attribute.toString(); + } else { + return null; + } + } + + @Override + public JWKSet convertToEntityAttribute(String dbData) { + if (dbData != null) { + try { + JWKSet jwks = JWKSet.parse(dbData); + return jwks; + } catch (ParseException e) { + logger.error("Unable to parse JWK Set", e); + return null; + } + } else { + return null; + } + + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWSAlgorithmStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWSAlgorithmStringConverter.java new file mode 100644 index 0000000000..6321ea595c --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWSAlgorithmStringConverter.java @@ -0,0 +1,43 @@ +/** + * 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.persistence.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.nimbusds.jose.JWSAlgorithm; + +@Converter +public class JWSAlgorithmStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(JWSAlgorithm attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + @Override + public JWSAlgorithm convertToEntityAttribute(String dbData) { + if (dbData != null) { + return JWSAlgorithm.parse(dbData); + } else { + return null; + } + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWTStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWTStringConverter.java new file mode 100644 index 0000000000..23776a60fa --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/JWTStringConverter.java @@ -0,0 +1,58 @@ +/** + * 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.persistence.model.convert; + +import java.text.ParseException; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +@Converter +public class JWTStringConverter implements AttributeConverter { + + public static Logger logger = LoggerFactory.getLogger(JWTStringConverter.class); + + @Override + public String convertToDatabaseColumn(JWT attribute) { + if (attribute != null) { + return attribute.serialize(); + } else { + return null; + } + } + + @Override + public JWT convertToEntityAttribute(String dbData) { + if (dbData != null) { + try { + JWT jwt = JWTParser.parse(dbData); + return jwt; + } catch (ParseException e) { + logger.error("Unable to parse JWT", e); + return null; + } + } else { + return null; + } + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/PKCEAlgorithmStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/PKCEAlgorithmStringConverter.java new file mode 100644 index 0000000000..3ae5babd60 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/PKCEAlgorithmStringConverter.java @@ -0,0 +1,44 @@ +/** + * 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.persistence.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import it.infn.mw.iam.persistence.model.PKCEAlgorithm; + +@Converter +public class PKCEAlgorithmStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(PKCEAlgorithm attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + @Override + public PKCEAlgorithm convertToEntityAttribute(String dbData) { + if (dbData != null) { + return PKCEAlgorithm.parse(dbData); + } else { + return null; + } + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/SerializableStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/SerializableStringConverter.java new file mode 100644 index 0000000000..efc7b772d5 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/SerializableStringConverter.java @@ -0,0 +1,56 @@ +/** + * 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.persistence.model.convert; + +import java.io.Serializable; +import java.util.Date; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Converter +public class SerializableStringConverter implements AttributeConverter { + + private static Logger logger = LoggerFactory.getLogger(SerializableStringConverter.class); + + @Override + public String convertToDatabaseColumn(Serializable attribute) { + + if (attribute == null) { + return null; + } + if (attribute instanceof String) { + return (String) attribute; + } + if (attribute instanceof Long) { + return attribute.toString(); + } + if (attribute instanceof Date) { + return Long.toString(((Date) attribute).getTime()); + } + logger.warn("Dropping data from request: " + attribute + " :: " + attribute.getClass()); + return null; + } + + @Override + public Serializable convertToEntityAttribute(String dbData) { + return dbData; + } + +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/SimpleGrantedAuthorityStringConverter.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/SimpleGrantedAuthorityStringConverter.java new file mode 100644 index 0000000000..657d02eac2 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/convert/SimpleGrantedAuthorityStringConverter.java @@ -0,0 +1,46 @@ +/** + * 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.persistence.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +@Converter +public class SimpleGrantedAuthorityStringConverter + implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(SimpleGrantedAuthority attribute) { + if (attribute != null) { + return attribute.getAuthority(); + } else { + return null; + } + } + + @Override + public SimpleGrantedAuthority convertToEntityAttribute(String dbData) { + if (dbData != null) { + return new SimpleGrantedAuthority(dbData); + } else { + return null; + } + } + +} + diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/serializer/ApprovedSiteSerializer.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/serializer/ApprovedSiteSerializer.java new file mode 100644 index 0000000000..d99d075e0e --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/serializer/ApprovedSiteSerializer.java @@ -0,0 +1,58 @@ +/** + * 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.persistence.model.serializer; + +import java.lang.reflect.Type; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import it.infn.mw.iam.persistence.model.ApprovedSite; + +public class ApprovedSiteSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(ApprovedSite src, Type typeOfSrc, JsonSerializationContext context) { + + JsonObject json = new JsonObject(); + + json.addProperty("id", src.getId()); + json.addProperty("creationDate", src.getCreationDate().toString()); + if (src.getAccessDate() != null) { + json.addProperty("accessDate", src.getAccessDate().toString()); + } else { + json.add("accessDate", JsonNull.INSTANCE); + } + if (src.getTimeoutDate() != null) { + json.addProperty("timeoutDate", src.getTimeoutDate().toString()); + } else { + json.add("timeoutDate", JsonNull.INSTANCE); + } + json.addProperty("clientId", src.getClient().getClientId()); + json.addProperty("userId", src.getAccount().getUsername()); + + JsonArray scopes = new JsonArray(); + src.getAllowedScopes().forEach(scopes::add); + json.add("scope", scopes); + + return json; + } + +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamAccountClientRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountClientRepository.java similarity index 88% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamAccountClientRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountClientRepository.java index 05d8e20a68..4761869a50 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamAccountClientRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountClientRepository.java @@ -13,22 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.persistence.repository.client; +package it.infn.mw.iam.persistence.repository; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; public interface IamAccountClientRepository - extends CrudRepository, JpaSpecificationExecutor { + extends JpaRepository, JpaSpecificationExecutor { @Query("select ac.client from IamAccountClient ac where ac.client.clientId = :clientId") Page findClientByClientClientId(String clientId, Pageable pageable); diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamApprovedSiteRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamApprovedSiteRepository.java new file mode 100644 index 0000000000..6d53eb35a6 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamApprovedSiteRepository.java @@ -0,0 +1,64 @@ +/** + * 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.persistence.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import it.infn.mw.iam.persistence.model.ApprovedSite; + +public interface IamApprovedSiteRepository extends JpaRepository { + + @Query(""" + SELECT a + FROM ApprovedSite a + WHERE a.client.clientId = :clientId + AND a.account.username = :userId + """) + List findByClientIdAndUserId(String clientId, String userId); + + @Query(""" + SELECT a + FROM ApprovedSite a + WHERE a.account.username = :userId + """) + List findByUserId(String userId); + + @Query(""" + SELECT a + FROM ApprovedSite a + WHERE a.client.clientId = :clientId + """) + List findByClientId(String clientId); + + @Query(""" + SELECT a + FROM ApprovedSite a + WHERE a.timeoutDate IS NOT NULL + AND a.timeoutDate < CURRENT_TIMESTAMP + """) + List findExpired(); + + @Query(""" + SELECT + COUNT(DISTINCT a.account.id) AS userCount, + COUNT(DISTINCT a.client.id) AS clientCount + FROM ApprovedSite a + """) + UserClientCounts findDistinctUserAndClientCounts(); +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthenticationHolderRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthenticationHolderRepository.java new file mode 100644 index 0000000000..bd0e50eb14 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthenticationHolderRepository.java @@ -0,0 +1,33 @@ +/** + * 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.persistence.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import it.infn.mw.iam.persistence.model.AuthenticationHolderEntity; + +public interface IamAuthenticationHolderRepository + extends JpaRepository { + + @Query("select a from AuthenticationHolderEntity a where " + + "a.id not in (select t.authenticationHolder.id from OAuth2AccessTokenEntity t) and " + + "a.id not in (select r.authenticationHolder.id from OAuth2RefreshTokenEntity r) and " + + "a.id not in (select c.authenticationHolder.id from AuthorizationCodeEntity c)") + Page getOrphans(Pageable p); +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthorizationCodeRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthorizationCodeRepository.java new file mode 100644 index 0000000000..5911f16dcd --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthorizationCodeRepository.java @@ -0,0 +1,38 @@ +/** + * 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.persistence.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import it.infn.mw.iam.persistence.model.AuthorizationCodeEntity; + +public interface IamAuthorizationCodeRepository + extends JpaRepository { + + Optional findByCode(String code); + + @Query(""" + SELECT a + FROM AuthorizationCodeEntity a + WHERE a.expiration IS NOT NULL + AND a.expiration < CURRENT_TIMESTAMP + """) + List findExpired(); +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthorizationCodeRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamBlacklistedSiteRepository.java similarity index 77% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthorizationCodeRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamBlacklistedSiteRepository.java index 665185d554..3c5fbeaa3e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthorizationCodeRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamBlacklistedSiteRepository.java @@ -15,9 +15,13 @@ */ package it.infn.mw.iam.persistence.repository; -import org.mitre.oauth2.model.AuthorizationCodeEntity; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; -public interface IamAuthorizationCodeRepository - extends JpaRepository { +import it.infn.mw.iam.persistence.model.BlacklistedSite; + +public interface IamBlacklistedSiteRepository extends JpaRepository { + + List findByUri(String uri); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamClientRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamClientRepository.java similarity index 84% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamClientRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamClientRepository.java index 89ad14387a..9e73cea2ec 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/client/IamClientRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamClientRepository.java @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.persistence.repository.client; +package it.infn.mw.iam.persistence.repository; import java.util.Date; import java.util.List; import java.util.Optional; -import org.mitre.oauth2.model.ClientDetailsEntity; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; -public interface IamClientRepository extends PagingAndSortingRepository, +import it.infn.mw.iam.persistence.model.ClientDetailsEntity; + +public interface IamClientRepository extends JpaRepository, JpaSpecificationExecutor { Optional findByClientId(String clientId); diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamDeviceCodeRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamDeviceCodeRepository.java new file mode 100644 index 0000000000..1687957367 --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamDeviceCodeRepository.java @@ -0,0 +1,35 @@ +/** + * 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.persistence.repository; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import it.infn.mw.iam.persistence.model.DeviceCode; + +public interface IamDeviceCodeRepository extends JpaRepository { + + Optional findByUserCode(String userCode); + + Optional findByDeviceCode(String deviceCode); + + @Query("select dc from DeviceCode dc where dc.expiration <= CURRENT_DATE") + Page findExpiredDeviceCode(Pageable op); +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRepository.java index f9afc87e3d..981c0c062d 100644 --- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRepository.java @@ -21,13 +21,13 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import it.infn.mw.iam.persistence.model.IamGroup; -public interface IamGroupRepository extends PagingAndSortingRepository { +public interface IamGroupRepository extends JpaRepository { Optional findByUuid(@Param("uuid") String uuid); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthAccessTokenRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthAccessTokenRepository.java similarity index 89% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthAccessTokenRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthAccessTokenRepository.java index bc4fd34d96..e2a38af106 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthAccessTokenRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthAccessTokenRepository.java @@ -19,13 +19,14 @@ import java.util.List; import java.util.Optional; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import it.infn.mw.iam.persistence.model.OAuth2AccessTokenEntity; + public interface IamOAuthAccessTokenRepository extends JpaRepository { @@ -36,9 +37,15 @@ public interface IamOAuthAccessTokenRepository @Query("select t from OAuth2AccessTokenEntity t " + "where t.client.id = :clientId " - + "and ('registration-token' member of t.scope or 'resource-token' member of t.scope)") + + "and ('registration-token' member of t.scope)") Optional findRegistrationToken(@Param("clientId") Long id); + @Query("select t from OAuth2AccessTokenEntity t " + + "where t.client.id = :clientId " + + "and ('resource-token' member of t.scope)") + Optional findResourceToken(@Param("clientId") Long id); + + @Query("select t from OAuth2AccessTokenEntity t " + "where t.client.id = :clientId " + "and 'resource-token' not member of t.scope " @@ -76,7 +83,7 @@ Page findValidAccessTokensForUser( Pageable op); @Query("select t from OAuth2AccessTokenEntity t " - + "where t.authenticationHolder.clientId = :clientId " + + "where t.authenticationHolder.client.clientId = :clientId " + "and 'resource-token' not member of t.scope " + "and 'registration-token' not member of t.scope " + "and t.expiration is NOT NULL " @@ -90,7 +97,7 @@ Page findValidAccessTokensForClient( + "where t.authenticationHolder.userAuth.name = :userId " + "and 'resource-token' not member of t.scope " + "and 'registration-token' not member of t.scope " - + "and t.authenticationHolder.clientId = :clientId " + + "and t.authenticationHolder.client.clientId = :clientId " + "and t.expiration is NOT NULL " + "and t.expiration > :timestamp " + "order by t.expiration") @@ -126,7 +133,7 @@ long countValidAccessTokensForUser(@Param("userId") String userId, @Query("select count(t) from OAuth2AccessTokenEntity t " + "where t.expiration is NOT NULL " + "and t.expiration > :timestamp " - + "and t.authenticationHolder.clientId = :clientId " + + "and t.authenticationHolder.client.clientId = :clientId " + "and 'resource-token' not member of t.scope " + "and 'registration-token' not member of t.scope ") long countValidAccessTokensForClient(@Param("clientId") String clientId, @@ -136,7 +143,7 @@ long countValidAccessTokensForClient(@Param("clientId") String clientId, + "where t.expiration is NOT NULL " + "and t.expiration > :timestamp " + "and t.authenticationHolder.userAuth.name = :userId " - + "and t.authenticationHolder.clientId = :clientId " + + "and t.authenticationHolder.client.clientId = :clientId " + "and 'resource-token' not member of t.scope " + "and 'registration-token' not member of t.scope ") long countValidAccessTokensForUserAndClient(@Param("userId") String userId, @@ -155,6 +162,9 @@ long countValidAccessTokensForUserAndClient(@Param("userId") String userId, @Query("select t from OAuth2AccessTokenEntity t where t.expiration <= CURRENT_DATE") Page findExpiredTokens(Pageable op); + + @Query("select t from OAuth2AccessTokenEntity t where t.approvedSite.id = :approvedSiteId") + List findByApprovedSiteId(@Param("approvedSiteId") Long approvedSiteId); // @formatter:on } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthRefreshTokenRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthRefreshTokenRepository.java similarity index 91% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthRefreshTokenRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthRefreshTokenRepository.java index 6966b2cc97..a23009d9d0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthRefreshTokenRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamOAuthRefreshTokenRepository.java @@ -19,20 +19,19 @@ import java.util.List; import java.util.Optional; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import com.nimbusds.jwt.JWT; +import it.infn.mw.iam.persistence.model.OAuth2RefreshTokenEntity; public interface IamOAuthRefreshTokenRepository extends JpaRepository { - @Query("select r from OAuth2RefreshTokenEntity r where r.jwt = :value") - Optional findByTokenValue(@Param("value") JWT value); + @Query("select r from OAuth2RefreshTokenEntity r where r.tokenValue = :value") + Optional findByTokenValue(@Param("value") String tokenValue); @Query("select r from OAuth2RefreshTokenEntity r where r.client.id = :clientId") List findByClientId(@Param("clientId") Long id); @@ -52,14 +51,14 @@ Page findValidRefreshTokensForUser(@Param("userId") St @Param("timestamp") Date timestamp, Pageable op); @Query("select t from OAuth2RefreshTokenEntity t " - + "where (t.authenticationHolder.clientId = :clientId) " + + "where (t.authenticationHolder.client.clientId = :clientId) " + "and (t.expiration is NULL or t.expiration > :timestamp) order by t.expiration,t.id") Page findValidRefreshTokensForClient(@Param("clientId") String clientId, @Param("timestamp") Date timestamp, Pageable op); @Query("select t from OAuth2RefreshTokenEntity t " + "where (t.authenticationHolder.userAuth.name = :userId) " - + "and (t.authenticationHolder.clientId = :clientId) " + + "and (t.authenticationHolder.client.clientId = :clientId) " + "and (t.expiration is NULL or t.expiration > :timestamp) order by t.expiration,t.id") Page findValidRefreshTokensForUserAndClient( @Param("userId") String userId, @Param("clientId") String clientId, @@ -82,14 +81,14 @@ long countValidRefreshTokensForUser(@Param("userId") String userId, @Query("select count(t) from OAuth2RefreshTokenEntity t " + "where (t.expiration is NULL or t.expiration > :timestamp) " - + "and (t.authenticationHolder.clientId = :clientId)") + + "and (t.authenticationHolder.client.clientId = :clientId)") long countValidRefreshTokensForClient(@Param("clientId") String clientId, @Param("timestamp") Date timestamp); @Query("select count(t) from OAuth2RefreshTokenEntity t " + "where (t.expiration is NULL or t.expiration > :timestamp) " + "and (t.authenticationHolder.userAuth.name = :userId) " - + "and (t.authenticationHolder.clientId = :clientId)") + + "and (t.authenticationHolder.client.clientId = :clientId)") long countValidRefreshTokensForUserAndClient(@Param("userId") String userId, @Param("clientId") String clientId, @Param("timestamp") Date timestamp); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamApprovedSiteRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamSystemScopeRepository.java similarity index 77% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamApprovedSiteRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamSystemScopeRepository.java index e4c6f25857..e8c412c026 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamApprovedSiteRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamSystemScopeRepository.java @@ -15,9 +15,13 @@ */ package it.infn.mw.iam.persistence.repository; -import org.mitre.openid.connect.model.ApprovedSite; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; -public interface IamApprovedSiteRepository extends JpaRepository { +import it.infn.mw.iam.persistence.model.SystemScope; + +public interface IamSystemScopeRepository extends JpaRepository { + Optional findByValue(String value); } diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamWhitelistedSiteRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamWhitelistedSiteRepository.java new file mode 100644 index 0000000000..01677473bc --- /dev/null +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamWhitelistedSiteRepository.java @@ -0,0 +1,27 @@ +/** + * 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.persistence.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import it.infn.mw.iam.persistence.model.WhitelistedSite; + +public interface IamWhitelistedSiteRepository extends JpaRepository { + + Optional findByClientId(String clientId); +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthenticationHolderRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/UserClientCounts.java similarity index 72% rename from iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthenticationHolderRepository.java rename to iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/UserClientCounts.java index c531626c90..00725b10bb 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/persistence/repository/IamAuthenticationHolderRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/UserClientCounts.java @@ -15,9 +15,10 @@ */ package it.infn.mw.iam.persistence.repository; -import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.springframework.data.repository.PagingAndSortingRepository; +public interface UserClientCounts { -public interface IamAuthenticationHolderRepository - extends PagingAndSortingRepository { -} + long getUserCount(); + + long getClientCount(); + +} \ No newline at end of file diff --git a/iam-persistence/src/main/resources/db/migration/h2/V96__add_foreign_keys.sql b/iam-persistence/src/main/resources/db/migration/h2/V96__add_foreign_keys.sql index 3b55858aab..51470bac16 100644 --- a/iam-persistence/src/main/resources/db/migration/h2/V96__add_foreign_keys.sql +++ b/iam-persistence/src/main/resources/db/migration/h2/V96__add_foreign_keys.sql @@ -71,7 +71,7 @@ DELETE FROM access_token WHERE refresh_token_id NOT IN (SELECT id FROM refresh_t DELETE FROM access_token WHERE client_id NOT IN (SELECT id FROM client_details); DELETE FROM access_token WHERE auth_holder_id NOT IN (SELECT id FROM authentication_holder); -ALTER TABLE access_token ADD CONSTRAINT FK_access_token_refresh_token_id FOREIGN KEY (refresh_token_id) REFERENCES refresh_token (id) ON DELETE CASCADE; +ALTER TABLE access_token ADD CONSTRAINT FK_access_token_refresh_token_id FOREIGN KEY (refresh_token_id) REFERENCES refresh_token (id); -- ON DELETE CASCADE; ALTER TABLE access_token ADD CONSTRAINT FK_access_token_client_id FOREIGN KEY (client_id) REFERENCES client_details (id) ON DELETE CASCADE; ALTER TABLE access_token ADD CONSTRAINT FK_access_token_auth_holder_id FOREIGN KEY (auth_holder_id) REFERENCES authentication_holder (id) ON DELETE CASCADE; diff --git a/pom.xml b/pom.xml index 3ade43222b..6097cdb640 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ 17 5.14.1 + 9.37.4 1.3.7.cnaf-20260213 2.5.2.RELEASE @@ -241,32 +242,6 @@ - - org.mitre - openid-connect-common - ${mitreid.version} - - - - org.mitre - openid-connect-server - ${mitreid.version} - - - org.bouncycastle - bcprov-jdk15on - - - org.bouncycastle - bcprov-ext-jdk15on - - - org.bouncycastle - bcpkix-jdk15on - - - - org.mitre openid-connect-client @@ -339,6 +314,13 @@ sonar-maven-plugin ${sonar-maven-plugin.version} + + + + com.nimbusds + nimbus-jose-jwt + ${nimbus-jose-jwt.version} +