update oidc+partey

This commit is contained in:
_Bastler 2022-05-12 09:32:54 +02:00
parent eb829bfa26
commit b876bad3e7
42 changed files with 3061 additions and 440 deletions

View File

@ -0,0 +1,67 @@
/**
*
*/
package de.bstly.we.businesslogic;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.stereotype.Component;
import com.google.common.collect.Sets;
import de.bstly.we.model.User;
import de.bstly.we.security.LocalRememberMeServices;
import de.bstly.we.security.LocalRememberMeServices.ConcurrentToken;
/**
* The Class SessionManager.
*/
@Component
public class SessionManager {
@Autowired
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
@Autowired
private LocalRememberMeServices rememberMeServices;
/**
* Delete sessions for user.
*
* @param user the user
*/
public void deleteSessionsForUser(User user) {
Map<String, ? extends Session> usersSessions = sessionRepository.findByPrincipalName(user.getUsername());
for (Session session : usersSessions.values()) {
if (session.getAttribute("userId") != null && session.getAttribute("userId").equals(user.getId())) {
sessionRepository.deleteById(session.getId());
}
}
}
/**
* Concurrent clean up.
*/
@Scheduled(cron = "${rememberme.concurrent.cron:0 */5 * * * * }")
public void concurrentCleanUp() {
Set<String> delete = Sets.newHashSet();
for (Entry<String, ConcurrentToken> entry : rememberMeServices.getConcurrentTokens().entrySet()) {
if (Instant.now().minus(rememberMeServices.getConcurrentTimeout(), ChronoUnit.SECONDS)
.isAfter(entry.getValue().getRenewed())) {
delete.add(entry.getKey());
}
}
for (String key : delete) {
rememberMeServices.getConcurrentTokens().remove(key);
}
}
}

View File

@ -39,8 +39,8 @@ import de.bstly.we.model.PermissionMapping;
import de.bstly.we.model.ProfileFieldType; import de.bstly.we.model.ProfileFieldType;
import de.bstly.we.model.User; import de.bstly.we.model.User;
import de.bstly.we.model.UserProfileField; import de.bstly.we.model.UserProfileField;
import de.bstly.we.model.Visibility;
import de.bstly.we.model.UserStatus; import de.bstly.we.model.UserStatus;
import de.bstly.we.model.Visibility;
/** /**
* The Class UserController. * The Class UserController.

View File

@ -3,17 +3,42 @@
*/ */
package de.bstly.we.security; package de.bstly.we.security;
import javax.servlet.http.HttpServletRequest; import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.log.LogMessage;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.CookieTheftException;
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices; import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
import com.google.common.collect.Maps;
/** /**
* The Class LocalRememberMeServices. * The Class LocalRememberMeServices.
*/ */
public class LocalRememberMeServices extends PersistentTokenBasedRememberMeServices { public class LocalRememberMeServices extends PersistentTokenBasedRememberMeServices {
private PersistentTokenRepository tokenRepository;
@Value("${rememberme.concurrent.enabled:true}")
private boolean concurrent;
@Value("${rememberme.concurrent.timeout:120}")
private long concurrentTimeout; // default 2 minutes
private Map<String, ConcurrentToken> concurrentTokens = Maps.newHashMap();
/** /**
* Instantiates a new local remember me services. * Instantiates a new local remember me services.
* *
@ -24,6 +49,7 @@ public class LocalRememberMeServices extends PersistentTokenBasedRememberMeServi
public LocalRememberMeServices(String key, UserDetailsService userDetailsService, public LocalRememberMeServices(String key, UserDetailsService userDetailsService,
PersistentTokenRepository tokenRepository) { PersistentTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository); super(key, userDetailsService, tokenRepository);
this.tokenRepository = tokenRepository;
} }
/* /*
@ -45,4 +71,174 @@ public class LocalRememberMeServices extends PersistentTokenBasedRememberMeServi
return super.rememberMeRequested(request, parameter); return super.rememberMeRequested(request, parameter);
} }
@Override
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
HttpServletResponse response) {
if (cookieTokens.length != 2) {
throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '"
+ Arrays.asList(cookieTokens) + "'");
}
String presentedSeries = cookieTokens[0];
String presentedToken = cookieTokens[1];
PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
if (token == null) {
// No series match, so we can't authenticate using this cookie
throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
}
boolean concurrentToken = false;
String newTokenData = generateTokenData();
// We have a match for this user/series combination
this.logger.trace("concurrentTokens: " + concurrentTokens);
if (!presentedToken.equals(token.getTokenValue())) {
if (concurrent && concurrentTokens.containsKey(presentedToken)) {
this.logger.debug("found token '" + presentedToken + "' in concurrent tokens");
if (concurrentTokens.get(presentedToken).getSeries().equals(presentedSeries)) {
this.logger.debug("found series '" + presentedSeries + "' in concurrent tokens");
if (Instant.now().minus(concurrentTimeout, ChronoUnit.SECONDS)
.isBefore(concurrentTokens.get(presentedToken).getRenewed())) {
newTokenData = token.getTokenValue();
concurrentToken = true;
} else {
this.logger.debug("cocurrent token expired!");
concurrentTokens.remove(presentedToken);
}
}
}
if (!concurrentToken) {
// Token doesn't match series value. Delete all logins for this user and throw
// an exception to warn them.
this.tokenRepository.removeUserTokens(token.getUsername());
throw new CookieTheftException(this.messages.getMessage(
"PersistentTokenBasedRememberMeServices.cookieStolen",
"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
}
}
if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
throw new RememberMeAuthenticationException("Remember-me login has expired");
}
// Token also matches, so login is valid. Update the token value, keeping the
// *same* series number.
this.logger.debug(LogMessage.format("Refreshing persistent login token for user '%s', series '%s'",
token.getUsername(), token.getSeries()));
if (!concurrentToken && !concurrentTokens.containsKey(presentedToken)) {
concurrentTokens.put(presentedToken, new ConcurrentToken(presentedSeries, Instant.now()));
}
PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(),
newTokenData, new Date());
try {
this.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());
addCookie(newToken, request, response);
} catch (Exception ex) {
this.logger.error("Failed to update token: ", ex);
throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
}
return getUserDetailsService().loadUserByUsername(token.getUsername());
}
/**
* Removes the user tokens.
*
* @param username the username
*/
public void removeUserTokens(String username) {
tokenRepository.removeUserTokens(username);
}
/**
* Adds the cookie.
*
* @param token the token
* @param request the request
* @param response the response
*/
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) {
setCookie(new String[] { token.getSeries(), token.getTokenValue() }, getTokenValiditySeconds(), request,
response);
}
/**
* Gets the concurrent tokens.
*
* @return the concurrent tokens
*/
public Map<String, ConcurrentToken> getConcurrentTokens() {
if (concurrentTokens == null) {
concurrentTokens = Maps.newHashMap();
}
return concurrentTokens;
}
/**
* Gets the concurrent timeout.
*
* @return the concurrent timeout
*/
public long getConcurrentTimeout() {
return concurrentTimeout;
}
/**
* The Class ConcurrentToken.
*/
public static class ConcurrentToken {
private String series;
private Instant renewed;
/**
* Instantiates a new concurrent token.
*
* @param series the series
* @param renewed the renewed
*/
public ConcurrentToken(String series, Instant renewed) {
super();
this.series = series;
this.renewed = renewed;
}
/**
* Gets the series.
*
* @return the series
*/
public String getSeries() {
return series;
}
/**
* Sets the series.
*
* @param series the new series
*/
public void setSeries(String series) {
this.series = series;
}
/**
* Gets the renewed.
*
* @return the renewed
*/
public Instant getRenewed() {
return renewed;
}
/**
* Sets the renewed.
*
* @param renewed the new renewed
*/
public void setRenewed(Instant renewed) {
this.renewed = renewed;
}
}
} }

View File

