Skip to content

Commit

Permalink
Try to implement JIT account provisioning
Browse files Browse the repository at this point in the history
for OPs
  • Loading branch information
rmiccoli committed Feb 27, 2025
1 parent 4090f70 commit 9ff3cdc
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package it.infn.mw.iam.authn;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.stereotype.Component;
Expand All @@ -35,7 +34,6 @@ public class DefaultExternalAccountLinker implements ExternalAccountLinker {
final IamAccountRepository repo;
final SamlUserIdentifierResolver samlUserIdResolver;

@Autowired
public DefaultExternalAccountLinker(IamAccountRepository repo,
SamlUserIdentifierResolver resolver) {
this.repo = repo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.util.Optional;

import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
Expand All @@ -40,7 +39,6 @@ public class DefaultOidcUserDetailsService implements OidcUserDetailsService {
IamAccountRepository repo;
InactiveAccountAuthenticationHander inactiveAccountHandler;

@Autowired
public DefaultOidcUserDetailsService(IamAccountRepository repo,
InactiveAccountAuthenticationHander handler) {
this.repo = repo;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package it.infn.mw.iam.authn.oidc.service;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import org.mitre.openid.connect.model.OIDCAuthenticationToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import it.infn.mw.iam.authn.InactiveAccountAuthenticationHander;
import it.infn.mw.iam.core.user.IamAccountService;
import it.infn.mw.iam.persistence.model.IamAccount;
import it.infn.mw.iam.persistence.model.IamOidcId;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;

public class JustInTimeProvisioningOIDCUserDetailsService extends DefaultOidcUserDetailsService {

private final IamAccountService accountService;
private final Optional<Set<String>> trustedIdpEntityIds;

public JustInTimeProvisioningOIDCUserDetailsService(IamAccountRepository repo,
InactiveAccountAuthenticationHander handler, IamAccountService accountService,
Optional<Set<String>> trustedIdpEntityIds) {
super(repo, handler);
this.accountService = accountService;
this.trustedIdpEntityIds = trustedIdpEntityIds;
}

private void checkTrustedIdp(String issuer) {
trustedIdpEntityIds.ifPresent(trustedIds -> {
if (!trustedIds.contains(issuer)) {
throw new UsernameNotFoundException(
String.format("OIDC issuer '%s' is not trusted for JIT provisioning.", issuer));
}
});
}

private void checkRequiredClaims(OIDCAuthenticationToken token) {
if (token.getUserInfo().getGivenName() == null || token.getUserInfo().getFamilyName() == null
|| token.getUserInfo().getEmail() == null) {
throw new UsernameNotFoundException("OIDC token is missing required claims.");
}
}

private IamAccount provisionAccount(OIDCAuthenticationToken token) {
checkTrustedIdp(token.getIssuer().toString());
checkRequiredClaims(token);

IamAccount newAccount = IamAccount.newAccount();
newAccount.setUsername(UUID.randomUUID().toString());
newAccount.setProvisioned(true);

IamOidcId oidcId = new IamOidcId();
oidcId.setIssuer(token.getIssuer());
oidcId.setSubject(token.getSub());
oidcId.setAccount(newAccount);

newAccount.getOidcIds().add(oidcId);

newAccount.setActive(true);

newAccount.getUserInfo().setGivenName(token.getUserInfo().getGivenName());
newAccount.getUserInfo().setFamilyName(token.getUserInfo().getFamilyName());
newAccount.getUserInfo().setEmail(token.getUserInfo().getEmail());

accountService.createAccount(newAccount);
return newAccount;
}

@Override
public Object loadUserByOIDC(OIDCAuthenticationToken token) {
Optional<IamAccount> account = repo.findByOidcId(token.getIssuer().toString(), token.getSub());

if (account.isPresent()) {
return buildUserFromIamAccount(account.get());
} else {
return buildUserFromIamAccount(provisionAccount(token));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package it.infn.mw.iam.config.oidc;

import java.util.Optional;
import java.util.Set;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

import com.google.common.base.Splitter;
import com.google.common.collect.Sets;

@Validated
@ConfigurationProperties(prefix = "oidc.jit-account-provisioning")
public class IamOidcJITAccountProvisioningProperties {

private boolean enabled;
private String trustedIdps = "all";
private boolean cleanupTaskEnabled;
private long cleanupTaskPeriodSec = 86400;
private int inactiveAccountLifetimeDays = 15;

// Getters e Setters
public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getTrustedIdps() {
return trustedIdps;
}

public void setTrustedIdps(String trustedIdps) {
this.trustedIdps = trustedIdps;
}

public Optional<Set<String>> getTrustedIdpsAsOptionalSet() {
if ("all".equals(trustedIdps)) {
return Optional.empty();
}

Set<String> trustedIdpIds =
Sets.newHashSet(Splitter.on(",").trimResults().omitEmptyStrings().split(trustedIdps));

if (trustedIdpIds.isEmpty()) {
return Optional.empty();
}

return Optional.of(trustedIdpIds);
}

public boolean isCleanupTaskEnabled() {
return cleanupTaskEnabled;
}

public void setCleanupTaskEnabled(boolean cleanupTaskEnabled) {
this.cleanupTaskEnabled = cleanupTaskEnabled;
}

public long getCleanupTaskPeriodSec() {
return cleanupTaskPeriodSec;
}

public void setCleanupTaskPeriodSec(long cleanupTaskPeriodSec) {
this.cleanupTaskPeriodSec = cleanupTaskPeriodSec;
}

public int getInactiveAccountLifetimeDays() {
return inactiveAccountLifetimeDays;
}

public void setInactiveAccountLifetimeDays(int inactiveAccountLifetimeDays) {
this.inactiveAccountLifetimeDays = inactiveAccountLifetimeDays;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -69,14 +70,17 @@
import it.infn.mw.iam.authn.oidc.OidcTokenRequestor;
import it.infn.mw.iam.authn.oidc.RestTemplateFactory;
import it.infn.mw.iam.authn.oidc.service.DefaultOidcUserDetailsService;
import it.infn.mw.iam.authn.oidc.service.JustInTimeProvisioningOIDCUserDetailsService;
import it.infn.mw.iam.authn.oidc.service.NullClientConfigurationService;
import it.infn.mw.iam.authn.oidc.service.OidcUserDetailsService;
import it.infn.mw.iam.authn.util.SessionTimeoutHelper;
import it.infn.mw.iam.core.IamThirdPartyIssuerService;
import it.infn.mw.iam.core.user.IamAccountService;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;
import it.infn.mw.iam.service.aup.AUPSignatureCheckService;

@Configuration
@EnableConfigurationProperties(IamOidcJITAccountProvisioningProperties.class)
public class OidcConfiguration {

@Value("${iam.baseUrl}")
Expand All @@ -91,6 +95,9 @@ public class OidcConfiguration {
@Autowired
private AccountUtils accountUtils;

@Autowired
private IamOidcJITAccountProvisioningProperties jitProperties;

public static final String DEFINE_ME_PLEASE = "define_me_please";

@Bean
Expand Down Expand Up @@ -169,11 +176,12 @@ AuthenticationManager authenticationManager(
@Bean
OIDCAuthenticationProvider openIdConnectAuthenticationProvider(Clock clock,
OidcUserDetailsService userDetailService, UserInfoFetcher userInfoFetcher,
AuthenticationValidator<OIDCAuthenticationToken> validator, SessionTimeoutHelper timeoutHelper) {
AuthenticationValidator<OIDCAuthenticationToken> validator,
SessionTimeoutHelper timeoutHelper) {

OidcAuthenticationProvider provider =
new OidcAuthenticationProvider(userDetailService, validator, timeoutHelper);

provider.setUserInfoFetcher(userInfoFetcher);

return provider;
Expand Down Expand Up @@ -243,20 +251,27 @@ AuthRequestUrlBuilder authRequestBuilder() {
}

@Bean
OidcUserDetailsService userDetailService(IamAccountRepository repo,
InactiveAccountAuthenticationHander handler) {
OidcUserDetailsService oidcUserDetailsService(IamAccountRepository repo,
InactiveAccountAuthenticationHander handler, IamAccountService accountService) {

if (jitProperties.isEnabled()) {
return new JustInTimeProvisioningOIDCUserDetailsService(repo, handler, accountService,
jitProperties.getTrustedIdpsAsOptionalSet());
}

return new DefaultOidcUserDetailsService(repo, handler);
}



@Bean
UserInfoFetcher userInfoFetcher() {
return new UserInfoFetcher();
}

@Bean
OidcTokenRequestor tokenRequestor(RestTemplateFactory restTemplateFactory,
ObjectMapper mapper) {
OidcTokenRequestor tokenRequestor(RestTemplateFactory restTemplateFactory, ObjectMapper mapper) {
return new DefaultOidcTokenRequestor(restTemplateFactory, mapper);
}

}
9 changes: 8 additions & 1 deletion iam-login-service/src/main/resources/application-oidc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ oidc:
text: Google
style: btn-social btn-google
image:
fa-icon: google
fa-icon: google

jit-account-provisioning:
enabled: ${IAM_OIDC_JIT_ACCOUNT_PROVISIONING_ENABLED:false}
trusted-idps: ${IAM_OIDC_JIT_ACCOUNT_PROVISIONING_TRUSTED_IDPS:all}
cleanup-task-enabled: ${IAM_OIDC_JIT_ACCOUNT_PROVISIONING_CLEANUP_TASK_ENABLED:false}
cleanup-task-period-sec: ${IAM_OIDC_JIT_ACCOUNT_PROVISIONING_CLEANUP_TASK_PERIOD_SEC:86400}
inactive-account-lifetime-days: ${IAM_OIDC_JIT_ACCOUNT_PROVISIONING_INACTIVE_ACCOUNT_LIFETIME_DAYS:15}

0 comments on commit 9ff3cdc

Please sign in to comment.