fix OIDC client

This commit is contained in:
_Bastler 2022-12-09 11:02:21 +01:00
parent c73c172abe
commit 27c6ab5630
14 changed files with 187 additions and 11 deletions

View File

@ -64,6 +64,16 @@ public class UserAliasManager implements UserDataProvider {
return userAliasRepository.findOne(qUserAlias.alias.eq(alias)).orElse(null); return userAliasRepository.findOne(qUserAlias.alias.eq(alias)).orElse(null);
} }
/**
*
* @param userId
* @param alias
* @return
*/
public boolean hasAlias(Long userId, String alias) {
return userAliasRepository.exists(qUserAlias.target.eq(userId).and(qUserAlias.alias.eq(alias)));
}
/** /**
* Gets the all by target. * Gets the all by target.
* *

View File

@ -4,6 +4,7 @@
package de.bstly.we.controller; package de.bstly.we.controller;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -13,7 +14,9 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -22,12 +25,14 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import de.bstly.we.businesslogic.UserAliasManager;
import de.bstly.we.businesslogic.UserManager; import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.controller.model.PasswordResetModel; import de.bstly.we.controller.model.PasswordResetModel;
import de.bstly.we.controller.support.EntityResponseStatusException; import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.controller.support.RequestBodyErrors; import de.bstly.we.controller.support.RequestBodyErrors;
import de.bstly.we.controller.validation.PasswordModelValidator; import de.bstly.we.controller.validation.PasswordModelValidator;
import de.bstly.we.model.User; import de.bstly.we.model.User;
import de.bstly.we.security.model.LocalUserDetails;
/** /**
* The Class AuthenticationController. * The Class AuthenticationController.
@ -40,6 +45,8 @@ public class AuthenticationController extends BaseController {
private UserManager userManager; private UserManager userManager;
@Autowired @Autowired
private PasswordModelValidator passwordModelValidator; private PasswordModelValidator passwordModelValidator;
@Autowired
private UserAliasManager userAliasManager;
/** /**
* Me. * Me.
@ -102,7 +109,25 @@ public class AuthenticationController extends BaseController {
user = userManager.setPassword(user.getId(), passwordResetModel.getPassword()); user = userManager.setPassword(user.getId(), passwordResetModel.getPassword());
user.setResetToken(null); user.setResetToken(null);
userManager.update(user); userManager.update(user);
}
@PreAuthorize("authentication.authenticated")
@PostMapping("/alias")
public void setAlias(@RequestBody Optional<String> alias, HttpServletRequest req, HttpServletResponse resp) {
if (alias.isPresent() && !userAliasManager.hasAlias(getCurrentUserId(), alias.get())) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof LocalUserDetails) {
LocalUserDetails details = (LocalUserDetails) authentication.getPrincipal();
details.setAlias(alias.orElse(null));
Authentication newAuthentication = new UsernamePasswordAuthenticationToken(details,
authentication.getCredentials(), details.getAuthorities());
context.setAuthentication(newAuthentication);
}
} }
} }

View File

@ -3,6 +3,7 @@
*/ */
package de.bstly.we.controller; package de.bstly.we.controller;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -92,6 +93,23 @@ public class PermissionManagementController extends BaseController {
permission.getStarts(), permission.getExpires()); permission.getStarts(), permission.getExpires());
} }
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("list")
public List<Permission> createPermissions(@RequestBody List<Permission> permissions,
@RequestParam("target") Optional<Long> target, @RequestParam("starts") Optional<Instant> starts,
@RequestParam("expires") Optional<Instant> expires) {
List<Permission> result = Lists.newArrayList();
for (Permission permission : permissions) {
permission.setId(null);
permission.setTarget(target.orElse(permission.getTarget()));
permission.setStarts(starts.orElse(permission.getStarts()));
permission.setExpires(expires.orElse(permission.getExpires()));
result.add(permissionManager.create(permission.getTarget(), permission.getName(), permission.isAddon(),
permission.getStarts(), permission.getExpires()));
}
return result;
}
/** /**
* Update permission. * Update permission.
* *
@ -116,13 +134,15 @@ public class PermissionManagementController extends BaseController {
*/ */
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@PatchMapping("list") @PatchMapping("list")
public List<Permission> updatePermissions(@RequestBody List<Permission> permissions) { public List<Permission> updatePermissions(@RequestBody List<Permission> permissions,
@RequestParam("starts") Optional<Instant> starts, @RequestParam("expires") Optional<Instant> expires) {
List<Permission> result = Lists.newArrayList(); List<Permission> result = Lists.newArrayList();
for (Permission permission : permissions) { for (Permission permission : permissions) {
if (permission.getId() == null) { if (permission.getId() == null) {
throw new EntityResponseStatusException(HttpStatus.CONFLICT); throw new EntityResponseStatusException(HttpStatus.CONFLICT);
} }
permission.setStarts(starts.orElse(permission.getStarts()));
permission.setExpires(expires.orElse(permission.getExpires()));
result.add(permissionManager.update(permission)); result.add(permissionManager.update(permission));
} }
return result; return result;

View File

@ -109,6 +109,22 @@ public class QuotaManagementController extends BaseController {
quota.isDisposable()); quota.isDisposable());
} }
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/list")
public List<Quota> createQuotaList(@RequestBody List<Quota> quotas, @RequestParam("target") Optional<Long> target,
@RequestParam("value") Optional<Long> value) {
List<Quota> result = Lists.newArrayList();
for (Quota quota : quotas) {
quota.setId(null);
quota.setTarget(target.orElse(quota.getTarget()));
quota.setValue(value.orElse(quota.getValue()));
result.add(quotaManager.create(quota.getTarget(), quota.getName(), quota.getValue(), quota.getUnit(),
quota.isDisposable()));
}
return result;
}
/** /**
* Update quota. * Update quota.
* *
@ -133,7 +149,7 @@ public class QuotaManagementController extends BaseController {
*/ */
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@PatchMapping("/list") @PatchMapping("/list")
public List<Quota> updateQuotaList(@RequestBody List<Quota> quotas) { public List<Quota> updateQuotaList(@RequestBody List<Quota> quotas, @RequestParam("value") Optional<Long> value) {
List<Quota> result = Lists.newArrayList(); List<Quota> result = Lists.newArrayList();
@ -141,6 +157,7 @@ public class QuotaManagementController extends BaseController {
if (quotaManager.get(quota.getTarget(), quota.getName()) == null) { if (quotaManager.get(quota.getTarget(), quota.getName()) == null) {
throw new EntityResponseStatusException(HttpStatus.CONFLICT); throw new EntityResponseStatusException(HttpStatus.CONFLICT);
} }
quota.setValue(value.orElse(quota.getValue()));
result.add(quotaManager.update(quota)); result.add(quotaManager.update(quota));
} }

View File

@ -18,6 +18,7 @@ public class LocalUserDetails extends User {
*/ */
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Long userId; private Long userId;
private String alias;
/** /**
* Instantiates a new local user details. * Instantiates a new local user details.
@ -52,4 +53,18 @@ public class LocalUserDetails extends User {
this.userId = userId; this.userId = userId;
} }
/**
* @return the alias
*/
public String getAlias() {
return alias;
}
/**
* @param alias the alias to set
*/
public void setAlias(String alias) {
this.alias = alias;
}
} }