@ -149,27 +149,21 @@ public class JukeboxManager implements SmartInitializingSingleton {
if (!StringUtils.hasText(config.getAccessToken()) || config.getExpires() == null if (!StringUtils.hasText(config.getAccessToken()) || config.getExpires() == null
|| config.getExpires().isBefore(Instant.now())) { || config.getExpires().isBefore(Instant.now())) {
String authHeader = "Basic " String authHeader = "Basic " + new String(Base64.getEncoder()
+ new String(Base64.getEncoder() .encode(String.format("%s:%s", config.getClientId(), config.getClientSecret()).getBytes()));
.encode(String WebClient.RequestBodySpec request = WebClient.builder().baseUrl(config.getAccountUrl()).build()
.format("%s:%s", config.getClientId(), config.getClientSecret()) .method(HttpMethod.POST).uri(uriBuilder -> uriBuilder.path("/api/token").build())
.getBytes()));
WebClient.RequestBodySpec request = WebClient.builder().baseUrl(config.getAccountUrl())
.build().method(HttpMethod.POST)
.uri(uriBuilder -> uriBuilder.path("/api/token").build())
.header(HttpHeaders.AUTHORIZATION, authHeader) .header(HttpHeaders.AUTHORIZATION, authHeader)
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
request.bodyValue("grant_type=refresh_token&refresh_token=" request.bodyValue("grant_type=refresh_token&refresh_token=" + config.getRefreshToken());
+ config.getRefreshToken());
String jsonString = request.retrieve().bodyToMono(String.class).block(); String jsonString = request.retrieve().bodyToMono(String.class).block();
if (StringUtils.hasText(jsonString)) { if (StringUtils.hasText(jsonString)) {
JsonObject response = JsonParser.parseString(jsonString).getAsJsonObject(); JsonObject response = JsonParser.parseString(jsonString).getAsJsonObject();
config.setAccessToken(response.get("access_token").getAsString()); config.setAccessToken(response.get("access_token").getAsString());
config.setExpires(Instant.now().plus(response.get("expires_in").getAsLong(), config.setExpires(Instant.now().plus(response.get("expires_in").getAsLong(), ChronoUnit.SECONDS));
ChronoUnit.SECONDS));
if (response.has("refresh_token")) { if (response.has("refresh_token")) {
config.setRefreshToken(response.get("refresh_token").getAsString()); config.setRefreshToken(response.get("refresh_token").getAsString());
@ -197,8 +191,7 @@ public class JukeboxManager implements SmartInitializingSingleton {
WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET) WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET)
.uri(uriBuilder -> uriBuilder.path("/v1/me/player").build()) .uri(uriBuilder -> uriBuilder.path("/v1/me/player").build())
.header(HttpHeaders.AUTHORIZATION, "Bearer " .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
+ config.getAccessToken());
String jsonString = request.retrieve().bodyToMono(String.class).block(); String jsonString = request.retrieve().bodyToMono(String.class).block();
@ -222,11 +215,9 @@ public class JukeboxManager implements SmartInitializingSingleton {
device_ids.add(config.getDeviceId()); device_ids.add(config.getDeviceId());
queryParameters.add("device_ids", device_ids.toString()); queryParameters.add("device_ids", device_ids.toString());
queryParameters.add("play", "true"); queryParameters.add("play", "true");
request = webClient.method(HttpMethod.PUT) request = webClient.method(HttpMethod.PUT).uri(
.uri(uriBuilder -> uriBuilder.path("/v1/me/player") uriBuilder -> uriBuilder.path("/v1/me/player").queryParams(queryParameters).build())
.queryParams(queryParameters).build()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
.header(HttpHeaders.AUTHORIZATION, "Bearer "
+ config.getAccessToken());
request.retrieve().bodyToMono(String.class).block(); request.retrieve().bodyToMono(String.class).block();
return getStatus(); return getStatus();
@ -280,19 +271,15 @@ public class JukeboxManager implements SmartInitializingSingleton {
queryParameters.add("context_uri", context_uri); queryParameters.add("context_uri", context_uri);
} }
WebClient.RequestBodySpec request = webClient.method(HttpMethod.PUT) WebClient.RequestBodySpec request = webClient.method(HttpMethod.PUT)
.uri(uriBuilder -> uriBuilder.path("/v1/me/player/play") .uri(uriBuilder -> uriBuilder.path("/v1/me/player/play").queryParams(queryParameters).build())
.queryParams(queryParameters).build()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
.header(HttpHeaders.AUTHORIZATION, "Bearer "
+ config.getAccessToken());
try { try {
request.retrieve().bodyToMono(String.class).block(); request.retrieve().bodyToMono(String.class).block();
logger.debug("started playback"); logger.debug("started playback");
} catch (Exception e) { } catch (Exception e) {
if (!StringUtils.hasText(context_uri) if (!StringUtils.hasText(context_uri) && StringUtils.hasText(config.getFallbackContextId())) {
&& StringUtils.hasText(config.getFallbackContextId())) { logger.debug("retry to start playback with: " + config.getFallbackContextId());
logger.debug("retry to start playback with: "
+ config.getFallbackContextId());
tryStartPlayback(config.getFallbackContextId()); tryStartPlayback(config.getFallbackContextId());
} else { } else {
config.setActive(false); config.setActive(false);
@ -321,13 +308,12 @@ public class JukeboxManager implements SmartInitializingSingleton {
if (offset != null) { if (offset != null) {
queryParameters.add("offset", String.valueOf(offset)); queryParameters.add("offset", String.valueOf(offset));
} }
WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET).uri( WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET)
uriBuilder -> uriBuilder.path("/v1/search").queryParams(queryParameters).build()) .uri(uriBuilder -> uriBuilder.path("/v1/search").queryParams(queryParameters).build())
.header(HttpHeaders.AUTHORIZATION, "Bearer " .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
+ config.getAccessToken());
String jsonString = request.retrieve().bodyToMono(String.class) String jsonString = request.retrieve().bodyToMono(String.class).onErrorResume(e -> Mono.just(e.getMessage()))
.onErrorResume(e -> Mono.just(e.getMessage())).block(); .block();
if (StringUtils.hasText(jsonString)) { if (StringUtils.hasText(jsonString)) {
return JsonParser.parseString(jsonString); return JsonParser.parseString(jsonString);
@ -352,14 +338,12 @@ public class JukeboxManager implements SmartInitializingSingleton {
if (limit != null) { if (limit != null) {
queryParameters.add("limit", String.valueOf(limit)); queryParameters.add("limit", String.valueOf(limit));
} }
WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET) WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET).uri(
.uri(uriBuilder -> uriBuilder.path("/v1/me/player/recently-played") uriBuilder -> uriBuilder.path("/v1/me/player/recently-played").queryParams(queryParameters).build())
.queryParams(queryParameters).build()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
.header(HttpHeaders.AUTHORIZATION, "Bearer "
+ config.getAccessToken());
String jsonString = request.retrieve().bodyToMono(String.class) String jsonString = request.retrieve().bodyToMono(String.class).onErrorResume(e -> Mono.just(e.getMessage()))
.onErrorResume(e -> Mono.just(e.getMessage())).block(); .block();
if (StringUtils.hasText(jsonString)) { if (StringUtils.hasText(jsonString)) {
return JsonParser.parseString(jsonString); return JsonParser.parseString(jsonString);
@ -396,10 +380,8 @@ public class JukeboxManager implements SmartInitializingSingleton {
MultiValueMap<String, String> queryParameters = new LinkedMultiValueMap<String, String>(); MultiValueMap<String, String> queryParameters = new LinkedMultiValueMap<String, String>();
queryParameters.add("device_id", config.getDeviceId()); queryParameters.add("device_id", config.getDeviceId());
WebClient.RequestBodySpec request = webClient.method(HttpMethod.PUT) WebClient.RequestBodySpec request = webClient.method(HttpMethod.PUT)
.uri(uriBuilder -> uriBuilder.path("/v1/me/player/play") .uri(uriBuilder -> uriBuilder.path("/v1/me/player/play").queryParams(queryParameters).build())
.queryParams(queryParameters).build()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
.header(HttpHeaders.AUTHORIZATION, "Bearer "
+ config.getAccessToken());
JsonObject body = new JsonObject(); JsonObject body = new JsonObject();
if (StringUtils.hasText(config.getFallbackContextId())) { if (StringUtils.hasText(config.getFallbackContextId())) {
@ -419,10 +401,8 @@ public class JukeboxManager implements SmartInitializingSingleton {
queryParameters.add("uri", uri); queryParameters.add("uri", uri);
queryParameters.add("device_id", config.getDeviceId()); queryParameters.add("device_id", config.getDeviceId());
WebClient.RequestBodySpec request = webClient.method(HttpMethod.POST) WebClient.RequestBodySpec request = webClient.method(HttpMethod.POST)
.uri(uriBuilder -> uriBuilder.path("/v1/me/player/queue") .uri(uriBuilder -> uriBuilder.path("/v1/me/player/queue").queryParams(queryParameters).build())
.queryParams(queryParameters).build()) .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
.header(HttpHeaders.AUTHORIZATION, "Bearer "
+ config.getAccessToken());
request.retrieve().bodyToMono(String.class).block(); request.retrieve().bodyToMono(String.class).block();
} }
@ -468,8 +448,7 @@ public class JukeboxManager implements SmartInitializingSingleton {
WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET) WebClient.RequestBodySpec request = webClient.method(HttpMethod.GET)
.uri(uriBuilder -> uriBuilder.path("/v1/me/player/devices").build()) .uri(uriBuilder -> uriBuilder.path("/v1/me/player/devices").build())
.header(HttpHeaders.AUTHORIZATION, "Bearer " .header(HttpHeaders.AUTHORIZATION, "Bearer " + config.getAccessToken());
+ config.getAccessToken());
boolean deviceFound = false; boolean deviceFound = false;
@ -482,8 +461,7 @@ public class JukeboxManager implements SmartInitializingSingleton {
JsonArray devices = response.getAsJsonObject().getAsJsonArray("devices"); JsonArray devices = response.getAsJsonObject().getAsJsonArray("devices");
for (JsonElement deviceElement : devices) { for (JsonElement deviceElement : devices) {
JsonObject device = deviceElement.getAsJsonObject(); JsonObject device = deviceElement.getAsJsonObject();
if (device.has("id") if (device.has("id") && device.get("id").getAsString().equals(config.getDeviceId())) {
&& device.get("id").getAsString().equals(config.getDeviceId())) {
deviceFound = true; deviceFound = true;
return; return;
} }
@ -496,8 +474,7 @@ public class JukeboxManager implements SmartInitializingSingleton {
try { try {
Runtime.getRuntime().exec(command); Runtime.getRuntime().exec(command);
} catch (IOException e) { } catch (IOException e) {
logger.warn("Could execute command: " logger.warn("Could execute command: " + command, e);
+ command, e);
return; return;
} }

View File

@ -70,8 +70,8 @@ public class JukeboxController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.GONE); throw new EntityResponseStatusException(HttpStatus.GONE);
} }
if (queueList.containsKey(getCurrentUserId()) && queueList.get(getCurrentUserId()) if (queueList.containsKey(getCurrentUserId())
.isAfter(Instant.now().minus(1, ChronoUnit.MINUTES))) { && queueList.get(getCurrentUserId()).isAfter(Instant.now().minus(1, ChronoUnit.MINUTES))) {
throw new EntityResponseStatusException( throw new EntityResponseStatusException(
Duration.between(queueList.get(getCurrentUserId()), Instant.now()).getSeconds(), Duration.between(queueList.get(getCurrentUserId()), Instant.now()).getSeconds(),
HttpStatus.PAYMENT_REQUIRED); HttpStatus.PAYMENT_REQUIRED);
@ -92,10 +92,10 @@ public class JukeboxController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.GONE); throw new EntityResponseStatusException(HttpStatus.GONE);
} }
if (searchList.containsKey(getCurrentUserId()) && searchList.get(getCurrentUserId()) if (searchList.containsKey(getCurrentUserId())
.isAfter(Instant.now().minus(3, ChronoUnit.SECONDS))) { && searchList.get(getCurrentUserId()).isAfter(Instant.now().minus(3, ChronoUnit.SECONDS))) {
throw new EntityResponseStatusException(Duration throw new EntityResponseStatusException(
.between(searchList.get(getCurrentUserId()), Instant.now()).getSeconds(), Duration.between(searchList.get(getCurrentUserId()), Instant.now()).getSeconds(),
HttpStatus.PAYMENT_REQUIRED); HttpStatus.PAYMENT_REQUIRED);
} }
} }
@ -137,9 +137,8 @@ public class JukeboxController extends BaseController {
*/ */
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@GetMapping("/search") @GetMapping("/search")
public void search(@RequestParam("q") String query, public void search(@RequestParam("q") String query, @RequestParam("offset") Optional<Long> offset,
@RequestParam("offset") Optional<Long> offset, HttpServletResponse response) HttpServletResponse response) throws JsonIOException, IOException {
throws JsonIOException, IOException {
checkSearchPermission(); checkSearchPermission();
response.setContentType("application/json"); response.setContentType("application/json");
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");

View File

@ -110,9 +110,8 @@ public class JukeboxManagementController extends BaseController {
*/ */
@PreAuthorize("hasRole('ROLE_ADMIN')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/search") @GetMapping("/search")
public void search(@RequestParam("q") String query, public void search(@RequestParam("q") String query, @RequestParam("offset") Optional<Long> offset,
@RequestParam("offset") Optional<Long> offset, HttpServletResponse response) HttpServletResponse response) throws JsonIOException, IOException {
throws JsonIOException, IOException {
response.setContentType("application/json"); response.setContentType("application/json");
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
gson.toJson(jukeboxManager.searchTrack(query, offset.orElse(null)), response.getWriter()); gson.toJson(jukeboxManager.searchTrack(query, offset.orElse(null)), response.getWriter());

View File

@ -11,7 +11,7 @@ import org.springframework.stereotype.Service;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import de.bstly.we.oidc.model.OidcAuthorizationCode; import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
/** /**
* The Class OidcAuthorizationCodeManager. * The Class OidcAuthorizationCodeManager.

View File

@ -0,0 +1,141 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
import de.bstly.we.oidc.model.OidcAuthorization;
import de.bstly.we.oidc.model.QOidcAuthorization;
import de.bstly.we.oidc.repository.OidcAuthorizationRepository;
/**
* The Class OidcAuthorizationManager.
*/
@Service
public class OidcAuthorizationManager {
@Autowired
private OidcAuthorizationRepository oidcAuthorizationRepository;
private QOidcAuthorization qOidcAuthorization = QOidcAuthorization.oidcAuthorization;
/**
* hold codes in memory
*/
private final Map<String, OidcAuthorizationCode> authorizationCodes = Maps.newHashMap();
/**
* Gets the authorization.
*
* @param clientId the client id
* @param subject the subject
* @return the authorization
*/
public OidcAuthorization getAuthorization(Long clientId, Long subject) {
return oidcAuthorizationRepository
.findOne(qOidcAuthorization.client.eq(clientId).and(qOidcAuthorization.subject.eq(subject)))
.orElse(null);
}
/**
* Checks if is authorized.
*
* @param clientId the client id
* @param subject the subject
* @param scopes the scopes
* @return true, if is authorized
*/
public boolean isAuthorized(Long clientId, Long subject, Set<String> scopes) {
OidcAuthorization oidcAuthorization = getAuthorization(clientId, subject);
if (oidcAuthorization == null) {
return false;
}
return scopes == null
|| oidcAuthorization.getScopes() != null && oidcAuthorization.getScopes().containsAll(scopes);
}
/**
* Authorize.
*
* @param clientId the client id
* @param subject the subject
* @param scopes the scopes
* @return the oidc authorization
*/
public OidcAuthorization authorize(Long clientId, Long subject, Set<String> scopes) {
OidcAuthorization oidcAuthorization = getAuthorization(clientId, subject);
if (oidcAuthorization == null) {
oidcAuthorization = new OidcAuthorization();
oidcAuthorization.setClient(clientId);
oidcAuthorization.setSubject(subject);
oidcAuthorization.setScopes(Sets.newHashSet());
}
oidcAuthorization.getScopes().addAll(scopes);
return oidcAuthorizationRepository.save(oidcAuthorization);
}
/**
* Removes the authorization.
*
* @param clientId the client id
* @param subject the subject
*/
public void removeAuthorization(Long clientId, Long subject) {
OidcAuthorization oidcAuthorization = getAuthorization(clientId, subject);
if (oidcAuthorization != null) {
oidcAuthorizationRepository.delete(oidcAuthorization);
}
}
/**
* Creates the code.
*
* @param clientId the client id
* @param redirectUri the redirect uri
* @param scopes the scopes
* @param subject the subject
* @param nonce the nonce
* @param springSession the spring session
* @return the oidc authorization code
*/
public OidcAuthorizationCode createCode(String clientId, URI redirectUri, Set<String> scopes, Long subject,
String nonce, String springSession) {
OidcAuthorizationCode authorizationCode = new OidcAuthorizationCode(clientId, redirectUri, scopes, subject,
nonce);
authorizationCode.setSpringSession(springSession);
authorizationCodes.put(authorizationCode.getCode(), authorizationCode);
return authorizationCode;
}
/**
* Gets the code.
*
* @param code the code
* @return the code
*/
public OidcAuthorizationCode getCode(String code) {
return authorizationCodes.get(code);
}
/**
* Removes the code.
*
* @param code the code
*/
public void removeCode(String code) {
authorizationCodes.remove(code);
}
}

View File

@ -5,19 +5,26 @@ package de.bstly.we.oidc.businesslogic;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType; import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.controller.model.OidcClientInfo;
import de.bstly.we.oidc.model.OidcAuthorization;
import de.bstly.we.oidc.model.OidcClient; import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.model.QOidcClient; import de.bstly.we.oidc.model.QOidcClient;
import de.bstly.we.oidc.repository.OidcClientRepository; import de.bstly.we.oidc.repository.OidcClientRepository;
@ -33,8 +40,32 @@ public class OidcClientManager {
@Autowired @Autowired
private OidcClientRepository oidcClientRepository; private OidcClientRepository oidcClientRepository;
@Autowired
private OidcAuthorizationManager oidcAuthorizationManager;
private QOidcClient qOidcClient = QOidcClient.oidcClient; private QOidcClient qOidcClient = QOidcClient.oidcClient;
@Value("${oidc.provider.issuer:}")
private String oidcIssuer;
/**
* Gets the issuer.
*
* @param request the request
* @return the issuer
*/
public String getIssuer(HttpServletRequest request) {
String issuer = oidcIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme() + "://" + request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":" + request.getServerPort();
}
}
return issuer;
}
/** /**
* Creates the client. * Creates the client.
* *
@ -112,6 +143,16 @@ public class OidcClientManager {
return oidcClientRepository.save(oidcClient); return oidcClientRepository.save(oidcClient);
} }
/**
* Gets the.
*
* @param id the id
* @return the oidc client
*/
public OidcClient get(Long id) {
return oidcClientRepository.findById(id).orElse(null);
}
/** /**
* Gets the by client id. * Gets the by client id.
* *
@ -180,4 +221,38 @@ public class OidcClientManager {
Sort sort = descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending(); Sort sort = descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending();
return oidcClientRepository.findAll(PageRequest.of(page, size, sort)); return oidcClientRepository.findAll(PageRequest.of(page, size, sort));
} }
/**
* Gets the client info.
*
* @param client the client
* @param subject the subject
* @return the client info
*/
public OidcClientInfo getClientInfo(OidcClient client, Long subject) {
OidcClientInfo info = new OidcClientInfo();
info.setClientId(client.getClientId());
info.setName(client.getClientName());
info.setLoginUrl(client.getLoginUrl());
info.setFrontchannelLogoutUri(client.getFrontchannelLogoutUri());
info.setFrontchannelLogoutSessionRequired(client.isFrontchannelLogoutSessionRequired());
info.setBackchannelLogout(StringUtils.hasText(client.getBackchannelLogoutUri()));
info.setAuthorize(client.isAuthorize());
info.setScopes(client.getScopes());
info.setSessions(Lists.newArrayList());
if (info.getScopes() == null) {
info.setScopes(Sets.newHashSet());
}
OidcAuthorization authorization = oidcAuthorizationManager.getAuthorization(client.getId(), subject);
if (authorization != null) {
info.setAuthorizedScopes(authorization.getScopes());
}
return info;
}
} }

View File

@ -0,0 +1,442 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import de.bstly.we.jwt.businesslogic.JwtKeyManager;
import de.bstly.we.jwt.model.JwtKey;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcSession;
import de.bstly.we.oidc.model.QOidcSession;
import de.bstly.we.oidc.repository.OidcSessionRepository;
import reactor.core.publisher.Mono;
/**
* The Class OidcSessionManager.
*/
@Service
public class OidcSessionManager {
private Logger logger = LoggerFactory.getLogger(OidcSessionManager.class);
@Autowired
private OidcSessionRepository oidcSessionRepository;
@Autowired
private OidcClientManager oidcRegisteredClientManager;
@Autowired
private JwtKeyManager jwtKeyManager;
protected WebClient webClient = WebClient.builder().build();
private QOidcSession qOidcSession = QOidcSession.oidcSession;
public static final int SID_LENGTH = 32;
public static final int JWT_ID_LENGTH = 32;
/**
* Creates the sid.
*
* @return the string
*/
public String createSid() {
String sid = new StringBuilder(RandomStringUtils.random(SID_LENGTH, true, true)).insert(8, "-").insert(13, "-")
.insert(18, "-").insert(23, "-").toString();
while (oidcSessionRepository.exists(qOidcSession.sid.eq(sid))) {
sid = new StringBuilder(RandomStringUtils.random(SID_LENGTH, true, true)).insert(8, "-").insert(13, "-")
.insert(18, "-").insert(23, "-").toString();
}
return sid;
}
/**
* Creates the session.
*
* @param clientId the client id
* @param subject the subject
* @param idToken the id token
* @param sid the sid
* @param springSession the spring session
* @return the oidc session
* @throws JOSEException the JOSE exception
*/
public OidcSession createSession(Long clientId, Long subject, String idToken, String sid, String springSession)
throws JOSEException {
OidcSession session = new OidcSession();
session.setClientId(clientId);
session.setSubject(subject);
session.setIdToken(idToken);
session.setSid(sid);
session.setSpringSession(springSession);
return oidcSessionRepository.save(session);
}
/**
* Gets the by sid.
*
* @param sid the sid
* @return the by sid
*/
public OidcSession getBySid(String sid) {
Assert.isTrue(StringUtils.hasText(sid), "No sid defined!");
return oidcSessionRepository.findOne(qOidcSession.sid.eq(sid)).orElse(null);
}
/**
* Gets the by id token.
*
* @param idToken the id token
* @return the by id token
*/
public OidcSession getByIdToken(String idToken) {
return oidcSessionRepository.findOne(qOidcSession.idToken.eq(idToken)).orElse(null);
}
/**
* Gets the all by spring session.
*
* @param springSession the spring session
* @return the all by spring session
*/
public List<OidcSession> getAllBySpringSession(String springSession) {
return Lists.newArrayList(oidcSessionRepository.findAll(qOidcSession.springSession.eq(springSession)));
}
/**
* Gets the all by target.
*
* @param clientId the client id
* @return the all by target
*/
public List<OidcSession> getAllByTarget(Long clientId) {
return Lists.newArrayList(oidcSessionRepository.findAll(qOidcSession.client.eq(clientId)));
}
/**
* Gets the all by subject.
*
* @param subject the subject
* @return the all by subject
*/
public List<OidcSession> getAllBySubject(Long subject) {
return Lists.newArrayList(oidcSessionRepository.findAll(qOidcSession.subject.eq(subject)));
}
/**
* Gets the all by target and subject.
*
* @param clientId the client id
* @param subject the subject
* @return the all by target and subject
*/
public List<OidcSession> getAllByTargetAndSubject(Long clientId, Long subject) {
return Lists.newArrayList(
oidcSessionRepository.findAll(qOidcSession.client.eq(clientId).and(qOidcSession.subject.eq(subject))));
}
/**
* Delete.
*
* @param session the session
*/
public void delete(OidcSession session) {
if (oidcSessionRepository.existsById(session.getId())) {
oidcSessionRepository.delete(session);
}
}
/**
* Delete by sid.
*
* @param sid the sid
*/
public void deleteBySid(String sid) {
OidcSession session = getBySid(sid);
if (session != null) {
delete(session);
}
}
/**
* Delete by id token.
*
* @param idToken the id token
*/
public void deleteByIdToken(String idToken) {
OidcSession session = getByIdToken(idToken);
if (session != null) {
delete(session);
}
}
/**
* Delete all by spring session.
*
* @param springSession the spring session
*/
public void deleteAllBySpringSession(String springSession) {
for (OidcSession token : getAllBySpringSession(springSession)) {
delete(token);
}
}
/**
* Delete all by target.
*
* @param target the target
*/
public void deleteAllByTarget(Long target) {
for (OidcSession token : getAllByTarget(target)) {
delete(token);
}
}
/**
* Delete all by subject.
*
* @param subject the subject
*/
public void deleteAllBySubject(Long subject) {
for (OidcSession token : getAllBySubject(subject)) {
delete(token);
}
}
/**
* Backchannel logout.
*
* @param sid the sid
* @param issuer the issuer
*/
public void backchannelLogout(String sid, String issuer) {
OidcSession session = getBySid(sid);
Assert.notNull(session, "invalid sid!");
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client == null) {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
return;
}
try {
sendBackchannelLogout(session.getSubject(), sid, issuer, client);
oidcSessionRepository.delete(session);
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
}
/**
* Backchannel logout.
*
* @param clientId the client id
* @param subject the subject
* @param issuer the issuer
*/
public void backchannelLogout(Long clientId, Long subject, String issuer) {
List<OidcSession> sessions = getAllByTargetAndSubject(clientId, subject);
if (!sessions.isEmpty()) {
OidcSession session = sessions.get(0);
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client != null) {
try {
sendBackchannelLogout(subject, null, issuer, client);
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
} else {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
}
oidcSessionRepository.deleteAll(sessions);
}
}
/**
* Backchannel logout.
*
* @param subject the subject
* @param issuer the issuer
*/
public void backchannelLogout(Long subject, String issuer) {
Set<Long> clients = Sets.newHashSet();
for (OidcSession session : getAllBySubject(subject)) {
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client == null) {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
continue;
}
if (!clients.contains(client.getId())) {
try {
sendBackchannelLogout(subject, null, issuer, client);
clients.add(client.getId());
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
}
oidcSessionRepository.delete(session);
}
}
/**
* Backchannel logout spring.
*
* @param springSessionId the spring session id
* @param issuer the issuer
*/
public void backchannelLogoutSpring(String springSessionId, String issuer) {
for (OidcSession session : getAllBySpringSession(springSessionId)) {
OidcClient client = oidcRegisteredClientManager.get(session.getClientId());
if (client == null) {
logger.error("OidcSession '" + session.getId() + "' (subject: " + session.getSubject()
+ ") with invalid OidcRegisteredClient: " + session.getClientId());
continue;
}
try {
sendBackchannelLogout(session.getSubject(), session.getSid(), issuer, client);
oidcSessionRepository.delete(session);
} catch (JOSEException e) {
logger.warn("JOSEException on backchannelLogout!", e);
}
}
}
/**
* Send backchannel logout.
*
* @param subject the subject
* @param sid the sid
* @param issuer the issuer
* @param client the client
* @throws JOSEException the JOSE exception
*/
// @Async("threadPoolTaskExecutor")
public void sendBackchannelLogout(Long subject, String sid, String issuer, OidcClient client) throws JOSEException {
if (!StringUtils.hasText(client.getBackchannelLogoutUri())) {
logger.warn("Could not send backchannel logout for '" + subject
+ "' without backchannel_logout_uri of OidcRegisteredClient '" + client.getId()
+ "' not possible!");
return;
}
SignedJWT logoutToken = createBackchannelLogoutToken(subject, sid, issuer, client);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("logout_token", logoutToken.serialize());
ResponseEntity<?> response = null;
try {
response = webClient.method(HttpMethod.POST).uri(client.getBackchannelLogoutUri())
.contentType(MediaType.APPLICATION_FORM_URLENCODED).body(BodyInserters.fromFormData(formData))
.retrieve().onStatus(HttpStatus::isError, error -> Mono.empty()).toBodilessEntity().block();
} catch (Exception e) {
logger.warn("backchannelLogout POST '" + client.getBackchannelLogoutUri() + "' failed", e);
}
if (response != null && !response.getStatusCode().equals(HttpStatus.OK)) {
logger.info("backchannelLogout for '" + subject + "' failed with status: " + response.getStatusCode()
+ " from " + client.getBackchannelLogoutUri() + "(client: " + client.getId() + ")");
}
}
/**
* Creates the backchannel logout token.
*
* @param subject the subject
* @param sid the sid
* @param issuer the issuer
* @param client the client
* @return the signed JWT
* @throws JOSEException the JOSE exception
*/
protected SignedJWT createBackchannelLogoutToken(Long subject, String sid, String issuer, OidcClient client)
throws JOSEException {
if (!StringUtils.hasText(client.getBackchannelLogoutUri())) {
logger.warn("Creation of logout token '" + subject
+ "' without backchannel_logout_uri of OidcRegisteredClient '" + client.getId()
+ "' not possible!");
return null;
}
JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder();
claimsSetBuilder.subject(String.valueOf(subject));
claimsSetBuilder.issuer(issuer);
claimsSetBuilder.audience(client.getClientId());
claimsSetBuilder.issueTime(new Date());
claimsSetBuilder.jwtID(RandomStringUtils.random(JWT_ID_LENGTH, true, true));
if (StringUtils.hasText(sid)) {
claimsSetBuilder.claim("sid", sid);
}
Map<String, Object> events = Maps.newHashMap();
events.put("http://schemas.openid.net/event/backchannel-logout", Maps.newHashMap());
claimsSetBuilder.claim("events", events);
JwtKey jwtKey = jwtKeyManager.getLatest(OidcTokenManager.OIDC_JWT_KEY_NAME, false);
if (jwtKey == null) {
throw new JOSEException("No jwtKey found!");
}
JWK jwk = jwtKeyManager.parseKey(jwtKey);
JWSSigner jwsSigner = jwtKeyManager.createSigner(jwtKey);
if (jwsSigner == null) {
throw new JOSEException("Signer could not be created!");
}
JWSAlgorithm algorithm = jwtKeyManager.getJwsAlgorithm(jwtKey);
if (algorithm == null) {
algorithm = JWSAlgorithm.HS512;
}
JWSHeader.Builder headerBuilder = new JWSHeader.Builder(algorithm);
headerBuilder.keyID(jwk.getKeyID());
SignedJWT jwt = new SignedJWT(headerBuilder.build(), claimsSetBuilder.build());
jwt.sign(jwsSigner);
return jwt;
}
}

View File

@ -7,26 +7,35 @@ import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.beust.jcommander.internal.Maps; import com.beust.jcommander.internal.Maps;
import com.google.common.collect.Lists;
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType; import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyType; import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jwt.JWTClaimsSet.Builder; import com.nimbusds.jwt.JWTClaimsSet.Builder;
import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.SignedJWT;
import com.querydsl.core.BooleanBuilder;
import de.bstly.we.businesslogic.PermissionManager; import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.QuotaManager; import de.bstly.we.businesslogic.QuotaManager;
@ -39,6 +48,7 @@ import de.bstly.we.model.Permission;
import de.bstly.we.model.Quota; import de.bstly.we.model.Quota;
import de.bstly.we.model.User; import de.bstly.we.model.User;
import de.bstly.we.model.UserProfileField; import de.bstly.we.model.UserProfileField;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
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.oidc.model.QOidcToken; import de.bstly.we.oidc.model.QOidcToken;
@ -50,12 +60,16 @@ import de.bstly.we.oidc.repository.OidcTokenRepository;
@Service @Service
public class OidcTokenManager implements SmartInitializingSingleton { public class OidcTokenManager implements SmartInitializingSingleton {
private Logger logger = LoggerFactory.getLogger(OidcTokenManager.class);
public static final int ACCESS_TOKEN_LENGTH = 64; public static final int ACCESS_TOKEN_LENGTH = 64;
public static final int REFRESH_TOKEN_LENGTH = 64;
public static final String OIDC_JWT_KEY_NAME = "oidc"; public static final String OIDC_JWT_KEY_NAME = "oidc";
public static final String BEARER_TOKEN_TYPE = "Bearer"; public static final String BEARER_TOKEN_TYPE = "Bearer";
@Autowired @Autowired
private OidcTokenRepository tokenRepository; private OidcTokenRepository oidcTokenRepository;
@Autowired @Autowired
private UserManager userManager; private UserManager userManager;
@Autowired @Autowired
@ -104,11 +118,40 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @return the oidc token * @return the oidc token
*/ */
public OidcToken createToken(OidcClient client, Long userId) { public OidcToken createToken(OidcClient client, Long userId) {
return createToken(client, userId, null);
}
/**
* Creates the token.
*
* @param client the client
* @param userId the user id
* @param refreshToken the refresh token
* @return the oidc token
*/
public OidcToken createToken(OidcClient client, Long userId, boolean refreshToken) {
return createToken(client, userId,
refreshToken ? RandomStringUtils.random(REFRESH_TOKEN_LENGTH, true, true) : null);
}
/**
* Creates the token.
*
* @param client the client
* @param userId the user id
* @param refreshToken the refresh token
* @return the oidc token
*/
public OidcToken createToken(OidcClient client, Long userId, String refreshToken) {
OidcToken token = new OidcToken(); OidcToken token = new OidcToken();
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)) {
token.setRefreshToken(refreshToken);
}
token.setExpiresIn(client.getTokenLifetime()); token.setExpiresIn(client.getTokenLifetime());
return tokenRepository.save(token); token.setCreated(Instant.now());
return oidcTokenRepository.save(token);
} }
/** /**
@ -119,18 +162,18 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @param nonce the nonce * @param nonce the nonce
* @param scopes the scopes * @param scopes the scopes
* @param issuer the issuer * @param issuer the issuer
* @param sid the sid
* @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 nonce, Set<String> scopes,
String issuer) throws JOSEException { String issuer, String sid) throws JOSEException {
OidcToken token = new OidcToken();
token.setClient(client.getId());
User user = userManager.get(userId); User user = userManager.get(userId);
Assert.notNull(user, "User does not exist!"); Assert.notNull(user, "User does not exist!");
OidcToken token = createToken(client, client.getId(),
client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.refresh_token));
token.setUserId(user.getId()); token.setUserId(user.getId());
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());
@ -147,6 +190,10 @@ public class OidcTokenManager implements SmartInitializingSingleton {
claimsSetBuilder.claim("nonce", nonce); claimsSetBuilder.claim("nonce", nonce);
} }
if (StringUtils.hasText(sid)) {
claimsSetBuilder.claim("sid", sid);
}
JwtKey jwtKey = jwtKeyManager.getLatest(OIDC_JWT_KEY_NAME, false); JwtKey jwtKey = jwtKeyManager.getLatest(OIDC_JWT_KEY_NAME, false);
if (jwtKey == null) { if (jwtKey == null) {
@ -162,7 +209,7 @@ public class OidcTokenManager implements SmartInitializingSingleton {
token.setIdToken(jwt.serialize()); token.setIdToken(jwt.serialize());
return tokenRepository.save(token); return oidcTokenRepository.save(token);
} }
/** /**
@ -234,15 +281,112 @@ public class OidcTokenManager implements SmartInitializingSingleton {
* @return the by access token * @return the by access token
*/ */
public OidcToken getByAccessToken(String accessToken) { public OidcToken getByAccessToken(String accessToken) {
return tokenRepository.findOne(qOidcToken.accessToken.eq(accessToken)).orElse(null); return oidcTokenRepository.findOne(qOidcToken.accessToken.eq(accessToken)).orElse(null);
} }
/** /**
* Gets the jwk set. * Gets the last by refresh token.
* *
* @return the jwk set * @param refreshToken the refresh token
* @return the last by refresh token
*/ */
public JWKSet getJwkSet() { public OidcToken getLastByRefreshToken(String refreshToken) {
return jwtKeyManager.getJwkSet(OIDC_JWT_KEY_NAME, false); List<OidcToken> token = getAllByRefreshToken(refreshToken);
if (token.isEmpty()) {
return null;
}
return token.get(0);
} }
/**
* Gets the all by refresh token.
*
* @param refreshToken the refresh token
* @return the all by refresh token
*/
public List<OidcToken> getAllByRefreshToken(String refreshToken) {
return Lists.newArrayList(oidcTokenRepository.findAll(qOidcToken.refreshToken.eq(refreshToken),
Sort.by(Sort.Direction.DESC, "created")));
}
/**
* Delete.
*
* @param token the token
*/
public void delete(OidcToken token) {
if (oidcTokenRepository.existsById(token.getId())) {
oidcTokenRepository.delete(token);
}
}
/**
* Delete by access token.
*
* @param accessToken the access token
*/
public void deleteByAccessToken(String accessToken) {
OidcToken token = getByAccessToken(accessToken);
if (token != null) {
delete(token);
}
}
/**
* Delete all by refresh token.
*
* @param refreshToken the refresh token
*/
public void deleteAllByRefreshToken(String refreshToken) {
for (OidcToken token : oidcTokenRepository.findAll(qOidcToken.refreshToken.eq(refreshToken))) {
delete(token);
}
}
/**
* Delete all by target.
*
* @param userId the user id
*/
public void deleteAllByTarget(Long userId) {
for (OidcToken token : oidcTokenRepository.findAll(qOidcToken.userId.eq(userId))) {
delete(token);
}
}
/**
* Cron.
*/
@Scheduled(cron = "${openid-provider.token.cleanup.cron:0 0 0,12 * * * }")
public void cron() {
deleteExpired(false);
}
/**
* Delete expired.
*
* @param includeRefreshToken the include refresh token
*/
public void deleteExpired(boolean includeRefreshToken) {
BooleanBuilder predicate = new BooleanBuilder();
predicate.and(qOidcToken.expiresIn.gt(0L));
if (!includeRefreshToken) {
predicate.and(qOidcToken.refreshToken.isEmpty().or(qOidcToken.refreshToken.isNull()));
}
Pageable pageable = PageRequest.of(0, 500, Sort.by("id"));
Page<OidcToken> page;
do {
page = oidcTokenRepository.findAll(predicate.getValue(), pageable);
for (OidcToken oidcToken : page.getContent()) {
if (oidcToken.getCreated()
.isBefore(Instant.now().minus(oidcToken.getExpiresIn(), ChronoUnit.SECONDS))) {
logger.debug(
"delete expired OidcToken: " + oidcToken.getId() + " [" + oidcToken.getAccessToken() + "]");
delete(oidcToken);
}
}
pageable = page.nextPageable();
} while (page.hasNext());
}
} }

View File

@ -0,0 +1,113 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.exception;
import java.net.URI;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationErrorCode;
/**
* The Class InvalidAuthorizationRequestException.
*/
public class InvalidAuthorizationRequestException extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private URI redirectUri;
private OidcAuthorizationErrorCode errorCode;
private String errorDescription;
private String state;
/**
* Instantiates a new invalid authorization request exception.
*
* @param redirectUri the redirect uri
* @param errorCode the error code
* @param errorDescription the error description
* @param state the state
*/
public InvalidAuthorizationRequestException(URI redirectUri, OidcAuthorizationErrorCode errorCode,
String errorDescription, String state) {
super(errorDescription);
this.redirectUri = redirectUri;
this.errorCode = errorCode;
this.errorDescription = errorDescription;
this.state = state;
}
/**
* Gets the redirect uri.
*
* @return the redirect uri
*/
public URI getRedirectUri() {
return redirectUri;
}
/**
* Sets the redirect uri.
*
* @param redirectUri the new redirect uri
*/
public void setRedirectUri(URI redirectUri) {
this.redirectUri = redirectUri;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcAuthorizationErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcAuthorizationErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
/**
* Gets the state.
*
* @return the state
*/
public String getState() {
return state;
}
/**
* Sets the state.
*
* @param state the new state
*/
public void setState(String state) {
this.state = state;
}
}

View File

@ -0,0 +1,69 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.exception;
import de.bstly.we.oidc.businesslogic.model.OidcTokenErrorCode;
/**
* The Class InvalidTokenRequestException.
*/
public class InvalidTokenRequestException extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private OidcTokenErrorCode errorCode;
private String errorDescription;
/**
* Instantiates a new invalid token request exception.
*
* @param errorCode the error code
* @param errorDescription the error description
*/
public InvalidTokenRequestException(OidcTokenErrorCode errorCode, String errorDescription) {
super(errorDescription);
this.errorCode = errorCode;
this.errorDescription = errorDescription;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcTokenErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcTokenErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
}

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
import java.net.URI; import java.net.URI;
import java.time.Instant; import java.time.Instant;
@ -25,6 +25,7 @@ public class OidcAuthorizationCode {
private final Instant expiry; private final Instant expiry;
private final Long userId; private final Long userId;
private final String nonce; private final String nonce;
private String springSession;
/** /**
* Instantiates a new oidc authorization code. * Instantiates a new oidc authorization code.
@ -108,4 +109,22 @@ public class OidcAuthorizationCode {
return nonce; return nonce;
} }
/**
* Gets the spring session.
*
* @return the spring session
*/
public String getSpringSession() {
return springSession;
}
/**
* Sets the spring session.
*
* @param springSession the new spring session
*/
public void setSpringSession(String springSession) {
this.springSession = springSession;
}
} }

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
/** /**
* The Enum OidcAuthorizationErrorCode. * The Enum OidcAuthorizationErrorCode.

View File

@ -1,11 +1,11 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
/** /**
* The Enum OidcAuthorizationGrantType. * The Enum OidcAuthorizationGrantType.
*/ */
public enum OidcAuthorizationGrantType { public enum OidcAuthorizationGrantType {
authorization_code, client_credentials authorization_code, client_credentials, refresh_token
} }

View File

@ -0,0 +1,282 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.model;
import java.net.URI;
/**
* The Class OidcAuthorizationRequest.
*/
public class OidcAuthorizationRequest {
private String code;
private final String client_id;
private final String response_type;
private final URI redirect_uri;
private final String scope;
private String state;
private String nonce;
private String display;
private String prompt;
private String max_age;
private String ui_locales;
private String id_token_hint;
private String login_hint;
private String acr_values;
/**
* Instantiates a new oidc authorization request.
*
* @param code the code
* @param client_id the client id
* @param response_type the response type
* @param redirect_uri the redirect uri
* @param scope the scope
* @param state the state
* @param nonce the nonce
* @param display the display
* @param prompt the prompt
* @param max_age the max age
* @param ui_locales the ui locales
* @param id_token_hint the id token hint
* @param login_hint the login hint
* @param acr_values the acr values
*/
public OidcAuthorizationRequest(String code, String client_id, String response_type, URI redirect_uri, String scope,
String state, String nonce, String display, String prompt, String max_age, String ui_locales,
String id_token_hint, String login_hint, String acr_values) {
super();
this.code = code;
this.client_id = client_id;
this.response_type = response_type;
this.redirect_uri = redirect_uri;
this.scope = scope;
this.state = state;
this.nonce = nonce;
this.display = display;
this.prompt = prompt;
this.max_age = max_age;
this.ui_locales = ui_locales;
this.id_token_hint = id_token_hint;
this.login_hint = login_hint;
this.acr_values = acr_values;
}
/**
* Gets the code.
*
* @return the code
*/
public String getCode() {
return code;
}
/**
* Sets the code.
*
* @param code the new code
*/
public void setCode(String code) {
this.code = code;
}
/**
* Gets the client id.
*
* @return the client id
*/
public String getClient_id() {
return client_id;
}
/**
* Gets the response type.
*
* @return the response type
*/
public String getResponse_type() {
return response_type;
}
/**
* Gets the redirect uri.
*
* @return the redirect uri
*/
public URI getRedirect_uri() {
return redirect_uri;
}
/**
* Gets the scope.
*
* @return the scope
*/
public String getScope() {
return scope;
}
/**
* Gets the state.
*
* @return the state
*/
public String getState() {
return state;
}
/**
* Sets the state.
*
* @param state the new state
*/
public void setState(String state) {
this.state = state;
}
/**
* Gets the display.
*
* @return the display
*/
public String getDisplay() {
return display;
}
/**
* Sets the display.
*
* @param display the new display
*/
public void setDisplay(String display) {
this.display = display;
}
/**
* Gets the nonce.
*
* @return the nonce
*/
public String getNonce() {
return nonce;
}
/**
* Sets the nonce.
*
* @param nonce the new nonce
*/
public void setNonce(String nonce) {
this.nonce = nonce;
}
/**
* Gets the prompt.
*
* @return the prompt
*/
public String getPrompt() {
return prompt;
}
/**
* Sets the prompt.
*
* @param prompt the new prompt
*/
public void setPrompt(String prompt) {
this.prompt = prompt;
}
/**
* Gets the max age.
*
* @return the max age
*/
public String getMax_age() {
return max_age;
}
/**
* Sets the max age.
*
* @param max_age the new max age
*/
public void setMax_age(String max_age) {
this.max_age = max_age;
}
/**
* Gets the ui locales.
*
* @return the ui locales
*/
public String getUi_locales() {
return ui_locales;
}
/**
* Sets the ui locales.
*
* @param ui_locales the new ui locales
*/
public void setUi_locales(String ui_locales) {
this.ui_locales = ui_locales;
}
/**
* Gets the id token hint.
*
* @return the id token hint
*/
public String getId_token_hint() {
return id_token_hint;
}
/**
* Sets the id token hint.
*
* @param id_token_hint the new id token hint
*/
public void setId_token_hint(String id_token_hint) {
this.id_token_hint = id_token_hint;
}
/**
* Gets the login hint.
*
* @return the login hint
*/
public String getLogin_hint() {
return login_hint;
}
/**
* Sets the login hint.
*
* @param login_hint the new login hint
*/
public void setLogin_hint(String login_hint) {
this.login_hint = login_hint;
}
/**
* Gets the acr values.
*
* @return the acr values
*/
public String getAcr_values() {
return acr_values;
}
/**
* Sets the acr values.
*
* @param acr_values the new acr values
*/
public void setAcr_values(String acr_values) {
this.acr_values = acr_values;
}
}

View File

@ -0,0 +1,31 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.model;
/**
* The Enum OidcAuthorizationResponseType.
*/
public enum OidcAuthorizationResponseType {
AUTHORIZATION_CODE("code");
private final String authorizationResponseType;
/**
* Instantiates a new oidc authorization response type.
*
* @param authorizationGrantType the authorization grant type
*/
OidcAuthorizationResponseType(String authorizationGrantType) {
this.authorizationResponseType = authorizationGrantType;
}
/**
* Gets the authorization response type.
*
* @return the authorization response type
*/
public String getAuthorizationResponseType() {
return authorizationResponseType;
}
}

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
/** /**
* The Enum OidcClientAuthenticationMethod. * The Enum OidcClientAuthenticationMethod.

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
/** /**
* The Enum OidcTokenErrorCode. * The Enum OidcTokenErrorCode.

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
import java.net.URI; import java.net.URI;
@ -14,6 +14,7 @@ public class OidcTokenRequest {
private final OidcAuthorizationGrantType grant_type; private final OidcAuthorizationGrantType grant_type;
private String client_id; private String client_id;
private String client_secret; private String client_secret;
private String refresh_token;
private final URI redirect_uri; private final URI redirect_uri;
private final String scope; private final String scope;
@ -24,16 +25,18 @@ public class OidcTokenRequest {
* @param grant_type the grant type * @param grant_type the grant type
* @param client_id the client id * @param client_id the client id
* @param client_secret the client secret * @param client_secret the client secret
* @param refresh_token the refresh token
* @param redirect_uri the redirect uri * @param redirect_uri the redirect uri
* @param scope the scope * @param scope the scope
*/ */
public OidcTokenRequest(String code, OidcAuthorizationGrantType grant_type, String client_id, String client_secret, public OidcTokenRequest(String code, OidcAuthorizationGrantType grant_type, String client_id, String client_secret,
URI redirect_uri, String scope) { String refresh_token, URI redirect_uri, String scope) {
super(); super();
this.code = code; this.code = code;
this.grant_type = grant_type; this.grant_type = grant_type;
this.client_id = client_id; this.client_id = client_id;
this.client_secret = client_secret; this.client_secret = client_secret;
this.refresh_token = refresh_token;
this.redirect_uri = redirect_uri; this.redirect_uri = redirect_uri;
this.scope = scope; this.scope = scope;
} }
@ -74,6 +77,24 @@ public class OidcTokenRequest {
this.client_secret = client_secret; this.client_secret = client_secret;
} }
/**
* Gets the refresh token.
*
* @return the refresh token
*/
public String getRefresh_token() {
return refresh_token;
}
/**
* Sets the refresh token.
*
* @param refresh_token the new refresh token
*/
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
/** /**
* Gets the code. * Gets the code.
* *

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.businesslogic.model;
/** /**
* The Class OidcTokenResponse. * The Class OidcTokenResponse.

View File

@ -0,0 +1,50 @@
/**
*
*/
package de.bstly.we.oidc.businesslogic.model;
/**
* The Class OidcTokenRevokeRequest.
*/
public class OidcTokenRevokeRequest {
private String token;
private String token_type_hint;
/**
* Gets the token.
*
* @return the token
*/
public String getToken() {
return token;
}
/**
* Sets the token.
*
* @param token the new token
*/
public void setToken(String token) {
this.token = token;
}
/**
* Gets the token type hint.
*
* @return the token type hint
*/
public String getToken_type_hint() {
return token_type_hint;
}
/**
* Sets the token type hint.
*
* @param token_type_hint the new token type hint
*/
public void setToken_type_hint(String token_type_hint) {
this.token_type_hint = token_type_hint;
}
}

View File

@ -24,10 +24,10 @@ import com.google.common.collect.Sets;
import de.bstly.we.controller.BaseController; import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException; import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.oidc.businesslogic.OidcClientManager; import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.controller.model.OidcClientModel; import de.bstly.we.oidc.controller.model.OidcClientModel;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.model.OidcClient; import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.repository.OidcClientRepository; import de.bstly.we.oidc.repository.OidcClientRepository;
/** /**

View File

@ -13,25 +13,31 @@ import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.collect.Sets; 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.oidc.businesslogic.OidcAuthorizationCodeManager; 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.model.OidcAuthorizationCode; import de.bstly.we.oidc.businesslogic.exception.InvalidAuthorizationRequestException;
import de.bstly.we.oidc.model.OidcAuthorizationErrorCode; import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType; import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationErrorCode;
import de.bstly.we.oidc.model.OidcAuthorizationResponseType; import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationRequest;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationResponseType;
import de.bstly.we.oidc.model.OidcClient; import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.security.model.LocalUserDetails; import de.bstly.we.security.model.LocalUserDetails;
@ -49,7 +55,10 @@ public class OidcAuthorizationController {
@Autowired @Autowired
private OidcClientManager oidcClientManager; private OidcClientManager oidcClientManager;
@Autowired @Autowired
private OidcAuthorizationCodeManager oidcAuthorizationCodeManager; private OidcAuthorizationManager oidcAuthorizationManager;
@Value("${oidcAuthorizationFormUrl:/oidc/authorize/form}")
private String authorizationFormUrl;
/** /**
* Authorization request. * Authorization request.
@ -60,36 +69,21 @@ public class OidcAuthorizationController {
* @param redirectUri the redirect uri * @param redirectUri the redirect uri
* @param state the state * @param state the state
* @param nonce the nonce * @param nonce the nonce
* @param prompt the prompt
* @param login_hint the login hint
* @param code the code
* @param principal the principal * @param principal the principal
* @param request the request * @param request the request
* @param response the response * @param response the response
* @throws IOException Signals that an I/O exception has occurred. * @throws IOException Signals that an I/O exception has occurred.
*/ */
@PreAuthorize("isAuthenticated()") protected void authorizationRequest(String scope, String responseType, String clientId, URI redirectUri,
@GetMapping String state, String nonce, String prompt, String login_hint, String code, LocalUserDetails principal,
void authorizationRequest( HttpServletRequest request, HttpServletResponse response) throws IOException {
// for OIDC scope must contain "openid"
@RequestParam(name = "scope") String scope,
// for now only "code" is available
@RequestParam(name = "response_type") String responseType,
// client id
@RequestParam(name = "client_id") String clientId,
// redirect url
@RequestParam(name = "redirect_uri") URI redirectUri,
// optional state
@RequestParam(name = "state", required = false) String state,
// optional state
@RequestParam(name = "nonce", required = false) String nonce,
// authentication details
@AuthenticationPrincipal LocalUserDetails principal,
// the request
HttpServletRequest request,
// the response
HttpServletResponse response) throws IOException {
if (!StringUtils.hasText(clientId)) { if (!StringUtils.hasText(clientId)) {
logger.debug("missing client_id"); logger.debug("missing client_id");
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST, throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"missing client_id", state); "missing client_id", state);
} }
@ -97,14 +91,14 @@ public class OidcAuthorizationController {
if (client == null) { if (client == null) {
logger.debug("invalid client_id: " + clientId); logger.debug("invalid client_id: " + clientId);
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST, throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"invalid client_id", state); "invalid client_id", state);
} }
if (!client.getRedirectUris().contains(redirectUri.toString())) { if (!client.getRedirectUris().contains(redirectUri.toString())) {
logger.debug("invalid redirect_uri: " + redirectUri + " allowed: " + client.getRedirectUris()); logger.debug("invalid redirect_uri: " + redirectUri + " allowed: " + client.getRedirectUris());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST, throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"invalid redirect_uri", state); "invalid redirect_uri", state);
} }
@ -112,23 +106,22 @@ public class OidcAuthorizationController {
&& !permissionManager.hasPermission(principal.getUserId(), client.getClientName()) && !permissionManager.hasPermission(principal.getUserId(), client.getClientName())
&& !permissionManager.hasPermission(principal.getUserId(), Permissions.ROLE_ADMIN)) { && !permissionManager.hasPermission(principal.getUserId(), Permissions.ROLE_ADMIN)) {
logger.debug("user not allowed: " + principal.getUserId() + " - " + client.getClientName()); logger.debug("user not allowed: " + principal.getUserId() + " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.ACCESS_DENIED, throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.ACCESS_DENIED,
"user not allowed", state); "user not allowed", state);
} }
if (!client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.authorization_code)) { if (!client.getAuthorizationGrantTypes().contains(OidcAuthorizationGrantType.authorization_code)) {
logger.debug("authorization grant type not allowed: " + OidcAuthorizationGrantType.authorization_code logger.debug("authorization grant type not allowed: " + OidcAuthorizationGrantType.authorization_code
+ " - " + client.getClientName()); + " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.UNAUTHORIZED_CLIENT, throw new InvalidAuthorizationRequestException(redirectUri,
"authorization grant type not allowed", state); OidcAuthorizationErrorCode.INVALID_REQUEST_OBJECT, "authorization grant type not allowed", state);
} }
if (!responseType.equals(OidcAuthorizationResponseType.code.toString())) { if (!responseType.equals(OidcAuthorizationResponseType.AUTHORIZATION_CODE.getAuthorizationResponseType())) {
logger.debug("response type not allowed: " + OidcAuthorizationResponseType.code logger.debug("response type not allowed: "
+ OidcAuthorizationResponseType.AUTHORIZATION_CODE.getAuthorizationResponseType() + " - "
+ " - " + client.getClientName()); + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, throw new InvalidAuthorizationRequestException(redirectUri,
OidcAuthorizationErrorCode.UNSUPPORTED_RESPONSE_TYPE, "response type not allowed", state); OidcAuthorizationErrorCode.UNSUPPORTED_RESPONSE_TYPE, "response type not allowed", state);
} }
@ -137,27 +130,151 @@ public class OidcAuthorizationController {
if (!scopes.contains("openid")) { if (!scopes.contains("openid")) {
logger.debug("missing openid scope: " + scopes + " - " + client.getClientName()); logger.debug("missing openid scope: " + scopes + " - " + client.getClientName());
throw new InvalidAuthorizationRequestError(redirectUri, OidcAuthorizationErrorCode.INVALID_SCOPE, throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_SCOPE,
"missing openid scope", state); "missing openid scope", state);
} }
OidcAuthorizationCode authorizationCode = oidcAuthorizationCodeManager.create(clientId, redirectUri, scopes, if (!client.getScopes().containsAll(scopes)) {
principal.getUserId(), nonce); throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_SCOPE,
"some scopes not allowed", state);
}
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(redirectUri);
String uri = redirectUri.toString(); Set<String> prompts = Sets.newHashSet();
if (StringUtils.hasText(redirectUri.getQuery())) { if (StringUtils.hasText(prompt)) {
uri += "&code=" + authorizationCode.getCode(); prompts = Sets.newHashSet(prompt.split(" "));
}
if (prompts.contains("none") && prompts.size() > 1) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INVALID_REQUEST,
"prompt contains more than 'none'", state);
} else if (prompts.contains("none") && client.isAuthorize()
&& !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes)) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.INTERACTION_REQUIRED,
"prompt 'consent' required beforehand", state);
}
OidcAuthorizationCode authorizationCode;
if (StringUtils.hasText(code)) {
authorizationCode = oidcAuthorizationManager.getCode(code);
if (authorizationCode == null) {
throw new InvalidAuthorizationRequestException(redirectUri,
OidcAuthorizationErrorCode.INVALID_REQUEST_OBJECT, "invalid code", state);
}
if (!authorizationCode.getUserId().equals(principal.getUserId())) {
throw new InvalidAuthorizationRequestException(redirectUri, OidcAuthorizationErrorCode.LOGIN_REQUIRED,
"code does not match user", state);
}
oidcAuthorizationManager.authorize(client.getId(), principal.getUserId(), scopes);
} else { } else {
uri += "?code=" + authorizationCode.getCode(); if (client.isAuthorize()
&& !oidcAuthorizationManager.isAuthorized(client.getId(), principal.getUserId(), scopes)
|| prompts.contains("consent")) {
uriBuilder = UriComponentsBuilder.fromUriString(authorizationFormUrl);
uriBuilder.queryParam("client_id", clientId);
uriBuilder.queryParam("response_type", responseType);
uriBuilder.queryParam("redirect_uri", redirectUri);
uriBuilder.queryParam("scope", scope);
if (StringUtils.hasText(nonce)) {
uriBuilder.queryParam("nonce", nonce);
}
if (StringUtils.hasText(prompt)) {
uriBuilder.queryParam("prompt", prompt);
}
}
authorizationCode = oidcAuthorizationManager.createCode(clientId, redirectUri, scopes,
principal.getUserId(), nonce, request.getSession().getId());
} }
uriBuilder.queryParam("code", authorizationCode.getCode());
if (StringUtils.hasText(state)) { if (StringUtils.hasText(state)) {
uri += "&state=" + state; uriBuilder.queryParam("state", state);
} }
response.sendRedirect(uri); if (StringUtils.hasText(login_hint)) {
uriBuilder.queryParam("login_hint", login_hint);
}
response.sendRedirect(uriBuilder.build().toUriString());
}
/**
* Authorization get request.
*
* @param scope the scope
* @param responseType the response type
* @param clientId the client id
* @param redirectUri the redirect uri
* @param state the state
* @param nonce the nonce
* @param prompt the prompt
* @param login_hint the login hint
* @param principal the principal
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@GetMapping
public void authorizationGetRequest(
// for OIDC scope must contain "openid"
@RequestParam(name = "scope") String scope,
// response type
@RequestParam(name = "response_type") String responseType,
// client id
@RequestParam(name = "client_id") String clientId,
// redirect url
@RequestParam(name = "redirect_uri") URI redirectUri,
// optional state
@RequestParam(name = "state", required = false) String state,
// optional nonce
@RequestParam(name = "nonce", required = false) String nonce,
// optional prompt
@RequestParam(name = "prompt", required = false) String prompt,
// optional login_hint
@RequestParam(name = "login_hint", required = false) String login_hint,
// authentication details
@AuthenticationPrincipal LocalUserDetails principal,
// the request
HttpServletRequest request,
// the response
HttpServletResponse response) throws IOException {
authorizationRequest(scope, responseType, clientId, redirectUri, state, nonce, prompt, login_hint, null,
principal, request, response);
}
/**
* Authorization post request.
*
* @param authorizationRequest the authorization request
* @param principal the principal
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PostMapping
@PreAuthorize("authentication.authenticated")
public void authorizationPostRequest(
// authorization request
@ModelAttribute("authorizationRequest") OidcAuthorizationRequest authorizationRequest,
// authentication details
@AuthenticationPrincipal LocalUserDetails principal,
// the request
HttpServletRequest request,
// the response
HttpServletResponse response) throws IOException {
authorizationRequest(authorizationRequest.getScope(), authorizationRequest.getResponse_type(),
authorizationRequest.getClient_id(), authorizationRequest.getRedirect_uri(),
authorizationRequest.getState(), authorizationRequest.getNonce(), authorizationRequest.getPrompt(),
authorizationRequest.getLogin_hint(), authorizationRequest.getCode(), principal, request, response);
} }
/** /**
@ -167,126 +284,21 @@ public class OidcAuthorizationController {
* @param response the response * @param response the response
* @throws IOException Signals that an I/O exception has occurred. * @throws IOException Signals that an I/O exception has occurred.
*/ */
@ExceptionHandler(InvalidAuthorizationRequestError.class) @ExceptionHandler(InvalidAuthorizationRequestException.class)
public void handle(InvalidAuthorizationRequestError exception, HttpServletResponse response) throws IOException { public void handle(InvalidAuthorizationRequestException exception, HttpServletResponse response)
String uri = exception.getRedirectUri().toString(); throws IOException {
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(exception.getRedirectUri());
uri += "?error=" + exception.getErrorCode().getAuthorizationErrorCode(); uriBuilder.queryParam("error", exception.getErrorCode().getAuthorizationErrorCode());
if (StringUtils.hasText(exception.getErrorDescription())) { if (StringUtils.hasText(exception.getErrorDescription())) {
uri += "&error_description=" + exception.getErrorDescription(); uriBuilder.queryParam("error_description", exception.getErrorDescription());
} }
if (StringUtils.hasText(exception.getState())) { if (StringUtils.hasText(exception.getState())) {
uri += "&state=" + exception.getState(); uriBuilder.queryParam("state", exception.getState());
} }
response.sendRedirect(uri); response.sendRedirect(uriBuilder.build().toUriString());
} }
/**
* The Class InvalidAuthorizationRequestError.
*/
static class InvalidAuthorizationRequestError extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private URI redirectUri;
private OidcAuthorizationErrorCode errorCode;
private String errorDescription;
private String state;
/**
* Instantiates a new invalid authorization request error.
*
* @param redirectUri the redirect uri
* @param errorCode the error code
* @param errorDescription the error description
* @param state the state
*/
InvalidAuthorizationRequestError(URI redirectUri, OidcAuthorizationErrorCode errorCode, String errorDescription,
String state) {
super(errorDescription);
this.redirectUri = redirectUri;
this.errorCode = errorCode;
this.errorDescription = errorDescription;
this.state = state;
}
/**
* Gets the redirect uri.
*
* @return the redirect uri
*/
public URI getRedirectUri() {
return redirectUri;
}
/**
* Sets the redirect uri.
*
* @param redirectUri the new redirect uri
*/
public void setRedirectUri(URI redirectUri) {
this.redirectUri = redirectUri;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcAuthorizationErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcAuthorizationErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
/**
* Gets the state.
*
* @return the state
*/
public String getState() {
return state;
}
/**
* Sets the state.
*
* @param state the new state
*/
public void setState(String state) {
this.state = state;
}
}
} }

