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 extends GrantedAuthority> 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 extends GrantedAuthority> 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 extends GrantedAuthority> 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 extends GrantedAuthority> 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