View File

@ -120,6 +120,16 @@ public class OidcAuthorizationManager {
return authorizationCode; return authorizationCode;
} }
/**
*
* @param authorizationCode
* @return
*/
public OidcAuthorizationCode setCode(OidcAuthorizationCode authorizationCode) {
authorizationCodes.put(authorizationCode.getCode(), authorizationCode);
return authorizationCode;
}
/** /**
* Gets the code. * Gets the code.
* *

View File

@ -144,6 +144,7 @@ public class OidcTokenManager implements SmartInitializingSingleton {
*/ */
public OidcToken createToken(OidcClient client, Long userId, String refreshToken) { public OidcToken createToken(OidcClient client, Long userId, String refreshToken) {
OidcToken token = new OidcToken(); OidcToken token = new OidcToken();
token.setClient(client.getId());
token.setUserId(userId); token.setUserId(userId);
token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true)); token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true));
if (StringUtils.hasText(refreshToken)) { if (StringUtils.hasText(refreshToken)) {
@ -166,8 +167,8 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @return the oidc token * @return the oidc token
* @throws JOSEException the JOSE exception * @throws JOSEException the JOSE exception
*/ */
public OidcToken createTokenWithIdToken(OidcClient client, Long userId, String nonce, Set<String> scopes, public OidcToken createTokenWithIdToken(OidcClient client, Long userId, String alias, String nonce,
String issuer, String sid) throws JOSEException { Set<String> scopes, String issuer, String sid) throws JOSEException {
User user = userManager.get(userId); User user = userManager.get(userId);
Assert.notNull(user, "User does not exist!"); Assert.notNull(user, "User does not exist!");
@ -175,10 +176,11 @@ public class OidcTokenManager implements SmartInitializingSingleton {
client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.refresh_token)); client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.refresh_token));
token.setUserId(user.getId()); token.setUserId(user.getId());
token.setAlias(alias);
token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true)); token.setAccessToken(RandomStringUtils.random(ACCESS_TOKEN_LENGTH, true, true));
token.setExpiresIn(client.getTokenLifetime()); token.setExpiresIn(client.getTokenLifetime());
Builder claimsSetBuilder = createUserClaims(client, user); Builder claimsSetBuilder = createUserClaims(client, user, alias);
claimsSetBuilder.issuer(issuer); claimsSetBuilder.issuer(issuer);
claimsSetBuilder.audience(client.getClientId()); claimsSetBuilder.audience(client.getClientId());
@ -219,13 +221,17 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @param user the user * @param user the user
* @return the builder * @return the builder
*/ */
public Builder createUserClaims(OidcClient client, User user) { public Builder createUserClaims(OidcClient client, User user, String alias) {
Builder claimsSetBuilder = new Builder(); Builder claimsSetBuilder = new Builder();
claimsSetBuilder.subject(String.valueOf(user.getId())); claimsSetBuilder.subject(String.valueOf(user.getId()));
String username = user.getUsername(); String username = user.getUsername();
if (StringUtils.hasText(alias) && client.isAliasAllowed()) {
username = alias;
}
claimsSetBuilder.claim("name", username); claimsSetBuilder.claim("name", username);
claimsSetBuilder.claim("username", username); claimsSetBuilder.claim("username", username);
claimsSetBuilder.claim("preferred_username", username); claimsSetBuilder.claim("preferred_username", username);

View File

@ -26,6 +26,7 @@ public class OidcAuthorizationCode {
private final Long userId; private final Long userId;
private final String nonce; private final String nonce;
private String springSession; private String springSession;
private String alias;
/** /**
* Instantiates a new oidc authorization code. * Instantiates a new oidc authorization code.
@ -127,4 +128,18 @@ public class OidcAuthorizationCode {
this.springSession = springSession; this.springSession = springSession;
} }
/**
* @return the alias
*/
public String getAlias() {
return alias;
}
/**
* @param alias the alias to set
*/
public void setAlias(String alias) {
this.alias = alias;
}
} }

View File

@ -30,6 +30,7 @@ import com.google.common.collect.Sets;
import de.bstly.we.businesslogic.PermissionManager; import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.Permissions; import de.bstly.we.businesslogic.Permissions;
import de.bstly.we.businesslogic.UserAliasManager;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationManager; import de.bstly.we.oidc.businesslogic.OidcAuthorizationManager;
import de.bstly.we.oidc.businesslogic.OidcClientManager; import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.exception.InvalidAuthorizationRequestException; import de.bstly.we.oidc.businesslogic.exception.InvalidAuthorizationRequestException;
@ -56,6 +57,8 @@ public class OidcAuthorizationController {
private OidcClientManager oidcClientManager; private OidcClientManager oidcClientManager;
@Autowired @Autowired
private OidcAuthorizationManager oidcAuthorizationManager; private OidcAuthorizationManager oidcAuthorizationManager;
@Autowired
private UserAliasManager userAliasManager;
@Value("${oidcAuthorizationFormUrl:/oidc/authorize/form}") @Value("${oidcAuthorizationFormUrl:/oidc/authorize/form}")
private String authorizationFormUrl; private String authorizationFormUrl;
@ -169,16 +172,30 @@ public class OidcAuthorizationController {
"code does not match user", state); "code does not match user", state);
} }
if (StringUtils.hasText(principal.getAlias())) {
if (!userAliasManager.hasAlias(principal.getUserId(), principal.getAlias())) {
throw new InvalidAuthorizationRequestException(redirectUri,
OidcAuthorizationErrorCode.ACCOUNT_SELECTION_REQUIRED, "alias does not match user", state);
}
authorizationCode.setAlias(principal.getAlias());
oidcAuthorizationManager.setCode(authorizationCode);
}
oidcAuthorizationManager.authorize(client.getId(), principal.getUserId(), scopes); oidcAuthorizationManager.authorize(client.getId(), principal.getUserId(), scopes);
} else { } else {
if (client.isAuthorize() if (client.isAuthorize()
&& !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes) && !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes)
|| prompts.contains("consent")) { || prompts.contains("consent")
|| client.isAliasAllowed() && !userAliasManager.getAllByTarget(principal.getUserId()).isEmpty()) {
uriBuilder = UriComponentsBuilder.fromUriString(authorizationFormUrl); uriBuilder = UriComponentsBuilder.fromUriString(authorizationFormUrl);
uriBuilder.queryParam("client_id", clientId); uriBuilder.queryParam("client_id", clientId);
uriBuilder.queryParam("response_type", responseType); uriBuilder.queryParam("response_type", responseType);
uriBuilder.queryParam("redirect_uri", redirectUri); uriBuilder.queryParam("redirect_uri", redirectUri);
uriBuilder.queryParam("scope", scope); uriBuilder.queryParam("scope", scope);
uriBuilder.queryParam("authorize", client.isAuthorize()
&& !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes));
uriBuilder.queryParam("alias",
client.isAliasAllowed() && !userAliasManager.getAllByTarget(principal.getUserId()).isEmpty());
if (StringUtils.hasText(nonce)) { if (StringUtils.hasText(nonce)) {
uriBuilder.queryParam("nonce", nonce); uriBuilder.queryParam("nonce", nonce);

View File

@ -176,7 +176,7 @@ public class OidcTokenController {
try { try {
String sid = oidcSessionManager.createSid(); String sid = oidcSessionManager.createSid();
token = oidcTokenManager.createTokenWithIdToken(client, authorizationCode.getUserId(), token = oidcTokenManager.createTokenWithIdToken(client, authorizationCode.getUserId(),
authorizationCode.getNonce(), scopes, issuer, sid); authorizationCode.getAlias(), authorizationCode.getNonce(), scopes, issuer, sid);
oidcSessionManager.createSession(client.getId(), authorizationCode.getUserId(), token.getIdToken(), sid, oidcSessionManager.createSession(client.getId(), authorizationCode.getUserId(), token.getIdToken(), sid,
authorizationCode.getSpringSession()); authorizationCode.getSpringSession());
} catch (JOSEException e) { } catch (JOSEException e) {

View File

@ -23,6 +23,7 @@ import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.OidcTokenManager; import de.bstly.we.oidc.businesslogic.OidcTokenManager;
import de.bstly.we.oidc.model.OidcClient; import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcToken; import de.bstly.we.oidc.model.OidcToken;
import de.bstly.we.security.model.LocalUserDetails;
/** /**
* The Class OidcUserInfoController. * The Class OidcUserInfoController.
@ -50,6 +51,13 @@ public class OidcUserInfoController extends BaseController {
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Long userId = getCurrentUserId(); Long userId = getCurrentUserId();
String alias = null;
if (auth.getPrincipal() != null && auth.getPrincipal() instanceof LocalUserDetails) {
alias = ((LocalUserDetails) auth.getPrincipal()).getAlias();
}
OidcClient client = null; OidcClient client = null;
if (!auth.isAuthenticated()) { if (!auth.isAuthenticated()) {
if (authorizationHeader == null) { if (authorizationHeader == null) {
@ -63,6 +71,7 @@ public class OidcUserInfoController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNAUTHORIZED); throw new EntityResponseStatusException(HttpStatus.UNAUTHORIZED);
} }
userId = token.getUserId(); userId = token.getUserId();
alias = token.getAlias();
client = oidcClientManager.get(token.getClient()); client = oidcClientManager.get(token.getClient());
} }
@ -72,7 +81,7 @@ public class OidcUserInfoController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.CONFLICT); throw new EntityResponseStatusException(HttpStatus.CONFLICT);
} }
Builder claimsSetBuilder = oidcTokenManager.createUserClaims(client, user); Builder claimsSetBuilder = oidcTokenManager.createUserClaims(client, user, alias);
throw new EntityResponseStatusException(claimsSetBuilder.build().toJSONObject(), HttpStatus.OK); throw new EntityResponseStatusException(claimsSetBuilder.build().toJSONObject(), HttpStatus.OK);

View File

@ -71,6 +71,8 @@ public class OidcClient {
private boolean backchannelLogoutSessionRequired; private boolean backchannelLogoutSessionRequired;
@Column(name = "authorize", columnDefinition = "boolean default false") @Column(name = "authorize", columnDefinition = "boolean default false")
private boolean authorize; private boolean authorize;
@Column(name = "alias_allowed", columnDefinition = "boolean default false")
private boolean aliasAllowed;
@Column(name = "always_permitted", columnDefinition = "boolean default false") @Column(name = "always_permitted", columnDefinition = "boolean default false")
private boolean alwaysPermitted; private boolean alwaysPermitted;
@Column(name = "category") @Column(name = "category")
@ -348,6 +350,20 @@ public class OidcClient {
this.authorize = authorize; this.authorize = authorize;
} }
/**
* @return the aliasAllowed
*/
public boolean isAliasAllowed() {
return aliasAllowed;
}
/**
* @param aliasAllowed the aliasAllowed to set
*/
public void setAliasAllowed(boolean aliasAllowed) {
this.aliasAllowed = aliasAllowed;
}
/** /**
* Checks if is always permitted. * Checks if is always permitted.
* *

View File

@ -34,6 +34,8 @@ public class OidcToken {
private Instant created; private Instant created;
@Column(name = "user_id") @Column(name = "user_id")
private Long userId; private Long userId;
@Column(name = "alias")
private String alias;
@Column(name = "client_id") @Column(name = "client_id")
private Long client; private Long client;
@Column(name = "access_token") @Column(name = "access_token")
@ -103,6 +105,20 @@ public class OidcToken {
this.userId = userId; this.userId = userId;
} }
/**
* @return the alias
*/
public String getAlias() {
return alias;
}
/**
* @param alias the alias to set
*/
public void setAlias(String alias) {
this.alias = alias;
}
/** /**
* Gets the client. * Gets the client.
* *

View File

@ -13,7 +13,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version> <java.version>11</java.version>
<log4j2.version>2.19.0</log4j2.version> <log4j2.version>2.19.0</log4j2.version>
<revision>2.0.2-SNAPSHOT</revision> <revision>2.0.3-SNAPSHOT</revision>
</properties> </properties>
<parent> <parent>