View File

@ -9,9 +9,8 @@ import java.net.URISyntaxException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
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;
@ -19,7 +18,8 @@ import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import de.bstly.we.controller.support.EntityResponseStatusException; import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.oidc.model.OidcConfiguration; import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.controller.model.OidcConfiguration;
/** /**
* The Class OidcDiscoveryController. * The Class OidcDiscoveryController.
@ -28,8 +28,8 @@ import de.bstly.we.oidc.model.OidcConfiguration;
@RestController @RestController
public class OidcDiscoveryController { public class OidcDiscoveryController {
@Value("${oidc.provider.issuer:}") @Autowired
private String oidcIssuer; private OidcClientManager oidcClientManager;
/** /**
* Gets the configuration. * Gets the configuration.
@ -42,14 +42,7 @@ public class OidcDiscoveryController {
public OidcConfiguration getConfiguration(HttpServletRequest request, HttpServletResponse response) { public OidcConfiguration getConfiguration(HttpServletRequest request, HttpServletResponse response) {
OidcConfiguration config = new OidcConfiguration(); OidcConfiguration config = new OidcConfiguration();
String issuer = oidcIssuer; String issuer = oidcClientManager.getIssuer(request);
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme() + "://" + request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":" + request.getServerPort();
}
}
config.setIssuer(issuer); config.setIssuer(issuer);
config.setScopes_supported(Sets.newHashSet("openid")); config.setScopes_supported(Sets.newHashSet("openid"));

View File

@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.GetMapping;
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.jwt.businesslogic.JwtKeyManager;
import de.bstly.we.oidc.businesslogic.OidcTokenManager; import de.bstly.we.oidc.businesslogic.OidcTokenManager;
/** /**
@ -20,7 +21,7 @@ import de.bstly.we.oidc.businesslogic.OidcTokenManager;
public class OidcJwksController { public class OidcJwksController {
@Autowired @Autowired
private OidcTokenManager oidcTokenManager; private JwtKeyManager jwtKeyManager;
/** /**
* Gets the jwks. * Gets the jwks.
@ -29,6 +30,6 @@ public class OidcJwksController {
*/ */
@GetMapping @GetMapping
public Map<String, Object> getJwks() { public Map<String, Object> getJwks() {
return oidcTokenManager.getJwkSet().toJSONObject(); return jwtKeyManager.getJwkSet(OidcTokenManager.OIDC_JWT_KEY_NAME, false).toJSONObject();
} }
} }

