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);
}
/**
*
* @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.
*

View File

@ -4,6 +4,7 @@
package de.bstly.we.controller;
import java.io.IOException;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -13,7 +14,9 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.Errors;
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.RestController;
import de.bstly.we.businesslogic.UserAliasManager;
import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.controller.model.PasswordResetModel;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.controller.support.RequestBodyErrors;
import de.bstly.we.controller.validation.PasswordModelValidator;
import de.bstly.we.model.User;
import de.bstly.we.security.model.LocalUserDetails;
/**
* The Class AuthenticationController.
@ -40,6 +45,8 @@ public class AuthenticationController extends BaseController {
private UserManager userManager;
@Autowired
private PasswordModelValidator passwordModelValidator;
@Autowired
private UserAliasManager userAliasManager;
/**
* Me.
@ -102,7 +109,25 @@ public class AuthenticationController extends BaseController {
user = userManager.setPassword(user.getId(), passwordResetModel.getPassword());
user.setResetToken(null);
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;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
@ -92,6 +93,23 @@ public class PermissionManagementController extends BaseController {
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.
*
@ -116,13 +134,15 @@ public class PermissionManagementController extends BaseController {
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@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();
for (Permission permission : permissions) {
if (permission.getId() == null) {
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
}
permission.setStarts(starts.orElse(permission.getStarts()));
permission.setExpires(expires.orElse(permission.getExpires()));
result.add(permissionManager.update(permission));
}
return result;

View File

@ -109,6 +109,22 @@ public class QuotaManagementController extends BaseController {
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.
*
@ -133,7 +149,7 @@ public class QuotaManagementController extends BaseController {
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@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();
@ -141,6 +157,7 @@ public class QuotaManagementController extends BaseController {
if (quotaManager.get(quota.getTarget(), quota.getName()) == null) {
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
}
quota.setValue(value.orElse(quota.getValue()));
result.add(quotaManager.update(quota));
}

View File

@ -18,6 +18,7 @@ public class LocalUserDetails extends User {
*/
private static final long serialVersionUID = 1L;
private Long userId;
private String alias;
/**
* Instantiates a new local user details.
@ -52,4 +53,18 @@ public class LocalUserDetails extends User {
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;
}
/**
*
* @param authorizationCode
* @return
*/
public OidcAuthorizationCode setCode(OidcAuthorizationCode authorizationCode) {
authorizationCodes.put(authorizationCode.getCode(), authorizationCode);
return authorizationCode;
}
/**
* Gets the code.
*

View File

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

View File

@ -26,6 +26,7 @@ public class OidcAuthorizationCode {
private final Long userId;
private final String nonce;
private String springSession;
private String alias;
/**
* Instantiates a new oidc authorization code.
@ -127,4 +128,18 @@ public class OidcAuthorizationCode {
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.Permissions;
import de.bstly.we.businesslogic.UserAliasManager;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationManager;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.exception.InvalidAuthorizationRequestException;
@ -56,6 +57,8 @@ public class OidcAuthorizationController {
private OidcClientManager oidcClientManager;
@Autowired
private OidcAuthorizationManager oidcAuthorizationManager;
@Autowired
private UserAliasManager userAliasManager;
@Value("${oidcAuthorizationFormUrl:/oidc/authorize/form}")
private String authorizationFormUrl;
@ -169,16 +172,30 @@ public class OidcAuthorizationController {
"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);
} else {
if (client.isAuthorize()
&& !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.queryParam("client_id", clientId);
uriBuilder.queryParam("response_type", responseType);
uriBuilder.queryParam("redirect_uri", redirectUri);
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)) {
uriBuilder.queryParam("nonce", nonce);

View File

@ -176,7 +176,7 @@ public class OidcTokenController {
try {
String sid = oidcSessionManager.createSid();
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,
authorizationCode.getSpringSession());
} 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.model.OidcClient;
import de.bstly.we.oidc.model.OidcToken;
import de.bstly.we.security.model.LocalUserDetails;
/**
* The Class OidcUserInfoController.
@ -50,6 +51,13 @@ public class OidcUserInfoController extends BaseController {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Long userId = getCurrentUserId();
String alias = null;
if (auth.getPrincipal() != null && auth.getPrincipal() instanceof LocalUserDetails) {
alias = ((LocalUserDetails) auth.getPrincipal()).getAlias();
}
OidcClient client = null;
if (!auth.isAuthenticated()) {
if (authorizationHeader == null) {
@ -63,6 +71,7 @@ public class OidcUserInfoController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNAUTHORIZED);
}
userId = token.getUserId();
alias = token.getAlias();
client = oidcClientManager.get(token.getClient());
}
@ -72,7 +81,7 @@ public class OidcUserInfoController extends BaseController {
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);

View File

@ -71,6 +71,8 @@ public class OidcClient {
private boolean backchannelLogoutSessionRequired;
@Column(name = "authorize", columnDefinition = "boolean default false")
private boolean authorize;
@Column(name = "alias_allowed", columnDefinition = "boolean default false")
private boolean aliasAllowed;
@Column(name = "always_permitted", columnDefinition = "boolean default false")
private boolean alwaysPermitted;
@Column(name = "category")
@ -348,6 +350,20 @@ public class OidcClient {
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.
*

View File

@ -34,6 +34,8 @@ public class OidcToken {
private Instant created;
@Column(name = "user_id")
private Long userId;
@Column(name = "alias")
private String alias;
@Column(name = "client_id")
private Long client;
@Column(name = "access_token")
@ -103,6 +105,20 @@ public class OidcToken {
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.
*

View File

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