View File

@ -0,0 +1,234 @@
/**
*
*/
package de.bstly.we.oidc.controller;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.Permissions;
import de.bstly.we.businesslogic.SessionManager;
import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.model.User;
import de.bstly.we.oidc.businesslogic.OidcClientManager;
import de.bstly.we.oidc.businesslogic.OidcSessionManager;
import de.bstly.we.oidc.controller.model.OidcClientInfo;
import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcSession;
/**
* The Class OidcSessionController.
*/
@RestController
@RequestMapping("/oidc/session")
public class OidcSessionController extends BaseController {
@Autowired
private OidcClientManager oidcClientManager;
@Autowired
private OidcSessionManager oidcSessionManager;
@Autowired
private SessionManager sessionManager;
@Autowired
private RememberMeServices rememberMeServices;
@Autowired
private UserManager userManager;
@Autowired
private OidcClientManager oidcRegisteredClientManager;
@Autowired
private PermissionManager permissionManager;
@Value("${loginUrl:/login}")
private String loginUrl;
/**
* Gets the sessions.
*
* @return the sessions
*/
@PreAuthorize("authentication.authenticated")
@GetMapping
public List<OidcClientInfo> getSessions() {
Map<Long, OidcClientInfo> clients = Maps.newHashMap();
for (OidcSession session : oidcSessionManager.getAllBySubject(getCurrentUserId())) {
Long oidcClientId = session.getClientId();
OidcClientInfo clientInfo = clients.get(oidcClientId);
if (clientInfo == null) {
OidcClient client = oidcRegisteredClientManager.get(oidcClientId);
if (client == null || !client.isAlwaysPermitted()
&& (!permissionManager.hasPermission(getCurrentUserId(), client.getClientName())
|| !permissionManager.hasPermission(getCurrentUserId(), Permissions.ROLE_ADMIN))) {
continue;
}
clientInfo = oidcRegisteredClientManager.getClientInfo(client, getCurrentUserId());
}
clientInfo.getSessions().add(session);
clients.put(session.getClientId(), clientInfo);
}
return Lists.newArrayList(clients.values());
}
/**
* Gets the sessions for target.
*
* @param target the target
* @return the sessions for target
*/
@PreAuthorize("authentication.authenticated")
@GetMapping("/{target}")
public OidcClientInfo getSessionsForTarget(@PathVariable("target") Long target) {
OidcClient client = oidcRegisteredClientManager.get(target);
if (client == null || !client.isAlwaysPermitted()
&& (!permissionManager.hasPermission(getCurrentUserId(), client.getClientName())
|| !permissionManager.hasPermission(getCurrentUserId(), Permissions.ROLE_ADMIN))) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
OidcClientInfo clientInfo = oidcRegisteredClientManager.getClientInfo(client, getCurrentUserId());
clientInfo.getSessions().addAll(oidcSessionManager.getAllByTargetAndSubject(target, getCurrentUserId()));
return clientInfo;
}
/**
* Logout session.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutSession(@RequestParam("redirect_url") Optional<String> redirectUrlParam,
HttpServletRequest request, HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(request.getSession().getId(), issuer);
internalLogout(redirectUrlParam, request, response);
}
/**
* Logout all.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout/all", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutAll(@RequestParam("redirect_url") Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(getCurrentUserId(), issuer);
User user = userManager.get(getCurrentUserId());
sessionManager.deleteSessionsForUser(user);
internalLogout(redirectUrlParam, request, response);
}
/**
* Logout by sid.
*
* @param sid the sid
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout/sid/{sid}", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutBySid(@PathVariable("sid") String sid,
@RequestParam("redirect_url") Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(sid, issuer);
redirect(redirectUrlParam, request, response, false);
}
/**
* Logout all for target.
*
* @param target the target
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
@PreAuthorize("authentication.authenticated")
@RequestMapping(value = "/logout/all/{target}", method = { RequestMethod.GET, RequestMethod.POST })
public void logoutAllForTarget(@PathVariable("target") Long target,
@RequestParam("redirect_url") Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
String issuer = oidcClientManager.getIssuer(request);
oidcSessionManager.backchannelLogout(target, getCurrentUserId(), issuer);
redirect(redirectUrlParam, request, response, false);
}
/**
* Internal logout.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @throws IOException Signals that an I/O exception has occurred.
*/
protected void internalLogout(Optional<String> redirectUrlParam, HttpServletRequest request,
HttpServletResponse response) throws IOException {
new SecurityContextLogoutHandler().logout(request, response,
SecurityContextHolder.getContext().getAuthentication());
rememberMeServices.loginFail(request, response);
redirect(redirectUrlParam, request, response, true);
}
/**
* Redirect.
*
* @param redirectUrlParam the redirect url param
* @param request the request
* @param response the response
* @param fallback the fallback
* @throws IOException Signals that an I/O exception has occurred.
*/
protected void redirect(Optional<String> redirectUrlParam, HttpServletRequest request, HttpServletResponse response,
boolean fallback) throws IOException {
String redirectUrl = redirectUrlParam.orElse(null);
if (!StringUtils.hasText(redirectUrl) && fallback) {
redirectUrl = loginUrl;
}
if (StringUtils.hasText(redirectUrl)) {
response.sendRedirect(redirectUrl);
}
}
}

View File

@ -11,15 +11,16 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.transaction.Transactional;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders; 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.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -32,16 +33,19 @@ import org.springframework.web.server.ResponseStatusException;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEException;
import de.bstly.we.oidc.businesslogic.OidcAuthorizationCodeManager; 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.OidcSessionManager;
import de.bstly.we.oidc.businesslogic.OidcTokenManager; import de.bstly.we.oidc.businesslogic.OidcTokenManager;
import de.bstly.we.oidc.model.OidcAuthorizationCode; import de.bstly.we.oidc.businesslogic.exception.InvalidTokenRequestException;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationCode;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.businesslogic.model.OidcTokenErrorCode;
import de.bstly.we.oidc.businesslogic.model.OidcTokenRequest;
import de.bstly.we.oidc.businesslogic.model.OidcTokenResponse;
import de.bstly.we.oidc.businesslogic.model.OidcTokenRevokeRequest;
import de.bstly.we.oidc.model.OidcClient; import de.bstly.we.oidc.model.OidcClient;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod;
import de.bstly.we.oidc.model.OidcToken; import de.bstly.we.oidc.model.OidcToken;
import de.bstly.we.oidc.model.OidcTokenErrorCode;
import de.bstly.we.oidc.model.OidcTokenRequest;
import de.bstly.we.oidc.model.OidcTokenResponse;
/** /**
* The Class OidcTokenController. * The Class OidcTokenController.
@ -57,12 +61,11 @@ public class OidcTokenController {
@Autowired @Autowired
private OidcClientManager oidcClientManager; private OidcClientManager oidcClientManager;
@Autowired @Autowired
private OidcAuthorizationCodeManager oidcAuthorizationCodeManager; private OidcAuthorizationManager oidcAuthorizationManager;
@Autowired @Autowired
private OidcTokenManager oidcTokenManager; private OidcTokenManager oidcTokenManager;
@Autowired
@Value("${oidc.provider.issuer:}") private OidcSessionManager oidcSessionManager;
private String oidcIssuer;
/** /**
* Gets the token. * Gets the token.
@ -74,6 +77,7 @@ public class OidcTokenController {
* @return the token * @return the token
*/ */
@PostMapping @PostMapping
@Transactional
public OidcTokenResponse getToken( public OidcTokenResponse getToken(
// Authorization header for BASIC client authentication method // Authorization header for BASIC client authentication method
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authorizationHeader, @RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String authorizationHeader,
@ -84,6 +88,8 @@ public class OidcTokenController {
// the response // the response
HttpServletResponse response) { HttpServletResponse response) {
String issuer = oidcClientManager.getIssuer(request);
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store"); response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
response.setHeader(HttpHeaders.PRAGMA, "no-cache"); response.setHeader(HttpHeaders.PRAGMA, "no-cache");
@ -101,52 +107,61 @@ public class OidcTokenController {
clientAuthenticationMethod = OidcClientAuthenticationMethod.basic; clientAuthenticationMethod = OidcClientAuthenticationMethod.basic;
} else { } else {
logger.debug("invalid_basic_authentication: " + decoded); logger.debug("invalid_basic_authentication: " + decoded);
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT, "invalid_basic_authentication"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_CLIENT,
"invalid_basic_authentication");
} }
} }
if (!StringUtils.hasText(tokenRequest.getClient_id())) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "missing_client_id");
}
if (!StringUtils.hasText(tokenRequest.getClient_secret())) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "missing_client_secret");
}
OidcClient client = oidcClientManager.getByClientIdAndSecret(tokenRequest.getClient_id(), OidcClient client = oidcClientManager.getByClientIdAndSecret(tokenRequest.getClient_id(),
tokenRequest.getClient_secret()); tokenRequest.getClient_secret());
if (client == null) { if (client == null) {
logger.debug("client not found: " + tokenRequest.getClient_id()); logger.debug("client not found: " + tokenRequest.getClient_id());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client");
} }
if (!client.getClientAuthenticationMethods().contains(clientAuthenticationMethod)) { if (!client.getClientAuthenticationMethods().contains(clientAuthenticationMethod)) {
logger.debug("invalid_authentication_method: " + clientAuthenticationMethod); logger.debug("invalid_authentication_method: " + clientAuthenticationMethod);
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_REQUEST, "invalid_authentication_method"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "invalid_authentication_method");
} }
if (!client.getAuthorizationGrantTypes().contains(tokenRequest.getGrant_type())) { if (!client.getAuthorizationGrantTypes().contains(tokenRequest.getGrant_type())) {
logger.debug("invalid_grant_type: " + tokenRequest.getGrant_type()); logger.debug("invalid_grant_type: " + tokenRequest.getGrant_type());
throw new InvalidTokenRequestError(OidcTokenErrorCode.UNAUTHORIZED_CLIENT, "invalid_grant_type"); throw new InvalidTokenRequestException(OidcTokenErrorCode.UNAUTHORIZED_CLIENT, "invalid_grant_type");
} }
if (tokenRequest.getRedirect_uri() != null if (tokenRequest.getRedirect_uri() != null
&& !client.getRedirectUris().contains(tokenRequest.getRedirect_uri().toString())) { && !client.getRedirectUris().contains(tokenRequest.getRedirect_uri().toString())) {
logger.debug("invalid redirect_uri: " + tokenRequest.getRedirect_uri().toString() + " allowed: " logger.debug("invalid redirect_uri: " + tokenRequest.getRedirect_uri().toString() + " allowed: "
+ client.getRedirectUris()); + client.getRedirectUris());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_REQUEST, "invalid_redirect_uri"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_REQUEST, "invalid_redirect_uri");
} }
OidcToken token = null; OidcToken token = null;
switch (tokenRequest.getGrant_type()) { switch (tokenRequest.getGrant_type()) {
case authorization_code: case authorization_code:
OidcAuthorizationCode authorizationCode = oidcAuthorizationCodeManager.getByCode(tokenRequest.getCode()); OidcAuthorizationCode authorizationCode = oidcAuthorizationManager.getCode(tokenRequest.getCode());
if (authorizationCode == null) { if (authorizationCode == null) {
logger.debug("invalid authorization code: " + tokenRequest.getCode()); logger.debug("invalid authorization code: " + tokenRequest.getCode());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code");
} }
if (Instant.now().isAfter(authorizationCode.getExpiry())) { if (Instant.now().isAfter(authorizationCode.getExpiry())) {
logger.debug("authorization code expired: " + authorizationCode.getExpiry()); logger.debug("authorization code expired: " + authorizationCode.getExpiry());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_authorization_code");
} }
if (!tokenRequest.getClient_id().equals(authorizationCode.getClientId())) { if (!tokenRequest.getClient_id().equals(authorizationCode.getClientId())) {
logger.debug("invalid client for authorization code, expected: " + authorizationCode.getClientId() logger.debug("invalid client for authorization code, expected: " + authorizationCode.getClientId()
+ " got: " + tokenRequest.getClient_id()); + " got: " + tokenRequest.getClient_id());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_CLIENT, "invalid_client");
} }
Set<String> scopes = StringUtils.hasText(tokenRequest.getScope()) Set<String> scopes = StringUtils.hasText(tokenRequest.getScope())
@ -155,46 +170,80 @@ public class OidcTokenController {
if (!scopes.contains("openid") || !client.getScopes().containsAll(scopes)) { if (!scopes.contains("openid") || !client.getScopes().containsAll(scopes)) {
logger.debug("missing openid scope: " + scopes + " - " + client.getClientName()); logger.debug("missing openid scope: " + scopes + " - " + client.getClientName());
throw new InvalidTokenRequestError(OidcTokenErrorCode.INVALID_SCOPE, "invalid scopes"); throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_SCOPE, "invalid scopes");
}
String issuer = oidcIssuer;
if (!StringUtils.hasText(issuer)) {
issuer = request.getScheme() + "://" + request.getServerName();
if (request.getServerPort() != 443 && request.getServerPort() != 80) {
issuer += ":" + request.getServerPort();
}
} }
try { try {
String sid = oidcSessionManager.createSid();
token = oidcTokenManager.createTokenWithIdToken(client, authorizationCode.getUserId(), token = oidcTokenManager.createTokenWithIdToken(client, authorizationCode.getUserId(),
authorizationCode.getNonce(), scopes, issuer); authorizationCode.getNonce(), scopes, issuer, sid);
oidcSessionManager.createSession(client.getId(), authorizationCode.getUserId(), token.getIdToken(), sid,
authorizationCode.getSpringSession());
} catch (JOSEException e) { } catch (JOSEException e) {
logger.error("error creating token", client, authorizationCode); logger.error("error creating token", client, authorizationCode);
e.printStackTrace(); e.printStackTrace();
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
} }
oidcAuthorizationCodeManager.removeByCode(tokenRequest.getCode()); oidcAuthorizationManager.removeCode(tokenRequest.getCode());
break; break;
case client_credentials: case client_credentials:
token = oidcTokenManager.createToken(client, client.getId()); token = oidcTokenManager.createToken(client, client.getId(), false);
break;
case refresh_token:
if (!StringUtils.hasText(tokenRequest.getRefresh_token())) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_refresh_token");
}
OidcToken refreshToken = oidcTokenManager.getLastByRefreshToken(tokenRequest.getRefresh_token());
if (refreshToken == null || !(client.getId().equals(refreshToken.getClient()))) {
throw new InvalidTokenRequestException(OidcTokenErrorCode.INVALID_GRANT, "invalid_refresh_token");
}
token = oidcTokenManager.createToken(client, client.getId(), tokenRequest.getRefresh_token());
token.setRefreshToken(null);
break; break;
} }
if (token == null) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
OidcTokenResponse tokenResponse = new OidcTokenResponse(); OidcTokenResponse tokenResponse = new OidcTokenResponse();
tokenResponse.setAccess_token(token.getAccessToken()); tokenResponse.setAccess_token(token.getAccessToken());
tokenResponse.setId_token(token.getIdToken()); if (StringUtils.hasText(token.getRefreshToken())) {
tokenResponse.setRefresh_token(token.getRefreshToken());
}
if (StringUtils.hasText(token.getIdToken())) {
tokenResponse.setId_token(token.getIdToken());
}
tokenResponse.setToken_type(OidcTokenManager.BEARER_TOKEN_TYPE); tokenResponse.setToken_type(OidcTokenManager.BEARER_TOKEN_TYPE);
tokenResponse.setExpires_in(client.getTokenLifetime()); tokenResponse.setExpires_in(token.getExpiresIn());
oidcAuthorizationCodeManager.removeByCode(tokenRequest.getCode());
return tokenResponse; return tokenResponse;
} }
/**
* Revoke.
*
* @param oidcTokenRevokeRequest the oidc token revoke request
*/
@PreAuthorize("authentication.authenticated")
@PostMapping("revoke")
@Transactional
public void revoke(@ModelAttribute("oidcTokenRevokeRequest") OidcTokenRevokeRequest oidcTokenRevokeRequest) {
if (!StringUtils.hasText(oidcTokenRevokeRequest.getToken())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
if ("refresh_token".equals(oidcTokenRevokeRequest.getToken_type_hint())) {
oidcTokenManager.deleteAllByRefreshToken(oidcTokenRevokeRequest.getToken());
} else if ("access_token".equals(oidcTokenRevokeRequest.getToken_type_hint())
|| oidcTokenRevokeRequest.getToken_type_hint() == null) {
oidcTokenManager.deleteByAccessToken(oidcTokenRevokeRequest.getToken());
}
}
/** /**
* Handle. * Handle.
* *
@ -203,74 +252,11 @@ public class OidcTokenController {
* @return the response entity * @return the response entity
* @throws IOException Signals that an I/O exception has occurred. * @throws IOException Signals that an I/O exception has occurred.
*/ */
@ExceptionHandler(InvalidTokenRequestError.class) @ExceptionHandler(InvalidTokenRequestException.class)
public ResponseEntity<String> handle(InvalidTokenRequestError exception, HttpServletResponse response) public ResponseEntity<String> handle(InvalidTokenRequestException exception, HttpServletResponse response)
throws IOException { throws IOException {
// response.sendError(400, "redirect uri mismatch"); // response.sendError(400, "redirect uri mismatch");
return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON) return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON)
.body(" {\"error\": \"" + exception.getMessage() + "\"}"); .body(" {\"error\": \"" + exception.getMessage() + "\"}");
} }
/**
* The Class InvalidTokenRequestError.
*/
static class InvalidTokenRequestError extends RuntimeException {
/**
* default serialVersionUID
*/
private static final long serialVersionUID = 1L;
private OidcTokenErrorCode errorCode;
private String errorDescription;
/**
* Instantiates a new invalid token request error.
*
* @param errorCode the error code
* @param errorDescription the error description
*/
InvalidTokenRequestError(OidcTokenErrorCode errorCode, String errorDescription) {
super(errorDescription);
this.errorCode = errorCode;
this.errorDescription = errorDescription;
}
/**
* Gets the error code.
*
* @return the error code
*/
public OidcTokenErrorCode getErrorCode() {
return errorCode;
}
/**
* Sets the error code.
*
* @param errorCode the new error code
*/
public void setErrorCode(OidcTokenErrorCode errorCode) {
this.errorCode = errorCode;
}
/**
* Gets the error description.
*
* @return the error description
*/
public String getErrorDescription() {
return errorDescription;
}
/**
* Sets the error description.
*
* @param errorDescription the new error description
*/
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
}
} }

View File

@ -0,0 +1,266 @@
/**
*
*/
package de.bstly.we.oidc.controller.model;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.bstly.we.oidc.model.OidcSession;
/**
* The Class OidcClientInfo.
*/
public class OidcClientInfo {
private String clientId;
private String name;
private String description;
private String loginUrl;
private String frontchannelLogoutUri;
private boolean frontchannelLogoutSessionRequired;
private boolean backchannelLogout;
private boolean authorize;
private Set<String> scopes;
private Map<String, Set<String>> claimMapping;
private Set<String> authorizedScopes;
private Map<String, Object> claims;
private List<OidcSession> sessions;
/**
* Gets the client id.
*
* @return the client id
*/
public String getClientId() {
return clientId;
}
/**
* Sets the client id.
*
* @param clientId the new client id
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Gets the name.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Sets the name.
*
* @param name the new name
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the description.
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Sets the description.
*
* @param description the new description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Gets the login url.
*
* @return the login url
*/
public String getLoginUrl() {
return loginUrl;
}
/**
* Sets the login url.
*
* @param loginUrl the new login url
*/
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
/**
* Gets the frontchannel logout uri.
*
* @return the frontchannel logout uri
*/
public String getFrontchannelLogoutUri() {
return frontchannelLogoutUri;
}
/**
* Sets the frontchannel logout uri.
*
* @param frontchannelLogoutUri the new frontchannel logout uri
*/
public void setFrontchannelLogoutUri(String frontchannelLogoutUri) {
this.frontchannelLogoutUri = frontchannelLogoutUri;
}
/**
* Checks if is frontchannel logout session required.
*
* @return true, if is frontchannel logout session required
*/
public boolean isFrontchannelLogoutSessionRequired() {
return frontchannelLogoutSessionRequired;
}
/**
* Sets the frontchannel logout session required.
*
* @param frontchannelLogoutSessionRequired the new frontchannel logout session
* required
*/
public void setFrontchannelLogoutSessionRequired(boolean frontchannelLogoutSessionRequired) {
this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired;
}
/**
* Checks if is backchannel logout.
*
* @return true, if is backchannel logout
*/
public boolean isBackchannelLogout() {
return backchannelLogout;
}
/**
* Sets the backchannel logout.
*
* @param backchannelLogout the new backchannel logout
*/
public void setBackchannelLogout(boolean backchannelLogout) {
this.backchannelLogout = backchannelLogout;
}
/**
* Checks if is authorize.
*
* @return true, if is authorize
*/
public boolean isAuthorize() {
return authorize;
}
/**
* Sets the authorize.
*
* @param authorize the new authorize
*/
public void setAuthorize(boolean authorize) {
this.authorize = authorize;
}
/**
* Gets the scopes.
*
* @return the scopes
*/
public Set<String> getScopes() {
return scopes;
}
/**
* Sets the scopes.
*
* @param scopes the new scopes
*/
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
/**
* Gets the claim mapping.
*
* @return the claim mapping
*/
public Map<String, Set<String>> getClaimMapping() {
return claimMapping;
}
/**
* Sets the claim mapping.
*
* @param claimMapping the claim mapping
*/
public void setClaimMapping(Map<String, Set<String>> claimMapping) {
this.claimMapping = claimMapping;
}
/**
* Gets the authorized scopes.
*
* @return the authorized scopes
*/
public Set<String> getAuthorizedScopes() {
return authorizedScopes;
}
/**
* Sets the authorized scopes.
*
* @param authorizedScopes the new authorized scopes
*/
public void setAuthorizedScopes(Set<String> authorizedScopes) {
this.authorizedScopes = authorizedScopes;
}
/**
* Gets the claims.
*
* @return the claims
*/
public Map<String, Object> getClaims() {
return claims;
}
/**
* Sets the claims.
*
* @param claims the claims
*/
public void setClaims(Map<String, Object> claims) {
this.claims = claims;
}
/**
* Gets the sessions.
*
* @return the sessions
*/
public List<OidcSession> getSessions() {
return sessions;
}
/**
* Sets the sessions.
*
* @param sessions the new sessions
*/
public void setSessions(List<OidcSession> sessions) {
this.sessions = sessions;
}
}

View File

@ -5,8 +5,8 @@ package de.bstly.we.oidc.controller.model;
import java.util.Set; import java.util.Set;
import de.bstly.we.oidc.model.OidcAuthorizationGrantType; import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.model.OidcClientAuthenticationMethod; import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
/** /**
* The Class OidcClientModel. * The Class OidcClientModel.

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.controller.model;
import java.net.URI; import java.net.URI;
import java.util.Set; import java.util.Set;

View File

@ -0,0 +1,94 @@
/**
*
*/
package de.bstly.we.oidc.model;
import java.util.Set;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
/**
* The Class OidcAuthorization.
*/
@Entity
@Table(name = "oidc_authorizations")
public class OidcAuthorization {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "client_id")
private Long client;
@Column(name = "subject")
private Long subject;
@ElementCollection
@LazyCollection(LazyCollectionOption.FALSE)
@CollectionTable(name = "oidc_authorizations_scopes")
private Set<String> scopes;
/**
* Gets the client.
*
* @return the client
*/
public Long getClient() {
return client;
}
/**
* Sets the client.
*
* @param client the new client
*/
public void setClient(Long client) {
this.client = client;
}
/**
* Gets the subject.
*
* @return the subject
*/
public Long getSubject() {
return subject;
}
/**
* Sets the subject.
*
* @param subject the new subject
*/
public void setSubject(Long subject) {
this.subject = subject;
}
/**
* Gets the scopes.
*
* @return the scopes
*/
public Set<String> getScopes() {
return scopes;
}
/**
* Sets the scopes.
*
* @param scopes the new scopes
*/
public void setScopes(Set<String> scopes) {
this.scopes = scopes;
}
}

View File

@ -1,11 +0,0 @@
/**
*
*/
package de.bstly.we.oidc.model;
/**
* The Enum OidcAuthorizationResponseType.
*/
public enum OidcAuthorizationResponseType {
code
}

View File

@ -19,6 +19,9 @@ import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.LazyCollectionOption;
import de.bstly.we.oidc.businesslogic.model.OidcAuthorizationGrantType;
import de.bstly.we.oidc.businesslogic.model.OidcClientAuthenticationMethod;
/** /**
* The Class OidcClient. * The Class OidcClient.
*/ */
@ -58,6 +61,16 @@ public class OidcClient {
private Long tokenLifetime; private Long tokenLifetime;
@Column(name = "login_url", length = 1024) @Column(name = "login_url", length = 1024)
private String loginUrl; private String loginUrl;
@Column(name = "frontchannel_logout_uri")
private String frontchannelLogoutUri;
@Column(name = "frontchannel_logout_session_required", columnDefinition = "boolean default false")
private boolean frontchannelLogoutSessionRequired;
@Column(name = "backchannel_logout_uri")
private String backchannelLogoutUri;
@Column(name = "backchannel_logout_session_required", columnDefinition = "boolean default false")
private boolean backchannelLogoutSessionRequired;
@Column(name = "authorize", columnDefinition = "boolean default false")
private boolean authorize;
@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")
@ -243,6 +256,98 @@ public class OidcClient {
this.loginUrl = loginUrl; this.loginUrl = loginUrl;
} }
/**
* Gets the frontchannel logout uri.
*
* @return the frontchannel logout uri
*/
public String getFrontchannelLogoutUri() {
return frontchannelLogoutUri;
}
/**
* Sets the frontchannel logout uri.
*
* @param frontchannelLogoutUri the new frontchannel logout uri
*/
public void setFrontchannelLogoutUri(String frontchannelLogoutUri) {
this.frontchannelLogoutUri = frontchannelLogoutUri;
}
/**
* Checks if is frontchannel logout session required.
*
* @return true, if is frontchannel logout session required
*/
public boolean isFrontchannelLogoutSessionRequired() {
return frontchannelLogoutSessionRequired;
}
/**
* Sets the frontchannel logout session required.
*
* @param frontchannelLogoutSessionRequired the new frontchannel logout session
* required
*/
public void setFrontchannelLogoutSessionRequired(boolean frontchannelLogoutSessionRequired) {
this.frontchannelLogoutSessionRequired = frontchannelLogoutSessionRequired;
}
/**
* Gets the backchannel logout uri.
*
* @return the backchannel logout uri
*/
public String getBackchannelLogoutUri() {
return backchannelLogoutUri;
}
/**
* Sets the backchannel logout uri.
*
* @param backchannelLogoutUri the new backchannel logout uri
*/
public void setBackchannelLogoutUri(String backchannelLogoutUri) {
this.backchannelLogoutUri = backchannelLogoutUri;
}
/**
* Checks if is backchannel logout session required.
*
* @return true, if is backchannel logout session required
*/
public boolean isBackchannelLogoutSessionRequired() {
return backchannelLogoutSessionRequired;
}
/**
* Sets the backchannel logout session required.
*
* @param backchannelLogoutSessionRequired the new backchannel logout session
* required
*/
public void setBackchannelLogoutSessionRequired(boolean backchannelLogoutSessionRequired) {
this.backchannelLogoutSessionRequired = backchannelLogoutSessionRequired;
}
/**
* Checks if is authorize.
*
* @return true, if is authorize
*/
public boolean isAuthorize() {
return authorize;
}
/**
* Sets the authorize.
*
* @param authorize the new authorize
*/
public void setAuthorize(boolean authorize) {
this.authorize = authorize;
}
/** /**
* Checks if is always permitted. * Checks if is always permitted.
* *

View File

@ -0,0 +1,143 @@
/**
*
*/
package de.bstly.we.oidc.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* The Class OidcSession.
*/
@Entity
@Table(name = "oidc_sessions")
public class OidcSession {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "client_id")
private Long client;
@Column(name = "subject")
private Long subject;
@Column(name = "sid")
private String sid;
@Column(name = "id_token", length = 4000)
private String idToken;
@Column(name = "spring_session_id")
private String springSession;
/**
* Gets the id.
*
* @return the id
*/
public Long getId() {
return id;
}
/**
* Sets the id.
*
* @param id the new id
*/
public void setId(Long id) {
this.id = id;
}
/**
* Gets the client id.
*
* @return the client id
*/
public Long getClientId() {
return client;
}
/**
* Sets the client id.
*
* @param clientId the new client id
*/
public void setClientId(Long clientId) {
this.client = clientId;
}
/**
* Gets the subject.
*
* @return the subject
*/
public Long getSubject() {
return subject;
}
/**
* Sets the subject.
*
* @param subject the new subject
*/
public void setSubject(Long subject) {
this.subject = subject;
}
/**
* Gets the sid.
*
* @return the sid
*/
public String getSid() {
return sid;
}
/**
* Sets the sid.
*
* @param sid the new sid
*/
public void setSid(String sid) {
this.sid = sid;
}
/**
* Gets the id token.
*
* @return the id token
*/
public String getIdToken() {
return idToken;
}
/**
* Sets the id token.
*
* @param idToken the new id token
*/
public void setIdToken(String idToken) {
this.idToken = idToken;
}
/**
* Gets the spring session.
*
* @return the spring session
*/
public String getSpringSession() {
return springSession;
}
/**
* Sets the spring session.
*
* @param springSession the new spring session
*/
public void setSpringSession(String springSession) {
this.springSession = springSession;
}
}

View File

@ -3,6 +3,7 @@
*/ */
package de.bstly.we.oidc.model; package de.bstly.we.oidc.model;
import java.time.Instant;
import java.util.Set; import java.util.Set;
import javax.persistence.Column; import javax.persistence.Column;
@ -29,6 +30,8 @@ public class OidcToken {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id") @Column(name = "id")
private Long id; private Long id;
@Column(name = "created")
private Instant created;
@Column(name = "user_id") @Column(name = "user_id")
private Long userId; private Long userId;
@Column(name = "client_id") @Column(name = "client_id")
@ -64,6 +67,24 @@ public class OidcToken {
this.id = id; this.id = id;
} }
/**
* Gets the created.
*
* @return the created
*/
public Instant getCreated() {
return created;
}
/**
* Sets the created.
*
* @param created the new created
*/
public void setCreated(Instant created) {
this.created = created;
}
/** /**
* Gets the user id. * Gets the user id.
* *

View File

@ -0,0 +1,18 @@
/**
*
*/
package de.bstly.we.oidc.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import de.bstly.we.oidc.model.OidcAuthorization;
/**
* The Interface OidcAuthorizationRepository.
*/
@Repository
public interface OidcAuthorizationRepository
extends JpaRepository<OidcAuthorization, Long>, QuerydslPredicateExecutor<OidcAuthorization> {
}

View File

@ -0,0 +1,18 @@
/**
*
*/
package de.bstly.we.oidc.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import de.bstly.we.oidc.model.OidcSession;
/**
* The Interface OidcSessionRepository.
*/
@Repository
public interface OidcSessionRepository
extends JpaRepository<OidcSession, Long>, QuerydslPredicateExecutor<OidcSession> {
}

View File

@ -14,33 +14,20 @@ import de.bstly.we.partey.model.GameRoomPolicyTypes;
*/ */
public class MapDetailsData { public class MapDetailsData {
private String roomSlug = "";
private String mapUrl = ""; private String mapUrl = "";
private GameRoomPolicyTypes policy_type = GameRoomPolicyTypes.ANONYMOUS_POLICY; private GameRoomPolicyTypes policy_type = GameRoomPolicyTypes.ANONYMOUS_POLICY;
private List<String> tags = Lists.newArrayList(); private List<String> tags = Lists.newArrayList();
private List<CharacterTexture> textures = Lists.newArrayList();
private String contactPage = "";
private boolean authenticationMandatory; private boolean authenticationMandatory;
private String group = ""; private String roomSlug;
private String contactPage;
private String group;
private String iframeAuthentication; private String iframeAuthentication;
private String miniLogo;
/** private String loadingLogo;
* Gets the room slug. private String loginSceneLogo;
* private boolean showPoweredBy = false;
* @return the room slug private String loadingCowebsiteLogo;
*/ private boolean canReport = true;
public String getRoomSlug() {
return roomSlug;
}
/**
* Sets the room slug.
*
* @param roomSlug the new room slug
*/
public void setRoomSlug(String roomSlug) {
this.roomSlug = roomSlug;
}
/** /**
* Gets the map url. * Gets the map url.
@ -97,21 +84,39 @@ public class MapDetailsData {
} }
/** /**
* Gets the textures. * Checks if is authentication mandatory.
* *
* @return the textures * @return true, if is authentication mandatory
*/ */
public List<CharacterTexture> getTextures() { public boolean isAuthenticationMandatory() {
return textures; return authenticationMandatory;
} }
/** /**
* Sets the textures. * Sets the authentication mandatory.
* *
* @param textures the new textures * @param authenticationMandatory the new authentication mandatory
*/ */
public void setTextures(List<CharacterTexture> textures) { public void setAuthenticationMandatory(boolean authenticationMandatory) {
this.textures = textures; this.authenticationMandatory = authenticationMandatory;
}
/**
* Gets the room slug.
*
* @return the room slug
*/
public String getRoomSlug() {
return roomSlug;
}
/**
* Sets the room slug.
*
* @param roomSlug the new room slug
*/
public void setRoomSlug(String roomSlug) {
this.roomSlug = roomSlug;
} }
/** /**
@ -132,24 +137,6 @@ public class MapDetailsData {
this.contactPage = contactPage; this.contactPage = contactPage;
} }
/**
* Checks if is authentication mandatory.
*
* @return true, if is authentication mandatory
*/
public boolean isAuthenticationMandatory() {
return authenticationMandatory;
}
/**
* Sets the authentication mandatory.
*
* @param authenticationMandatory the new authentication mandatory
*/
public void setAuthenticationMandatory(boolean authenticationMandatory) {
this.authenticationMandatory = authenticationMandatory;
}
/** /**
* Gets the group. * Gets the group.
* *
@ -186,4 +173,112 @@ public class MapDetailsData {
this.iframeAuthentication = iframeAuthentication; this.iframeAuthentication = iframeAuthentication;
} }
/**
* Gets the mini logo.
*
* @return the mini logo
*/
public String getMiniLogo() {
return miniLogo;
}
/**
* Sets the mini logo.
*
* @param miniLogo the new mini logo
*/
public void setMiniLogo(String miniLogo) {
this.miniLogo = miniLogo;
}
/**
* Gets the loading logo.
*
* @return the loading logo
*/
public String getLoadingLogo() {
return loadingLogo;
}
/**
* Sets the loading logo.
*
* @param loadingLogo the new loading logo
*/
public void setLoadingLogo(String loadingLogo) {
this.loadingLogo = loadingLogo;
}
/**
* Gets the login scene logo.
*
* @return the login scene logo
*/
public String getLoginSceneLogo() {
return loginSceneLogo;
}
/**
* Sets the login scene logo.
*
* @param loginSceneLogo the new login scene logo
*/
public void setLoginSceneLogo(String loginSceneLogo) {
this.loginSceneLogo = loginSceneLogo;
}
/**
* Checks if is show powered by.
*
* @return true, if is show powered by
*/
public boolean isShowPoweredBy() {
return showPoweredBy;
}
/**
* Sets the show powered by.
*
* @param showPoweredBy the new show powered by
*/
public void setShowPoweredBy(boolean showPoweredBy) {
this.showPoweredBy = showPoweredBy;
}
/**
* Gets the loading cowebsite logo.
*
* @return the loading cowebsite logo
*/
public String getLoadingCowebsiteLogo() {
return loadingCowebsiteLogo;
}
/**
* Sets the loading cowebsite logo.
*
* @param loadingCowebsiteLogo the new loading cowebsite logo
*/
public void setLoadingCowebsiteLogo(String loadingCowebsiteLogo) {
this.loadingCowebsiteLogo = loadingCowebsiteLogo;
}
/**
* Checks if is can report.
*
* @return true, if is can report
*/
public boolean isCanReport() {
return canReport;
}
/**
* Sets the can report.
*
* @param canReport the new can report
*/
public void setCanReport(boolean canReport) {
this.canReport = canReport;
}
} }

View File

@ -189,6 +189,27 @@ public class ParteyUserTag implements UserData {
this.tag = tag; this.tag = tag;
} }
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof ParteyUserTagId) {
ParteyUserTagId parteyUserTagId = (ParteyUserTagId) obj;
return this.target.equals(parteyUserTagId.getTarget()) && this.tag.equals(parteyUserTagId.getTag());
}
return false;
}
/*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return this.target.hashCode() * this.tag.hashCode();
}
} }
} }

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.17.2</log4j2.version> <log4j2.version>2.17.2</log4j2.version>
<revision>1.8.0-SNAPSHOT</revision> <revision>1.9.1-SNAPSHOT</revision>
</properties> </properties>
<parent> <parent>