reportInvalidGrantError() {
38 | return ResponseEntity.badRequest().body(new TokenResponse("invalid_grant"));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/endpoint/token/resource/TokenRequest.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.endpoint.token.resource;
2 |
3 | import javax.validation.constraints.NotBlank;
4 | import javax.validation.constraints.NotNull;
5 | import java.net.URI;
6 |
7 | /**
8 | * Token Request as specified by:
9 | *
10 | * OAuth 2.0 (https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.3) OpenID Connect 1.0
11 | * (https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest)
12 | */
13 | public class TokenRequest {
14 |
15 | /**
16 | * Authorization Grant Type. REQUIRED One of {@link
17 | * com.example.authorizationserver.oauth.common.GrantType}
18 | */
19 | @NotBlank private final String grant_type;
20 |
21 | /**
22 | * Authorization code. REQUIRED if grant type is {@link
23 | * com.example.authorizationserver.oauth.common.GrantType#AUTHORIZATION_CODE}
24 | */
25 | private final String code;
26 |
27 | /**
28 | * Redirect URI. REQUIRED if grant type is {@link
29 | * com.example.authorizationserver.oauth.common.GrantType#AUTHORIZATION_CODE}
30 | */
31 | private final URI redirect_uri;
32 |
33 | /** Client Id. REQUIRED if not given by authorization header */
34 | private final String client_id;
35 |
36 | /**
37 | * Client Secret. REQUIRED for confidential client if not given by authorization header Applicable
38 | * for grant type is {@link
39 | * com.example.authorizationserver.oauth.common.GrantType#AUTHORIZATION_CODE} or {@link
40 | * com.example.authorizationserver.oauth.common.GrantType#CLIENT_CREDENTIALS}
41 | */
42 | private final String client_secret;
43 |
44 | /** Unhashed Code Verifier. REQUIRED for PKCE. */
45 | private final String code_verifier;
46 |
47 | /**
48 | * The resource owner username REQUIRED if grant type is {@link
49 | * com.example.authorizationserver.oauth.common.GrantType#PASSWORD}
50 | */
51 | private final String username;
52 |
53 | /**
54 | * The resource owner password. REQUIRED if grant type is {@link
55 | * com.example.authorizationserver.oauth.common.GrantType#PASSWORD}
56 | */
57 | private final String password;
58 |
59 | /**
60 | * The refresh token issued to the client. REQUIRED if grant type is {@link
61 | * com.example.authorizationserver.oauth.common.GrantType#REFRESH_TOKEN}
62 | */
63 | private final String refresh_token;
64 |
65 | /**
66 | * The scope of the access request. OPTIONAL if grant type is {@link
67 | * com.example.authorizationserver.oauth.common.GrantType#CLIENT_CREDENTIALS}
68 | */
69 | private final String scope;
70 |
71 | public TokenRequest(
72 | @NotBlank String grant_type,
73 | @NotBlank String code,
74 | @NotNull URI redirect_uri,
75 | String client_id,
76 | String client_secret,
77 | String code_verifier,
78 | String username,
79 | String password,
80 | String refresh_token,
81 | String scope) {
82 | this.grant_type = grant_type;
83 | this.code = code;
84 | this.redirect_uri = redirect_uri;
85 | this.client_id = client_id;
86 | this.client_secret = client_secret;
87 | this.code_verifier = code_verifier;
88 | this.username = username;
89 | this.password = password;
90 | this.refresh_token = refresh_token;
91 | this.scope = scope;
92 | }
93 |
94 | public String getGrant_type() {
95 | return grant_type;
96 | }
97 |
98 | public String getCode() {
99 | return code;
100 | }
101 |
102 | public URI getRedirect_uri() {
103 | return redirect_uri;
104 | }
105 |
106 | public String getClient_id() {
107 | return client_id;
108 | }
109 |
110 | public String getClient_secret() {
111 | return client_secret;
112 | }
113 |
114 | public String getCode_verifier() {
115 | return code_verifier;
116 | }
117 |
118 | public String getUsername() {
119 | return username;
120 | }
121 |
122 | public String getPassword() {
123 | return password;
124 | }
125 |
126 | public String getRefresh_token() {
127 | return refresh_token;
128 | }
129 |
130 | public String getScope() {
131 | return scope;
132 | }
133 |
134 | @Override
135 | public String toString() {
136 | return "TokenRequest{"
137 | + "grant_type='"
138 | + grant_type
139 | + '\''
140 | + ", code='"
141 | + code
142 | + '\''
143 | + ", redirect_uri="
144 | + redirect_uri
145 | + ", client_id='"
146 | + client_id
147 | + '\''
148 | + ", client_secret='*****'"
149 | + ", code_verifier='"
150 | + code_verifier
151 | + '\''
152 | + ", refresh_token='"
153 | + refresh_token
154 | + '\''
155 | + ", scope='"
156 | + scope
157 | + '\''
158 | + ", username='"
159 | + username
160 | + '\''
161 | + ", password='*****'"
162 | + '}';
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/endpoint/token/resource/TokenResponse.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.endpoint.token.resource;
2 |
3 | /**
4 | * Token Response as specified by:
5 | *
6 | *
OAuth 2.0 (https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.3) OpenID Connect 1.0
7 | * (https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest)
8 | */
9 | public class TokenResponse {
10 |
11 | public static final String BEARER_TOKEN_TYPE = "Bearer";
12 |
13 | private String access_token;
14 | private String token_type;
15 | private String refresh_token;
16 | private long expires_in;
17 | private String id_token;
18 | private String error;
19 |
20 | public TokenResponse(
21 | String access_token,
22 | String refresh_token,
23 | long expires_in,
24 | String id_token,
25 | String token_type) {
26 | this.access_token = access_token;
27 | this.refresh_token = refresh_token;
28 | this.expires_in = expires_in;
29 | this.id_token = id_token;
30 | this.token_type = token_type;
31 | }
32 |
33 | public TokenResponse(String error) {
34 | this.error = error;
35 | }
36 |
37 | public String getAccess_token() {
38 | return access_token;
39 | }
40 |
41 | public void setAccess_token(String access_token) {
42 | this.access_token = access_token;
43 | }
44 |
45 | public String getToken_type() {
46 | return token_type;
47 | }
48 |
49 | public void setToken_type(String token_type) {
50 | this.token_type = token_type;
51 | }
52 |
53 | public String getRefresh_token() {
54 | return refresh_token;
55 | }
56 |
57 | public void setRefresh_token(String refresh_token) {
58 | this.refresh_token = refresh_token;
59 | }
60 |
61 | public long getExpires_in() {
62 | return expires_in;
63 | }
64 |
65 | public void setExpires_in(long expires_in) {
66 | this.expires_in = expires_in;
67 | }
68 |
69 | public String getId_token() {
70 | return id_token;
71 | }
72 |
73 | public void setId_token(String id_token) {
74 | this.id_token = id_token;
75 | }
76 |
77 | public String getError() {
78 | return error;
79 | }
80 |
81 | public void setError(String error) {
82 | this.error = error;
83 | }
84 |
85 | @Override
86 | public String toString() {
87 | return "TokenResponse{"
88 | + "access_token='"
89 | + access_token
90 | + '\''
91 | + ", token_type='"
92 | + token_type
93 | + '\''
94 | + ", refresh_token='"
95 | + refresh_token
96 | + '\''
97 | + ", expires_in="
98 | + expires_in
99 | + ", id_token='"
100 | + id_token
101 | + '\''
102 | + '}';
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/pkce/CodeChallengeError.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.pkce;
2 |
3 | public class CodeChallengeError extends Exception {
4 | public CodeChallengeError() {
5 | super("PKCE: Code challenge failed");
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/pkce/ProofKeyForCodeExchangeVerifier.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.pkce;
2 |
3 | public interface ProofKeyForCodeExchangeVerifier {
4 |
5 | String CHALLENGE_METHOD_S_256 = "S256";
6 | String CHALLENGE_METHOD_PLAIN = "plain";
7 |
8 | /**
9 | * @param challengeMethod OPTIONAL, defaults to "plain" if not present in the request. Code
10 | * verifier transformation method is "S256" or "plain".
11 | * @param codeVerifier high-entropy cryptographic random STRING using the unreserved characters
12 | * [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" from Section 2.3 of [RFC3986], with a minimum
13 | * length of 43 characters and a maximum length of 128 characters.
14 | * @param codeChallenge The client creates a code challenge derived from the code verifier by
15 | * using one of the following transformations on the code verifier:
16 | *
plain code_challenge = code_verifier
17 | *
S256 code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
18 | *
19 | * @throws CodeChallengeError if verification of code challenge with code verifier fails
20 | */
21 | void verifyCodeChallenge(String challengeMethod, String codeVerifier, String codeChallenge)
22 | throws CodeChallengeError;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/pkce/ProofKeyForCodeExchangeVerifierStandardImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.pkce;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.security.MessageDigest;
9 | import java.security.NoSuchAlgorithmException;
10 | import java.util.Base64;
11 |
12 | import static java.nio.charset.StandardCharsets.UTF_8;
13 |
14 | @Service
15 | public class ProofKeyForCodeExchangeVerifierStandardImpl implements ProofKeyForCodeExchangeVerifier {
16 |
17 | private static final Logger LOG = LoggerFactory.getLogger(ProofKeyForCodeExchangeVerifierStandardImpl.class);
18 |
19 | @Override
20 | public void verifyCodeChallenge(String challengeMethod, String codeVerifier, String codeChallenge) throws CodeChallengeError {
21 |
22 | LOG.debug("Verifying PKCE code challenge with code verifier using method [{}]", challengeMethod);
23 |
24 | if (StringUtils.isBlank(codeVerifier)) {
25 | LOG.warn("Code verifier must not be empty");
26 | throw new CodeChallengeError();
27 | }
28 |
29 | if (codeVerifier.length() < 43 || codeVerifier.length() > 128) {
30 | LOG.warn("Code verifier must have a length between 43 and 128 characters");
31 | throw new CodeChallengeError();
32 | }
33 |
34 | if (CHALLENGE_METHOD_S_256.equalsIgnoreCase(challengeMethod)) {
35 | // Rehash the code verifier
36 | try {
37 | String rehashedChallenge = rehashCodeVerifier(codeVerifier);
38 | if (!MessageDigest.isEqual(
39 | codeChallenge.getBytes(UTF_8), rehashedChallenge.getBytes(UTF_8))) {
40 | throw new CodeChallengeError();
41 | }
42 | } catch (NoSuchAlgorithmException e) {
43 | throw new CodeChallengeError();
44 | }
45 | } else if (challengeMethod == null || challengeMethod.isBlank() || CHALLENGE_METHOD_PLAIN.equalsIgnoreCase(challengeMethod)) {
46 | if (!codeChallenge.equals(codeVerifier)) {
47 | throw new CodeChallengeError();
48 | }
49 | } else {
50 | LOG.warn("Invalid Code Challenge [{}]", codeChallenge);
51 | throw new CodeChallengeError();
52 | }
53 | }
54 |
55 | private String rehashCodeVerifier(String codeVerifier) throws NoSuchAlgorithmException {
56 | final MessageDigest digest = MessageDigest.getInstance("SHA-256");
57 | final byte[] hashedBytes = digest.digest(codeVerifier.getBytes(UTF_8));
58 | return Base64.getUrlEncoder().withoutPadding().encodeToString(hashedBytes);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/store/AuthorizationCode.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.store;
2 |
3 | import java.net.URI;
4 | import java.time.LocalDateTime;
5 | import java.util.Set;
6 |
7 | public class AuthorizationCode {
8 |
9 | private final String clientId;
10 | private final URI redirectUri;
11 | private final Set scopes;
12 | private final String code;
13 | private final LocalDateTime expiry;
14 | private final String subject;
15 | private final String nonce;
16 | private final String code_challenge;
17 | private final String code_challenge_method;
18 |
19 | public AuthorizationCode(
20 | String clientId,
21 | URI redirectUri,
22 | Set scopes,
23 | String code,
24 | String subject,
25 | String nonce,
26 | String code_challenge,
27 | String code_challenge_method) {
28 | this.clientId = clientId;
29 | this.redirectUri = redirectUri;
30 | this.scopes = scopes;
31 | this.code = code;
32 | this.subject = subject;
33 | this.nonce = nonce;
34 | this.code_challenge = code_challenge;
35 | this.code_challenge_method = code_challenge_method;
36 | this.expiry = LocalDateTime.now().plusMinutes(2);
37 | }
38 |
39 | public String getClientId() {
40 | return clientId;
41 | }
42 |
43 | public String getSubject() {
44 | return subject;
45 | }
46 |
47 | public String getNonce() {
48 | return nonce;
49 | }
50 |
51 | public URI getRedirectUri() {
52 | return redirectUri;
53 | }
54 |
55 | public String getCode() {
56 | return code;
57 | }
58 |
59 | public LocalDateTime getExpiry() {
60 | return expiry;
61 | }
62 |
63 | public Set getScopes() {
64 | return scopes;
65 | }
66 |
67 | public String getCode_challenge() {
68 | return code_challenge;
69 | }
70 |
71 | public String getCode_challenge_method() {
72 | return code_challenge_method;
73 | }
74 |
75 | public boolean isExpired() {
76 | return LocalDateTime.now().isAfter(getExpiry());
77 | }
78 |
79 | @Override
80 | public String toString() {
81 | return "AuthorizationState{"
82 | + "clientId='"
83 | + clientId
84 | + '\''
85 | + ", redirectUri="
86 | + redirectUri
87 | + ", scopes="
88 | + scopes
89 | + ", code='"
90 | + code
91 | + '\''
92 | + ", expiry="
93 | + expiry
94 | + ", subject="
95 | + subject
96 | + ", nonce="
97 | + nonce
98 | + ", code_challenge="
99 | + code_challenge
100 | + ", code_challenge_method="
101 | + code_challenge_method
102 | + '}';
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oauth/store/AuthorizationCodeService.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oauth.store;
2 |
3 | import org.apache.commons.lang3.RandomStringUtils;
4 | import org.springframework.stereotype.Service;
5 |
6 | import java.net.URI;
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.Set;
10 |
11 | @Service
12 | public class AuthorizationCodeService {
13 |
14 | private final Map codeMap = new HashMap<>();
15 |
16 | public AuthorizationCode getCode(String code) {
17 | return codeMap.get(code);
18 | }
19 |
20 | public AuthorizationCode createAndStoreAuthorizationState(
21 | String clientId,
22 | URI redirectUri,
23 | Set scopes,
24 | String subject,
25 | String nonce,
26 | String code_challenge,
27 | String code_challenge_method) {
28 | String code = RandomStringUtils.random(32, true, true);
29 | AuthorizationCode authorizationCode =
30 | new AuthorizationCode(
31 | clientId,
32 | redirectUri,
33 | scopes,
34 | code,
35 | subject,
36 | nonce,
37 | code_challenge,
38 | code_challenge_method);
39 | codeMap.put(code, authorizationCode);
40 | return authorizationCode;
41 | }
42 |
43 | public void removeCode(String code) {
44 | codeMap.remove(code);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oidc/common/Scope.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oidc.common;
2 |
3 | public enum Scope {
4 | OPENID,
5 | OFFLINE_ACCESS,
6 | EMAIL,
7 | ADDRESS,
8 | PHONE,
9 | PROFILE
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oidc/endpoint/discovery/DiscoveryEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oidc.endpoint.discovery;
2 |
3 | import com.example.authorizationserver.jwks.JwtPki;
4 | import com.example.authorizationserver.oauth.common.GrantType;
5 | import com.example.authorizationserver.oauth.endpoint.AuthorizationEndpoint;
6 | import com.example.authorizationserver.oauth.endpoint.introspection.IntrospectionEndpoint;
7 | import com.example.authorizationserver.oauth.endpoint.revocation.RevocationEndpoint;
8 | import com.example.authorizationserver.oauth.endpoint.token.TokenEndpoint;
9 | import com.example.authorizationserver.oidc.common.Scope;
10 | import com.example.authorizationserver.oidc.endpoint.userinfo.UserInfoEndpoint;
11 | import io.swagger.v3.oas.annotations.Operation;
12 | import org.springframework.web.bind.annotation.CrossOrigin;
13 | import org.springframework.web.bind.annotation.GetMapping;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 | import org.springframework.web.bind.annotation.RestController;
16 |
17 | @CrossOrigin(originPatterns = "*", allowCredentials = "true", allowedHeaders = "*")
18 | @RestController
19 | @RequestMapping(DiscoveryEndpoint.ENDPOINT)
20 | public class DiscoveryEndpoint {
21 |
22 | public static final String ENDPOINT = "/.well-known/openid-configuration";
23 |
24 | private final JwtPki jwtPki;
25 |
26 | public DiscoveryEndpoint(JwtPki jwtPki) {
27 | this.jwtPki = jwtPki;
28 | }
29 |
30 | @Operation(
31 | summary = "Retrieves the public OpenID Connect configuration",
32 | tags = {"OpenID Connect Discovery"}
33 | )
34 | @GetMapping
35 | public Discovery discoveryEndpoint() {
36 |
37 | Discovery discovery = new Discovery();
38 | discovery.setAuthorization_endpoint(jwtPki.getIssuer() + AuthorizationEndpoint.ENDPOINT);
39 | discovery.setIssuer(jwtPki.getIssuer());
40 | discovery.setToken_endpoint(jwtPki.getIssuer() + TokenEndpoint.ENDPOINT);
41 | discovery.setIntrospection_endpoint(jwtPki.getIssuer() + IntrospectionEndpoint.ENDPOINT);
42 | discovery.setRevocation_endpoint(jwtPki.getIssuer() + RevocationEndpoint.ENDPOINT);
43 | discovery.setUserinfo_endpoint(jwtPki.getIssuer() + UserInfoEndpoint.ENDPOINT);
44 | discovery.setJwks_uri(jwtPki.getIssuer() + "/jwks");
45 | discovery.getGrant_types_supported().add(GrantType.AUTHORIZATION_CODE.getGrant());
46 | discovery.getGrant_types_supported().add(GrantType.CLIENT_CREDENTIALS.getGrant());
47 | discovery.getGrant_types_supported().add(GrantType.PASSWORD.getGrant());
48 | discovery.getGrant_types_supported().add(GrantType.TOKEN_EXCHANGE.getGrant());
49 | discovery.getResponse_types_supported().add("code");
50 | discovery.getScopes_supported().add(Scope.OPENID.name().toLowerCase());
51 | discovery.getScopes_supported().add(Scope.OFFLINE_ACCESS.name().toLowerCase());
52 | discovery.getScopes_supported().add(Scope.PROFILE.name().toLowerCase());
53 | discovery.getScopes_supported().add(Scope.EMAIL.name().toLowerCase());
54 | discovery.getScopes_supported().add(Scope.PHONE.name().toLowerCase());
55 | discovery.getScopes_supported().add(Scope.ADDRESS.name().toLowerCase());
56 | discovery.getResponse_modes_supported().add("query");
57 | discovery.getResponse_modes_supported().add("form_post");
58 | discovery.getSubject_types_supported().add("public");
59 | discovery.getId_token_signing_alg_values_supported().add("RS256");
60 | discovery.getToken_endpoint_auth_methods_supported().add("client_secret_basic");
61 | discovery.getToken_endpoint_auth_methods_supported().add("client_secret_post");
62 | discovery.getCode_challenge_methods_supported().add("S256");
63 | discovery.getCode_challenge_methods_supported().add("plain");
64 | discovery.getClaims_supported().add("aud");
65 | discovery.getClaims_supported().add("auth_time");
66 | discovery.getClaims_supported().add("created_at");
67 | discovery.getClaims_supported().add("gender");
68 | discovery.getClaims_supported().add("birthdate");
69 | discovery.getClaims_supported().add("locale");
70 | discovery.getClaims_supported().add("zoneinfo");
71 | discovery.getClaims_supported().add("address");
72 | discovery.getClaims_supported().add("email");
73 | discovery.getClaims_supported().add("email_verified");
74 | discovery.getClaims_supported().add("exp");
75 | discovery.getClaims_supported().add("website");
76 | discovery.getClaims_supported().add("picture");
77 | discovery.getClaims_supported().add("family_name");
78 | discovery.getClaims_supported().add("given_name");
79 | discovery.getClaims_supported().add("iat");
80 | discovery.getClaims_supported().add("identities");
81 | discovery.getClaims_supported().add("iss");
82 | discovery.getClaims_supported().add("identities");
83 | discovery.getClaims_supported().add("name");
84 | discovery.getClaims_supported().add("nickname");
85 | discovery.getClaims_supported().add("phone_number");
86 | discovery.getClaims_supported().add("phone_number_verified");
87 | discovery.getClaims_supported().add("sub");
88 | discovery.getToken_endpoint_auth_signing_alg_values_supported().add("RS256");
89 |
90 | return discovery;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/oidc/endpoint/userinfo/UserInfoEndpoint.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oidc.endpoint.userinfo;
2 |
3 | import com.example.authorizationserver.oauth.common.AuthenticationUtil;
4 | import com.example.authorizationserver.scim.model.ScimUserEntity;
5 | import com.example.authorizationserver.scim.service.ScimService;
6 | import com.example.authorizationserver.token.jwt.JsonWebTokenService;
7 | import com.example.authorizationserver.token.store.TokenService;
8 | import com.example.authorizationserver.token.store.model.JsonWebToken;
9 | import com.example.authorizationserver.token.store.model.OpaqueToken;
10 | import com.nimbusds.jose.JOSEException;
11 | import com.nimbusds.jwt.JWTClaimsSet;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import org.springframework.http.HttpStatus;
15 | import org.springframework.http.ResponseEntity;
16 | import org.springframework.web.bind.MissingRequestHeaderException;
17 | import org.springframework.web.bind.annotation.*;
18 |
19 | import java.text.ParseException;
20 | import java.util.Optional;
21 | import java.util.UUID;
22 |
23 | @CrossOrigin(originPatterns = "*", allowCredentials = "true", allowedHeaders = "*")
24 | @RestController
25 | @RequestMapping(UserInfoEndpoint.ENDPOINT)
26 | public class UserInfoEndpoint {
27 | private static final Logger LOG =
28 | LoggerFactory.getLogger(UserInfoEndpoint.class);
29 |
30 | public static final String ENDPOINT = "/userinfo";
31 |
32 | private final TokenService tokenService;
33 | private final ScimService scimService;
34 | private final JsonWebTokenService jsonWebTokenService;
35 |
36 | public UserInfoEndpoint(
37 | TokenService tokenService, ScimService scimService, JsonWebTokenService jsonWebTokenService) {
38 | this.tokenService = tokenService;
39 | this.scimService = scimService;
40 | this.jsonWebTokenService = jsonWebTokenService;
41 | }
42 |
43 | @GetMapping
44 | public ResponseEntity userInfo(
45 | @RequestHeader("Authorization") String authorizationHeader) {
46 | String tokenValue = AuthenticationUtil.fromBearerAuthHeader(authorizationHeader);
47 |
48 | LOG.debug("Calling userinfo with bearer token header {}", tokenValue);
49 |
50 | JsonWebToken jsonWebToken = tokenService.findJsonWebToken(tokenValue);
51 | Optional user;
52 | if (jsonWebToken != null) {
53 | try {
54 | JWTClaimsSet jwtClaimsSet =
55 | jsonWebTokenService.parseAndValidateToken(jsonWebToken.getValue());
56 | if (TokenService.ANONYMOUS_TOKEN.equals(jwtClaimsSet.getStringClaim("ctx"))) {
57 | return ResponseEntity.ok(new UserInfo(jwtClaimsSet.getSubject()));
58 | } else {
59 | user = scimService.findUserByIdentifier(UUID.fromString(jwtClaimsSet.getSubject()));
60 | return user.map(u -> ResponseEntity.ok(new UserInfo(u)))
61 | .orElse(
62 | ResponseEntity.status(HttpStatus.UNAUTHORIZED)
63 | .header("WWW-Authenticate", "Bearer")
64 | .build());
65 | }
66 | } catch (ParseException | JOSEException e) {
67 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
68 | .header("WWW-Authenticate", "Bearer")
69 | .body(new UserInfo("invalid_token", "Access Token is invalid"));
70 | }
71 | } else {
72 | OpaqueToken opaqueWebToken = tokenService.findOpaqueToken(tokenValue);
73 | if (opaqueWebToken != null) {
74 | opaqueWebToken.validate();
75 | if (TokenService.ANONYMOUS_TOKEN.equals(opaqueWebToken.getSubject())) {
76 | return ResponseEntity.ok(new UserInfo(opaqueWebToken.getSubject()));
77 | } else {
78 | user = scimService.findUserByIdentifier(UUID.fromString(opaqueWebToken.getSubject()));
79 | return user.map(u -> ResponseEntity.ok(new UserInfo(u)))
80 | .orElse(
81 | ResponseEntity.status(HttpStatus.UNAUTHORIZED)
82 | .header("WWW-Authenticate", "Bearer")
83 | .build());
84 | }
85 | } else {
86 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
87 | .header("WWW-Authenticate", "Bearer")
88 | .body(new UserInfo("invalid_token", "Access Token is invalid"));
89 | }
90 | }
91 | }
92 |
93 | @ExceptionHandler(MissingRequestHeaderException.class)
94 | public ResponseEntity handle(MissingRequestHeaderException ex) {
95 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
96 | .header("WWW-Authenticate", "Bearer")
97 | .body(new UserInfo("invalid_token", "Access Token is required"));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/AddressResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import com.example.authorizationserver.scim.model.ScimAddressEntity;
4 |
5 | import javax.validation.constraints.NotBlank;
6 | import javax.validation.constraints.Size;
7 |
8 | public class AddressResource {
9 |
10 | @NotBlank
11 | @Size(max = 100)
12 | private String street;
13 |
14 | @NotBlank
15 | @Size(max = 20)
16 | private String zip;
17 |
18 | @NotBlank
19 | @Size(max = 100)
20 | private String city;
21 |
22 | @Size(max = 100)
23 | private String state;
24 |
25 | @NotBlank
26 | @Size(max = 100)
27 | private String country;
28 |
29 | public AddressResource() {}
30 |
31 | public AddressResource(ScimAddressEntity address) {
32 | this.city = address.getLocality();
33 | this.country = address.getCountry();
34 | this.state = address.getRegion();
35 | this.street = address.getStreetAddress();
36 | this.zip = address.getPostalCode();
37 | }
38 |
39 | public String getStreet() {
40 | return street;
41 | }
42 |
43 | public void setStreet(String street) {
44 | this.street = street;
45 | }
46 |
47 | public String getZip() {
48 | return zip;
49 | }
50 |
51 | public void setZip(String zip) {
52 | this.zip = zip;
53 | }
54 |
55 | public String getCity() {
56 | return city;
57 | }
58 |
59 | public void setCity(String city) {
60 | this.city = city;
61 | }
62 |
63 | public String getState() {
64 | return state;
65 | }
66 |
67 | public void setState(String state) {
68 | this.state = state;
69 | }
70 |
71 | public String getCountry() {
72 | return country;
73 | }
74 |
75 | public void setCountry(String country) {
76 | this.country = country;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/CreateScimUserResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.NotBlank;
8 | import javax.validation.constraints.NotNull;
9 | import javax.validation.constraints.Size;
10 | import java.net.URI;
11 | import java.util.Set;
12 | import java.util.UUID;
13 |
14 | public class CreateScimUserResource extends ScimUserResource {
15 |
16 | @NotNull
17 | @NotBlank
18 | @Size(min = 8, max = 255)
19 | private String password;
20 |
21 | public CreateScimUserResource() {
22 | }
23 |
24 | public CreateScimUserResource(ScimMetaResource meta, UUID identifier, String externalId, String userName,
25 | String familyName, String givenName, String middleName, String honorificPrefix,
26 | String honorificSuffix, String nickName, URI profileUrl, String title, String userType,
27 | String preferredLanguage, String locale, String timezone, boolean active, String password,
28 | Set emails, Set phoneNumbers,
29 | Set ims, Set photos, Set addresses,
30 | Set groups, Set entitlements, Set roles,
31 | Set x509Certificates) {
32 | super(meta, identifier, externalId, userName, familyName, givenName, middleName, honorificPrefix, honorificSuffix, nickName, profileUrl, title, userType, preferredLanguage, locale, timezone, active, emails, phoneNumbers, ims, photos, addresses, groups, entitlements, roles, x509Certificates);
33 | this.password = password;
34 | }
35 |
36 | public String getPassword() {
37 | return password;
38 | }
39 |
40 | public void setPassword(String password) {
41 | this.password = password;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return new ToStringBuilder(this)
47 | .appendSuper(super.toString())
48 | .append("password", password)
49 | .toString();
50 | }
51 |
52 | @Override
53 | public boolean equals(Object o) {
54 | if (this == o) return true;
55 |
56 | if (o == null || getClass() != o.getClass()) return false;
57 |
58 | CreateScimUserResource that = (CreateScimUserResource) o;
59 |
60 | return new EqualsBuilder()
61 | .appendSuper(super.equals(o))
62 | .append(password, that.password)
63 | .isEquals();
64 | }
65 |
66 | @Override
67 | public int hashCode() {
68 | return new HashCodeBuilder(17, 37)
69 | .appendSuper(super.hashCode())
70 | .append(password)
71 | .toHashCode();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimAddressResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.apache.commons.lang3.builder.EqualsBuilder;
5 | import org.apache.commons.lang3.builder.HashCodeBuilder;
6 | import org.apache.commons.lang3.builder.ToStringBuilder;
7 |
8 | import javax.validation.constraints.NotNull;
9 | import javax.validation.constraints.Pattern;
10 | import javax.validation.constraints.Size;
11 | import java.io.Serializable;
12 |
13 | public class ScimAddressResource implements Serializable {
14 |
15 | @Size(max = 100)
16 | private String streetAddress;
17 |
18 | @Size(max = 100)
19 | private String locality;
20 |
21 | @Size(max = 100)
22 | private String region;
23 |
24 | @Size(max = 100)
25 | private String postalCode;
26 |
27 | @Size(max = 2)
28 | @Pattern(regexp = "^[A-Z]{2}$")
29 | private String country;
30 |
31 | @Size(max = 100)
32 | private String type;
33 |
34 | @NotNull
35 | private boolean primary;
36 |
37 | public ScimAddressResource() {
38 | }
39 |
40 | public ScimAddressResource(String streetAddress, String locality, String region, String postalCode, String country, String type, boolean primary) {
41 | this.streetAddress = streetAddress;
42 | this.locality = locality;
43 | this.region = region;
44 | this.postalCode = postalCode;
45 | this.country = country;
46 | this.type = type;
47 | this.primary = primary;
48 | }
49 |
50 | public String formatted() {
51 | return ""
52 | + (StringUtils.isNotBlank(streetAddress) ? streetAddress + "\n" : "")
53 | + (StringUtils.isNotBlank(locality) ? locality + " " : "")
54 | + (StringUtils.isNotBlank(postalCode) ? postalCode + "\n" : "")
55 | + (StringUtils.isNotBlank(country) ? country : "");
56 | }
57 |
58 | public String getStreetAddress() {
59 | return streetAddress;
60 | }
61 |
62 | public void setStreetAddress(String streetAddress) {
63 | this.streetAddress = streetAddress;
64 | }
65 |
66 | public String getLocality() {
67 | return locality;
68 | }
69 |
70 | public void setLocality(String locality) {
71 | this.locality = locality;
72 | }
73 |
74 | public String getRegion() {
75 | return region;
76 | }
77 |
78 | public void setRegion(String region) {
79 | this.region = region;
80 | }
81 |
82 | public String getPostalCode() {
83 | return postalCode;
84 | }
85 |
86 | public void setPostalCode(String postalCode) {
87 | this.postalCode = postalCode;
88 | }
89 |
90 | public String getCountry() {
91 | return country;
92 | }
93 |
94 | public void setCountry(String country) {
95 | this.country = country;
96 | }
97 |
98 | public String getType() {
99 | return type;
100 | }
101 |
102 | public void setType(String type) {
103 | this.type = type;
104 | }
105 |
106 | public boolean isPrimary() {
107 | return primary;
108 | }
109 |
110 | public void setPrimary(boolean primary) {
111 | this.primary = primary;
112 | }
113 |
114 | @Override
115 | public String toString() {
116 | return new ToStringBuilder(this)
117 | .append("streetAddress", streetAddress)
118 | .append("locality", locality)
119 | .append("region", region)
120 | .append("postalCode", postalCode)
121 | .append("country", country)
122 | .append("type", type)
123 | .append("primary", primary)
124 | .toString();
125 | }
126 |
127 | @Override
128 | public boolean equals(Object o) {
129 | if (this == o) return true;
130 |
131 | if (o == null || getClass() != o.getClass()) return false;
132 |
133 | ScimAddressResource that = (ScimAddressResource) o;
134 |
135 | return new EqualsBuilder()
136 | .appendSuper(super.equals(o))
137 | .append(primary, that.primary)
138 | .append(streetAddress, that.streetAddress)
139 | .append(locality, that.locality)
140 | .append(region, that.region)
141 | .append(postalCode, that.postalCode)
142 | .append(country, that.country)
143 | .append(type, that.type)
144 | .isEquals();
145 | }
146 |
147 | @Override
148 | public int hashCode() {
149 | return new HashCodeBuilder(17, 37)
150 | .appendSuper(super.hashCode())
151 | .append(streetAddress)
152 | .append(locality)
153 | .append(region)
154 | .append(postalCode)
155 | .append(country)
156 | .append(type)
157 | .append(primary)
158 | .toHashCode();
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimEmailResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.Email;
8 | import javax.validation.constraints.NotNull;
9 | import java.io.Serializable;
10 |
11 | public class ScimEmailResource implements Serializable {
12 |
13 | @NotNull
14 | @Email
15 | private String value;
16 |
17 | @NotNull
18 | private String type;
19 |
20 | private boolean primary;
21 |
22 | public ScimEmailResource() {
23 | }
24 |
25 | public ScimEmailResource(String value, String type, boolean primary) {
26 | this.value = value;
27 | this.type = type;
28 | this.primary = primary;
29 | }
30 |
31 | public String getValue() {
32 | return value;
33 | }
34 |
35 | public void setValue(String value) {
36 | this.value = value;
37 | }
38 |
39 | public boolean isPrimary() {
40 | return primary;
41 | }
42 |
43 | public void setPrimary(boolean primary) {
44 | this.primary = primary;
45 | }
46 |
47 | public String getType() {
48 | return type;
49 | }
50 |
51 | public void setType(String type) {
52 | this.type = type;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return new ToStringBuilder(this)
58 | .append("value", value)
59 | .append("type", type)
60 | .append("primary", primary)
61 | .toString();
62 | }
63 |
64 | @Override
65 | public boolean equals(Object o) {
66 | if (this == o) return true;
67 |
68 | if (o == null || getClass() != o.getClass()) return false;
69 |
70 | ScimEmailResource that = (ScimEmailResource) o;
71 |
72 | return new EqualsBuilder()
73 | .appendSuper(super.equals(o))
74 | .append(primary, that.primary)
75 | .append(value, that.value)
76 | .append(type, that.type)
77 | .isEquals();
78 | }
79 |
80 | @Override
81 | public int hashCode() {
82 | return new HashCodeBuilder(17, 37)
83 | .appendSuper(super.hashCode())
84 | .append(value)
85 | .append(type)
86 | .append(primary)
87 | .toHashCode();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimGroupListResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.NotEmpty;
8 | import javax.validation.constraints.NotNull;
9 | import javax.validation.constraints.Size;
10 | import java.util.List;
11 | import java.util.UUID;
12 |
13 | public class ScimGroupListResource extends ScimResource {
14 |
15 | public static final String SCIM_GROUP_SCHEMA = "urn:ietf:params:scim:schemas:core:2.0:Group";
16 |
17 | @NotNull
18 | @NotEmpty
19 | @Size(min = 1, max = 255)
20 | private String displayName;
21 |
22 | public ScimGroupListResource() {
23 | }
24 |
25 | public ScimGroupListResource(ScimMetaResource meta, UUID identifier, String externalId, String displayName) {
26 | super(List.of(SCIM_GROUP_SCHEMA), meta, identifier, externalId);
27 | this.displayName = displayName;
28 | }
29 |
30 | public String getDisplayName() {
31 | return displayName;
32 | }
33 |
34 | public void setDisplayName(String displayName) {
35 | this.displayName = displayName;
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return new ToStringBuilder(this)
41 | .appendSuper(super.toString())
42 | .append("displayName", displayName)
43 | .toString();
44 | }
45 |
46 | @Override
47 | public boolean equals(Object o) {
48 | if (this == o) return true;
49 |
50 | if (o == null || getClass() != o.getClass()) return false;
51 |
52 | ScimGroupListResource that = (ScimGroupListResource) o;
53 |
54 | return new EqualsBuilder()
55 | .appendSuper(super.equals(o))
56 | .append(displayName, that.displayName)
57 | .isEquals();
58 | }
59 |
60 | @Override
61 | public int hashCode() {
62 | return new HashCodeBuilder(17, 37)
63 | .appendSuper(super.hashCode())
64 | .append(displayName)
65 | .toHashCode();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimGroupResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import java.util.HashSet;
8 | import java.util.Set;
9 | import java.util.UUID;
10 |
11 | public class ScimGroupResource extends ScimGroupListResource {
12 |
13 | private Set members = new HashSet<>();
14 |
15 | public ScimGroupResource() {
16 | }
17 |
18 | public ScimGroupResource(ScimMetaResource meta, UUID identifier, String externalId, String displayName, Set members) {
19 | super(meta, identifier, externalId, displayName);
20 | this.members = members;
21 | }
22 |
23 | public Set getMembers() {
24 | return members;
25 | }
26 |
27 | public void setMembers(Set members) {
28 | this.members = members;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return new ToStringBuilder(this)
34 | .appendSuper(super.toString())
35 | .append("members", members)
36 | .toString();
37 | }
38 |
39 | @Override
40 | public boolean equals(Object o) {
41 | if (this == o) return true;
42 |
43 | if (o == null || getClass() != o.getClass()) return false;
44 |
45 | ScimGroupResource that = (ScimGroupResource) o;
46 |
47 | return new EqualsBuilder()
48 | .appendSuper(super.equals(o))
49 | .append(members, that.members)
50 | .isEquals();
51 | }
52 |
53 | @Override
54 | public int hashCode() {
55 | return new HashCodeBuilder(17, 37)
56 | .appendSuper(super.hashCode())
57 | .append(members)
58 | .toHashCode();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimImsResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.io.Serializable;
10 |
11 | public class ScimImsResource implements Serializable {
12 |
13 | @NotNull
14 | @Size(min = 5, max = 50)
15 | private String value;
16 |
17 | @NotNull
18 | @Size(min = 1, max = 50)
19 | private String type;
20 |
21 | public ScimImsResource() {
22 | }
23 |
24 | public ScimImsResource(String value, String type) {
25 | this.value = value;
26 | this.type = type;
27 | }
28 |
29 | public String getValue() {
30 | return value;
31 | }
32 |
33 | public void setValue(String value) {
34 | this.value = value;
35 | }
36 |
37 | public String getType() {
38 | return type;
39 | }
40 |
41 | public void setType(String type) {
42 | this.type = type;
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return new ToStringBuilder(this)
48 | .append("value", value)
49 | .append("type", type)
50 | .toString();
51 | }
52 |
53 | @Override
54 | public boolean equals(Object o) {
55 | if (this == o) return true;
56 |
57 | if (o == null || getClass() != o.getClass()) return false;
58 |
59 | ScimImsResource that = (ScimImsResource) o;
60 |
61 | return new EqualsBuilder()
62 | .append(value, that.value)
63 | .append(type, that.type)
64 | .isEquals();
65 | }
66 |
67 | @Override
68 | public int hashCode() {
69 | return new HashCodeBuilder(17, 37)
70 | .append(value)
71 | .append(type)
72 | .toHashCode();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimMetaResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import java.io.Serializable;
8 | import java.time.Instant;
9 | import java.time.LocalDateTime;
10 |
11 | public class ScimMetaResource implements Serializable {
12 |
13 | private String resourceType;
14 |
15 | private Instant created;
16 |
17 | private Instant lastModified;
18 |
19 | private String version;
20 |
21 | private String location;
22 |
23 | public ScimMetaResource() {
24 | }
25 |
26 | public ScimMetaResource(String resourceType, Instant created, Instant lastModified, String version, String location) {
27 | this.resourceType = resourceType;
28 | this.created = created;
29 | this.lastModified = lastModified;
30 | this.version = version;
31 | this.location = location;
32 | }
33 |
34 | public String getResourceType() {
35 | return resourceType;
36 | }
37 |
38 | public void setResourceType(String resourceType) {
39 | this.resourceType = resourceType;
40 | }
41 |
42 | public Instant getCreated() {
43 | return created;
44 | }
45 |
46 | public void setCreated(Instant created) {
47 | this.created = created;
48 | }
49 |
50 | public Instant getLastModified() {
51 | return lastModified;
52 | }
53 |
54 | public void setLastModified(Instant lastModified) {
55 | this.lastModified = lastModified;
56 | }
57 |
58 | public String getVersion() {
59 | return version;
60 | }
61 |
62 | public void setVersion(String version) {
63 | this.version = version;
64 | }
65 |
66 | public String getLocation() {
67 | return location;
68 | }
69 |
70 | public void setLocation(String location) {
71 | this.location = location;
72 | }
73 |
74 | @Override
75 | public String toString() {
76 | return new ToStringBuilder(this)
77 | .append("resourceType", resourceType)
78 | .append("created", created)
79 | .append("lastModified", lastModified)
80 | .append("version", version)
81 | .append("location", location)
82 | .toString();
83 | }
84 |
85 | @Override
86 | public boolean equals(Object o) {
87 | if (this == o) return true;
88 |
89 | if (o == null || getClass() != o.getClass()) return false;
90 |
91 | ScimMetaResource that = (ScimMetaResource) o;
92 |
93 | return new EqualsBuilder()
94 | .append(resourceType, that.resourceType)
95 | .append(created, that.created)
96 | .append(lastModified, that.lastModified)
97 | .append(version, that.version)
98 | .append(location, that.location)
99 | .isEquals();
100 | }
101 |
102 | @Override
103 | public int hashCode() {
104 | return new HashCodeBuilder(17, 37)
105 | .append(resourceType)
106 | .append(created)
107 | .append(lastModified)
108 | .append(version)
109 | .append(location)
110 | .toHashCode();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimPhoneNumberResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.io.Serializable;
10 |
11 | public class ScimPhoneNumberResource implements Serializable {
12 |
13 | @NotNull
14 | @Size(min = 5, max = 50)
15 | private String value;
16 |
17 | @NotNull
18 | @Size(min = 1, max = 50)
19 | private String type;
20 |
21 | public ScimPhoneNumberResource() {
22 | }
23 |
24 | public ScimPhoneNumberResource(String value, String type) {
25 | this.value = value;
26 | this.type = type;
27 | }
28 |
29 | public String getValue() {
30 | return value;
31 | }
32 |
33 | public void setValue(String value) {
34 | this.value = value;
35 | }
36 |
37 | public String getType() {
38 | return type;
39 | }
40 |
41 | public void setType(String type) {
42 | this.type = type;
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return new ToStringBuilder(this)
48 | .append("phone", value)
49 | .append("type", type)
50 | .toString();
51 | }
52 |
53 | @Override
54 | public boolean equals(Object o) {
55 | if (this == o) return true;
56 |
57 | if (o == null || getClass() != o.getClass()) return false;
58 |
59 | ScimPhoneNumberResource that = (ScimPhoneNumberResource) o;
60 |
61 | return new EqualsBuilder()
62 | .appendSuper(super.equals(o))
63 | .append(value, that.value)
64 | .append(type, that.type)
65 | .isEquals();
66 | }
67 |
68 | @Override
69 | public int hashCode() {
70 | return new HashCodeBuilder(17, 37)
71 | .appendSuper(super.hashCode())
72 | .append(value)
73 | .append(type)
74 | .toHashCode();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimPhotoResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.io.Serializable;
10 | import java.net.URI;
11 |
12 | public class ScimPhotoResource implements Serializable {
13 |
14 | @NotNull
15 | private URI value;
16 |
17 | @NotNull
18 | @Size(min = 1, max = 50)
19 | private String type;
20 |
21 | public ScimPhotoResource() {
22 | }
23 |
24 | public ScimPhotoResource(URI value, String type) {
25 | this.value = value;
26 | this.type = type;
27 | }
28 |
29 | public URI getValue() {
30 | return value;
31 | }
32 |
33 | public void setValue(URI value) {
34 | this.value = value;
35 | }
36 |
37 | public String getType() {
38 | return type;
39 | }
40 |
41 | public void setType(String type) {
42 | this.type = type;
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return new ToStringBuilder(this)
48 | .append("value", value)
49 | .append("type", type)
50 | .toString();
51 | }
52 |
53 | @Override
54 | public boolean equals(Object o) {
55 | if (this == o) return true;
56 |
57 | if (o == null || getClass() != o.getClass()) return false;
58 |
59 | ScimPhotoResource that = (ScimPhotoResource) o;
60 |
61 | return new EqualsBuilder()
62 | .append(value, that.value)
63 | .append(type, that.type)
64 | .isEquals();
65 | }
66 |
67 | @Override
68 | public int hashCode() {
69 | return new HashCodeBuilder(17, 37)
70 | .append(value)
71 | .append(type)
72 | .toHashCode();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimRefResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.constraints.NotEmpty;
8 | import javax.validation.constraints.NotNull;
9 | import javax.validation.constraints.Size;
10 | import java.io.Serializable;
11 | import java.net.URI;
12 | import java.util.UUID;
13 |
14 | public class ScimRefResource implements Serializable {
15 |
16 | @NotNull
17 | private UUID value;
18 |
19 | @NotNull
20 | @NotEmpty
21 | @Size(min = 1, max = 255)
22 | private String display;
23 |
24 | @NotNull
25 | private URI $ref;
26 |
27 | public ScimRefResource() {
28 | }
29 |
30 | public ScimRefResource(UUID value, URI $ref, String display) {
31 | this.value = value;
32 | this.display = display;
33 | this.$ref = $ref;
34 | }
35 |
36 | public UUID getValue() {
37 | return value;
38 | }
39 |
40 | public void setValue(UUID value) {
41 | this.value = value;
42 | }
43 |
44 | public URI get$ref() {
45 | return $ref;
46 | }
47 |
48 | public void set$ref(URI $ref) {
49 | this.$ref = $ref;
50 | }
51 |
52 | public String getDisplay() {
53 | return display;
54 | }
55 |
56 | public void setDisplay(String display) {
57 | this.display = display;
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | return new ToStringBuilder(this)
63 | .append("value", value)
64 | .append("display", display)
65 | .append("$ref", $ref)
66 | .toString();
67 | }
68 |
69 | @Override
70 | public boolean equals(Object o) {
71 | if (this == o) return true;
72 |
73 | if (o == null || getClass() != o.getClass()) return false;
74 |
75 | ScimRefResource that = (ScimRefResource) o;
76 |
77 | return new EqualsBuilder()
78 | .append(value, that.value)
79 | .append(display, that.display)
80 | .append($ref, that.$ref)
81 | .isEquals();
82 | }
83 |
84 | @Override
85 | public int hashCode() {
86 | return new HashCodeBuilder(17, 37)
87 | .append(value)
88 | .append(display)
89 | .append($ref)
90 | .toHashCode();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/ScimResource.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.HashCodeBuilder;
5 | import org.apache.commons.lang3.builder.ToStringBuilder;
6 |
7 | import javax.validation.Valid;
8 | import javax.validation.constraints.NotEmpty;
9 | import javax.validation.constraints.NotNull;
10 | import javax.validation.constraints.Size;
11 | import java.io.Serializable;
12 | import java.lang.reflect.Array;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.UUID;
16 |
17 | public abstract class ScimResource implements Serializable {
18 |
19 | @NotNull
20 | @NotEmpty
21 | private List schemas = new ArrayList<>();
22 |
23 | @Valid
24 | private ScimMetaResource meta;
25 |
26 | private UUID identifier;
27 |
28 | @NotNull
29 | @NotEmpty
30 | @Size(min = 1, max = 50)
31 | private String externalId;
32 |
33 | public ScimResource() {
34 | }
35 |
36 | public ScimResource(List schemas, ScimMetaResource meta, UUID identifier, String externalId) {
37 | this.schemas = schemas;
38 | this.meta = meta;
39 | this.identifier = identifier;
40 | this.externalId = externalId;
41 | }
42 |
43 | public UUID getIdentifier() {
44 | return identifier;
45 | }
46 |
47 | public void setIdentifier(UUID identifier) {
48 | this.identifier = identifier;
49 | }
50 |
51 | public String getExternalId() {
52 | return externalId;
53 | }
54 |
55 | public void setExternalId(String externalId) {
56 | this.externalId = externalId;
57 | }
58 |
59 | public List getSchemas() {
60 | return schemas;
61 | }
62 |
63 | public void setSchemas(List schemas) {
64 | this.schemas = schemas;
65 | }
66 |
67 | public ScimMetaResource getMeta() {
68 | return meta;
69 | }
70 |
71 | public void setMeta(ScimMetaResource meta) {
72 | this.meta = meta;
73 | }
74 |
75 | @Override
76 | public String toString() {
77 | return new ToStringBuilder(this)
78 | .append("schemas", schemas)
79 | .append("meta", meta)
80 | .append("identifier", identifier)
81 | .append("externalId", externalId)
82 | .toString();
83 | }
84 |
85 | @Override
86 | public boolean equals(Object o) {
87 | if (this == o) return true;
88 |
89 | if (o == null || getClass() != o.getClass()) return false;
90 |
91 | ScimResource that = (ScimResource) o;
92 |
93 | return new EqualsBuilder()
94 | .append(schemas, that.schemas)
95 | .append(meta, that.meta)
96 | .append(identifier, that.identifier)
97 | .append(externalId, that.externalId)
98 | .isEquals();
99 | }
100 |
101 | @Override
102 | public int hashCode() {
103 | return new HashCodeBuilder(17, 37)
104 | .append(schemas)
105 | .append(meta)
106 | .append(identifier)
107 | .append(externalId)
108 | .toHashCode();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/mapper/CreateScimUserResourceMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource.mapper;
2 |
3 | import com.example.authorizationserver.scim.api.resource.*;
4 | import com.example.authorizationserver.scim.model.*;
5 | import org.springframework.stereotype.Component;
6 |
7 | import java.util.stream.Collectors;
8 |
9 | @Component
10 | public class CreateScimUserResourceMapper {
11 |
12 | public CreateScimUserResource mapEntityToResource(ScimUserEntity scimUserEntity) {
13 | return new CreateScimUserResource(
14 | new ScimMetaResource("User", null, null,
15 | "0", null),
16 | scimUserEntity.getIdentifier(), scimUserEntity.getExternalId(), scimUserEntity.getUserName(),
17 | scimUserEntity.getFamilyName(), scimUserEntity.getGivenName(), scimUserEntity.getMiddleName(),
18 | scimUserEntity.getHonorificPrefix(), scimUserEntity.getHonorificSuffix(), scimUserEntity.getNickName(),
19 | scimUserEntity.getProfileUrl(), scimUserEntity.getTitle(), scimUserEntity.getUserType(),
20 | scimUserEntity.getPreferredLanguage(), scimUserEntity.getLocale(), scimUserEntity.getTimezone(),
21 | scimUserEntity.isActive(), scimUserEntity.getPassword(),
22 | scimUserEntity.getEmails() != null ? scimUserEntity.getEmails().stream().map(e -> new ScimEmailResource(e.getEmail(), e.getType(), e.isPrimaryEmail())).collect(Collectors.toSet()) : null,
23 | scimUserEntity.getPhoneNumbers() != null ? scimUserEntity.getPhoneNumbers().stream().map(p -> new ScimPhoneNumberResource(p.getPhone(), p.getType())).collect(Collectors.toSet()) : null,
24 | scimUserEntity.getIms() != null ? scimUserEntity.getIms().stream().map(i -> new ScimImsResource(i.getIms(), i.getType())).collect(Collectors.toSet()) : null,
25 | scimUserEntity.getPhotos() != null ? scimUserEntity.getPhotos().stream().map(p -> new ScimPhotoResource(p.getPhotoUrl(), p.getType())).collect(Collectors.toSet()) : null,
26 | scimUserEntity.getAddresses() != null ? scimUserEntity.getAddresses().stream().map(a -> new ScimAddressResource(a.getStreetAddress(), a.getLocality(), a.getRegion(), a.getPostalCode(), a.getCountry(), a.getType(), a.isPrimaryAddress())).collect(Collectors.toSet()) : null,
27 | scimUserEntity.getGroups() != null ? scimUserEntity.getGroups().stream().map(g ->
28 | new ScimRefResource(g.getGroup().getIdentifier(), null, g.getGroup().getDisplayName())
29 | ).collect(Collectors.toSet()) : null,
30 | scimUserEntity.getEntitlements(), scimUserEntity.getRoles(), scimUserEntity.getX509Certificates());
31 | }
32 |
33 | public ScimUserEntity mapResourceToEntity(CreateScimUserResource createScimUserResource) {
34 |
35 | return new ScimUserEntity(createScimUserResource.getIdentifier(),
36 | createScimUserResource.getExternalId(), createScimUserResource.getUserName(), createScimUserResource.getFamilyName(),
37 | createScimUserResource.getGivenName(), createScimUserResource.getMiddleName(), createScimUserResource.getHonorificPrefix(), createScimUserResource.getHonorificSuffix(),
38 | createScimUserResource.getNickName(), createScimUserResource.getProfileUrl(), createScimUserResource.getTitle(), createScimUserResource.getUserType(),
39 | createScimUserResource.getPreferredLanguage(), createScimUserResource.getLocale(), createScimUserResource.getTimezone(), createScimUserResource.isActive(),
40 | createScimUserResource.getPassword(),
41 | createScimUserResource.getEmails() != null ? createScimUserResource.getEmails().stream().map(e -> new ScimEmailEntity(e.getValue(), e.getType(), e.isPrimary())).collect(Collectors.toSet()) : null,
42 | createScimUserResource.getPhoneNumbers() != null ? createScimUserResource.getPhoneNumbers().stream().map(p -> new ScimPhoneNumberEntity(p.getValue(), p.getType())).collect(Collectors.toSet()) : null,
43 | createScimUserResource.getIms() != null ? createScimUserResource.getIms().stream().map(p -> new ScimImsEntity(p.getValue(), p.getType())).collect(Collectors.toSet()) : null,
44 | createScimUserResource.getPhotos() != null ? createScimUserResource.getPhotos().stream().map(p -> new ScimPhotoEntity(p.getValue(), p.getType())).collect(Collectors.toSet()) : null,
45 | createScimUserResource.getAddresses() != null ? createScimUserResource.getAddresses().stream().map(a -> new ScimAddressEntity(a.getStreetAddress(), a.getLocality(), a.getRegion(), a.getPostalCode(), a.getCountry(), a.getType(), a.isPrimary())).collect(Collectors.toSet()) : null,
46 | createScimUserResource.getGroups() != null ? createScimUserResource.getGroups().stream().map(p ->
47 | new ScimUserGroupEntity(new ScimUserEntity(), new ScimGroupEntity(p.getValue(), null, p.getDisplay(), null))).collect(Collectors.toSet()) : null,
48 | createScimUserResource.getEntitlements(), createScimUserResource.getRoles(), createScimUserResource.getX509Certificates()
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/mapper/ScimGroupListResourceMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource.mapper;
2 |
3 | import com.example.authorizationserver.scim.api.resource.ScimGroupListResource;
4 | import com.example.authorizationserver.scim.api.resource.ScimMetaResource;
5 | import com.example.authorizationserver.scim.model.ScimGroupEntity;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | public class ScimGroupListResourceMapper {
10 |
11 | public ScimGroupListResource mapEntityToResource(ScimGroupEntity scimGroupEntity, String location) {
12 | return new ScimGroupListResource(new
13 | ScimMetaResource("Group", scimGroupEntity.getCreatedDate(),
14 | scimGroupEntity.getLastModifiedDate(),
15 | scimGroupEntity.getVersion().toString(), location), scimGroupEntity.getIdentifier(),
16 | scimGroupEntity.getExternalId(), scimGroupEntity.getDisplayName());
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/mapper/ScimGroupResourceMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource.mapper;
2 |
3 | import com.example.authorizationserver.scim.api.resource.ScimGroupResource;
4 | import com.example.authorizationserver.scim.api.resource.ScimMetaResource;
5 | import com.example.authorizationserver.scim.api.resource.ScimRefResource;
6 | import com.example.authorizationserver.scim.model.ScimGroupEntity;
7 | import com.example.authorizationserver.scim.model.ScimUserEntity;
8 | import com.example.authorizationserver.scim.model.ScimUserGroupEntity;
9 | import org.springframework.stereotype.Component;
10 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
11 |
12 | import java.net.URI;
13 | import java.util.stream.Collectors;
14 |
15 | import static com.example.authorizationserver.scim.api.ScimUserRestController.USER_ENDPOINT;
16 | import static java.util.Collections.emptySet;
17 |
18 | @Component
19 | public class ScimGroupResourceMapper {
20 |
21 | public ScimGroupResource mapEntityToResource(ScimGroupEntity scimGroupEntity, String location) {
22 | return new ScimGroupResource(new
23 | ScimMetaResource("Group", scimGroupEntity.getCreatedDate(),
24 | scimGroupEntity.getLastModifiedDate(),
25 | scimGroupEntity.getVersion().toString(), location), scimGroupEntity.getIdentifier(),
26 | scimGroupEntity.getExternalId(), scimGroupEntity.getDisplayName(),
27 | scimGroupEntity.getMembers() != null ?
28 | scimGroupEntity.getMembers()
29 | .stream().map(uge -> {
30 | URI userLocation =
31 | ServletUriComponentsBuilder.fromCurrentContextPath()
32 | .path(USER_ENDPOINT + "/{userId}")
33 | .buildAndExpand(uge.getUser().getIdentifier())
34 | .toUri();
35 | return new ScimRefResource(uge.getUser().getIdentifier(), userLocation, uge.getUser().getDisplayName());
36 | }).collect(Collectors.toSet()) : emptySet());
37 | }
38 |
39 | public ScimGroupEntity mapResourceToEntity(ScimGroupResource scimGroupResource) {
40 | return new ScimGroupEntity(scimGroupResource.getIdentifier(), scimGroupResource.getExternalId(),
41 | scimGroupResource.getDisplayName(),
42 | scimGroupResource.getMembers() != null ?
43 | scimGroupResource.getMembers().stream()
44 | .map(gref -> new ScimUserGroupEntity(new ScimUserEntity(gref.getValue()),
45 | new ScimGroupEntity(scimGroupResource.getIdentifier(), scimGroupResource.getExternalId(),
46 | scimGroupResource.getDisplayName(), null)))
47 | .collect(Collectors.toSet()) : emptySet());
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/mapper/ScimUserListResourceMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource.mapper;
2 |
3 | import com.example.authorizationserver.scim.api.resource.ScimMetaResource;
4 | import com.example.authorizationserver.scim.api.resource.ScimUserListResource;
5 | import com.example.authorizationserver.scim.model.ScimUserEntity;
6 | import org.springframework.stereotype.Component;
7 |
8 | @Component
9 | public class ScimUserListResourceMapper {
10 |
11 | public ScimUserListResource mapEntityToResource(ScimUserEntity scimUserEntity, String location) {
12 | return new ScimUserListResource(
13 | new ScimMetaResource("User",
14 | scimUserEntity.getCreatedDate(),
15 | scimUserEntity.getLastModifiedDate(),
16 | scimUserEntity.getVersion().toString(), location),
17 | scimUserEntity.getIdentifier(), scimUserEntity.getExternalId(), scimUserEntity.getUserName(),
18 | scimUserEntity.getFamilyName(), scimUserEntity.getGivenName(), scimUserEntity.getMiddleName(),
19 | scimUserEntity.getHonorificPrefix(), scimUserEntity.getHonorificSuffix(), scimUserEntity.getNickName(),
20 | scimUserEntity.getProfileUrl(), scimUserEntity.getTitle(), scimUserEntity.getUserType(),
21 | scimUserEntity.getPreferredLanguage(), scimUserEntity.getLocale(), scimUserEntity.getTimezone(),
22 | scimUserEntity.isActive());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/api/resource/mapper/ScimUserResourceMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.api.resource.mapper;
2 |
3 | import com.example.authorizationserver.scim.api.resource.*;
4 | import com.example.authorizationserver.scim.model.ScimUserEntity;
5 | import org.springframework.stereotype.Component;
6 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
7 |
8 | import java.net.URI;
9 | import java.util.stream.Collectors;
10 |
11 | import static com.example.authorizationserver.scim.api.ScimGroupRestController.GROUP_ENDPOINT;
12 | import static java.util.Collections.emptySet;
13 |
14 | @Component
15 | public class ScimUserResourceMapper {
16 |
17 | public ScimUserResource mapEntityToResource(ScimUserEntity scimUserEntity, String location) {
18 | return new ScimUserResource(
19 | new ScimMetaResource("User", null, null,
20 | scimUserEntity.getVersion().toString(), location),
21 | scimUserEntity.getIdentifier(), scimUserEntity.getExternalId(), scimUserEntity.getUserName(),
22 | scimUserEntity.getFamilyName(), scimUserEntity.getGivenName(), scimUserEntity.getMiddleName(),
23 | scimUserEntity.getHonorificPrefix(), scimUserEntity.getHonorificSuffix(), scimUserEntity.getNickName(),
24 | scimUserEntity.getProfileUrl(), scimUserEntity.getTitle(), scimUserEntity.getUserType(),
25 | scimUserEntity.getPreferredLanguage(), scimUserEntity.getLocale(), scimUserEntity.getTimezone(),
26 | scimUserEntity.isActive(),
27 | scimUserEntity.getEmails() != null ?
28 | scimUserEntity.getEmails().stream().map(e -> new ScimEmailResource(e.getEmail(), e.getType(), e.isPrimaryEmail())).collect(Collectors.toSet()) : emptySet(),
29 | scimUserEntity.getPhoneNumbers() != null ?
30 | scimUserEntity.getPhoneNumbers().stream().map(p -> new ScimPhoneNumberResource(p.getPhone(), p.getType())).collect(Collectors.toSet()) : emptySet(),
31 | scimUserEntity.getIms() != null ?
32 | scimUserEntity.getIms().stream().map(i -> new ScimImsResource(i.getIms(), i.getType())).collect(Collectors.toSet()): emptySet(),
33 | scimUserEntity.getPhotos() != null ?
34 | scimUserEntity.getPhotos().stream().map(p -> new ScimPhotoResource(p.getPhotoUrl(), p.getType())).collect(Collectors.toSet()) : emptySet(),
35 | scimUserEntity.getAddresses() != null ?
36 | scimUserEntity.getAddresses().stream().map(a -> new ScimAddressResource(a.getStreetAddress(), a.getLocality(), a.getRegion(), a.getPostalCode(), a.getCountry(), a.getType(), a.isPrimaryAddress())).collect(Collectors.toSet()): emptySet(),
37 | scimUserEntity.getGroups() != null ? scimUserEntity.getGroups().stream().map(g -> {
38 | URI groupLocation =
39 | ServletUriComponentsBuilder.fromCurrentContextPath()
40 | .path(GROUP_ENDPOINT + "/{groupId}")
41 | .buildAndExpand(g.getGroup().getIdentifier())
42 | .toUri();
43 | return new ScimRefResource(g.getGroup().getIdentifier(), groupLocation, g.getGroup().getDisplayName());
44 | }).collect(Collectors.toSet()) : emptySet(),
45 | scimUserEntity.getEntitlements() != null ? scimUserEntity.getEntitlements() : emptySet(),
46 | scimUserEntity.getRoles() != null ? scimUserEntity.getRoles() : emptySet(),
47 | scimUserEntity.getX509Certificates() != null ? scimUserEntity.getX509Certificates() : emptySet());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/dao/ScimGroupEntityRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.dao;
2 |
3 | import com.example.authorizationserver.scim.model.ScimGroupEntity;
4 | import org.springframework.data.jpa.repository.EntityGraph;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 |
7 | import java.util.Optional;
8 | import java.util.UUID;
9 |
10 | public interface ScimGroupEntityRepository extends JpaRepository {
11 |
12 | @EntityGraph(attributePaths = {"members", "members.user", "members.group"})
13 | Optional findOneByIdentifier(UUID identifier);
14 |
15 | void deleteOneByIdentifier(UUID identifier);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/dao/ScimUserEntityRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.dao;
2 |
3 | import com.example.authorizationserver.scim.model.ScimUserEntity;
4 | import org.springframework.data.jpa.repository.EntityGraph;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 |
7 | import java.util.Optional;
8 | import java.util.UUID;
9 |
10 | public interface ScimUserEntityRepository extends JpaRepository {
11 |
12 | @EntityGraph(attributePaths = {"emails", "phoneNumbers", "ims", "photos", "addresses", "groups", "groups.group", "roles", "entitlements", "x509Certificates"})
13 | Optional findOneByIdentifier(UUID identifier);
14 |
15 | @EntityGraph(attributePaths = {"emails", "phoneNumbers", "ims", "photos", "addresses", "groups", "groups.group", "roles", "entitlements", "x509Certificates"})
16 | Optional findOneByUserName(String username);
17 |
18 | void deleteOneByIdentifier(UUID identifier);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/dao/ScimUserGroupEntityRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.dao;
2 |
3 | import com.example.authorizationserver.scim.model.ScimUserGroupEntity;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.data.jpa.repository.Query;
6 | import org.springframework.data.repository.query.Param;
7 |
8 | import java.util.List;
9 | import java.util.UUID;
10 |
11 | public interface ScimUserGroupEntityRepository extends JpaRepository {
12 |
13 | @Query("select ge from ScimUserGroupEntity ge where ge.user.identifier = :userIdentifier and ge.group.identifier = :groupIdentifier")
14 | List findAllBy(@Param("userIdentifier") UUID userIdentifier, @Param("groupIdentifier") UUID groupIdentifier);
15 |
16 | void deleteOneByGroup_IdentifierAndUser_Identifier(UUID userIdentifier, UUID groupIdentifier);
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimAddressEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.AbstractPersistable;
5 |
6 | import javax.persistence.Entity;
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Pattern;
9 | import javax.validation.constraints.Size;
10 | import java.io.Serializable;
11 |
12 | @Entity
13 | public class ScimAddressEntity extends AbstractPersistable implements Serializable {
14 |
15 | @Size(max = 100)
16 | private String streetAddress;
17 |
18 | @Size(max = 100)
19 | private String locality;
20 |
21 | @Size(max = 100)
22 | private String region;
23 |
24 | @Size(max = 100)
25 | private String postalCode;
26 |
27 | @Size(max = 2)
28 | @Pattern(regexp = "^[A-Z]{2}$")
29 | private String country;
30 |
31 | @Size(max = 100)
32 | private String type;
33 |
34 | @NotNull
35 | private boolean primaryAddress;
36 |
37 | public ScimAddressEntity() {
38 | }
39 |
40 | public ScimAddressEntity(String streetAddress, String locality, String region, String postalCode, String country, String type, boolean primaryAddress) {
41 | this.streetAddress = streetAddress;
42 | this.locality = locality;
43 | this.region = region;
44 | this.postalCode = postalCode;
45 | this.country = country;
46 | this.type = type;
47 | this.primaryAddress = primaryAddress;
48 | }
49 |
50 | public String getStreetAddress() {
51 | return streetAddress;
52 | }
53 |
54 | public void setStreetAddress(String streetAddress) {
55 | this.streetAddress = streetAddress;
56 | }
57 |
58 | public String getLocality() {
59 | return locality;
60 | }
61 |
62 | public void setLocality(String locality) {
63 | this.locality = locality;
64 | }
65 |
66 | public String getRegion() {
67 | return region;
68 | }
69 |
70 | public void setRegion(String region) {
71 | this.region = region;
72 | }
73 |
74 | public String getPostalCode() {
75 | return postalCode;
76 | }
77 |
78 | public void setPostalCode(String postalCode) {
79 | this.postalCode = postalCode;
80 | }
81 |
82 | public String getCountry() {
83 | return country;
84 | }
85 |
86 | public void setCountry(String country) {
87 | this.country = country;
88 | }
89 |
90 | public String getType() {
91 | return type;
92 | }
93 |
94 | public void setType(String type) {
95 | this.type = type;
96 | }
97 |
98 | public boolean isPrimaryAddress() {
99 | return primaryAddress;
100 | }
101 |
102 | public void setPrimaryAddress(boolean primary) {
103 | this.primaryAddress = primary;
104 | }
105 |
106 | @Override
107 | public String toString() {
108 | return new ToStringBuilder(this)
109 | .appendSuper(super.toString())
110 | .append("streetAddress", streetAddress)
111 | .append("locality", locality)
112 | .append("region", region)
113 | .append("postalCode", postalCode)
114 | .append("country", country)
115 | .append("type", type)
116 | .append("primary", primaryAddress)
117 | .toString();
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimEmailEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.AbstractPersistable;
5 |
6 | import javax.persistence.Entity;
7 | import javax.validation.constraints.Email;
8 | import javax.validation.constraints.NotNull;
9 | import java.io.Serializable;
10 |
11 | @Entity
12 | public class ScimEmailEntity extends AbstractPersistable implements Serializable {
13 |
14 | @NotNull
15 | @Email
16 | private String email;
17 |
18 | @NotNull
19 | private String type;
20 |
21 | @NotNull
22 | private boolean primaryEmail;
23 |
24 | public ScimEmailEntity() {
25 | }
26 |
27 | public ScimEmailEntity(@NotNull @Email String email, @NotNull String type, @NotNull boolean primaryEmail) {
28 | this.email = email;
29 | this.type = type;
30 | this.primaryEmail = primaryEmail;
31 | }
32 |
33 | public String getEmail() {
34 | return email;
35 | }
36 |
37 | public void setEmail(String email) {
38 | this.email = email;
39 | }
40 |
41 | public String getType() {
42 | return type;
43 | }
44 |
45 | public void setType(String type) {
46 | this.type = type;
47 | }
48 |
49 | public boolean isPrimaryEmail() {
50 | return primaryEmail;
51 | }
52 |
53 | public void setPrimaryEmail(boolean primary) {
54 | this.primaryEmail = primary;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return new ToStringBuilder(this)
60 | .appendSuper(super.toString())
61 | .append("email", email)
62 | .append("type", type)
63 | .append("primary", primaryEmail)
64 | .toString();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimGroupEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
5 |
6 | import javax.persistence.*;
7 | import javax.validation.constraints.NotEmpty;
8 | import javax.validation.constraints.NotNull;
9 | import javax.validation.constraints.Size;
10 | import java.util.HashSet;
11 | import java.util.Set;
12 | import java.util.UUID;
13 |
14 | @Entity
15 | @EntityListeners(AuditingEntityListener.class)
16 | public class ScimGroupEntity extends ScimResourceEntity {
17 |
18 | @Column(unique = true)
19 | @NotNull
20 | @NotEmpty
21 | @Size(min = 1, max = 255)
22 | private String displayName;
23 |
24 | @OneToMany(
25 | mappedBy = "group",
26 | cascade = CascadeType.ALL,
27 | orphanRemoval = true
28 | )
29 | private Set members = new HashSet<>();
30 |
31 | public ScimGroupEntity() {
32 | }
33 |
34 | public ScimGroupEntity(UUID identifier, String externalId, String displayName, Set members) {
35 | super(identifier, externalId);
36 | this.displayName = displayName;
37 | this.members = members;
38 | }
39 |
40 | public String getDisplayName() {
41 | return displayName;
42 | }
43 |
44 | public void setDisplayName(String displayName) {
45 | this.displayName = displayName;
46 | }
47 |
48 | public Set getMembers() {
49 | return members;
50 | }
51 |
52 | public void setMembers(Set members) {
53 | this.members = members;
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return new ToStringBuilder(this)
59 | .appendSuper(super.toString())
60 | .append("displayName", displayName)
61 | .toString();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimImsEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.AbstractPersistable;
5 |
6 | import javax.persistence.Entity;
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.io.Serializable;
10 |
11 | @Entity
12 | public class ScimImsEntity extends AbstractPersistable implements Serializable {
13 |
14 | @NotNull
15 | @Size(min = 5, max = 50)
16 | private String ims;
17 |
18 | @NotNull
19 | @Size(min = 1, max = 50)
20 | private String type;
21 |
22 | public ScimImsEntity() {
23 | }
24 |
25 | public ScimImsEntity(@NotNull @Size(min = 5, max = 50) String ims, @NotNull @Size(min = 1, max = 50) String type) {
26 | this.ims = ims;
27 | this.type = type;
28 | }
29 |
30 | public String getIms() {
31 | return ims;
32 | }
33 |
34 | public void setIms(String ims) {
35 | this.ims = ims;
36 | }
37 |
38 | public String getType() {
39 | return type;
40 | }
41 |
42 | public void setType(String type) {
43 | this.type = type;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return new ToStringBuilder(this)
49 | .appendSuper(super.toString())
50 | .append("ims", ims)
51 | .append("type", type)
52 | .toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimPhoneNumberEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.AbstractPersistable;
5 |
6 | import javax.persistence.Entity;
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.io.Serializable;
10 |
11 | @Entity
12 | public class ScimPhoneNumberEntity extends AbstractPersistable implements Serializable {
13 |
14 | @NotNull
15 | @Size(min = 5, max = 50)
16 | private String phone;
17 |
18 | @NotNull
19 | @Size(min = 1, max = 50)
20 | private String type;
21 |
22 | public ScimPhoneNumberEntity() {
23 | }
24 |
25 | public ScimPhoneNumberEntity(@NotNull @Size(min = 5, max = 50) String phone, @NotNull @Size(min = 1, max = 50) String type) {
26 | this.phone = phone;
27 | this.type = type;
28 | }
29 |
30 | public String getPhone() {
31 | return phone;
32 | }
33 |
34 | public void setPhone(String phone) {
35 | this.phone = phone;
36 | }
37 |
38 | public String getType() {
39 | return type;
40 | }
41 |
42 | public void setType(String type) {
43 | this.type = type;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return new ToStringBuilder(this)
49 | .appendSuper(super.toString())
50 | .append("phone", phone)
51 | .append("type", type)
52 | .toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimPhotoEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.AbstractPersistable;
5 |
6 | import javax.persistence.Entity;
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.io.Serializable;
10 | import java.net.URI;
11 |
12 | @Entity
13 | public class ScimPhotoEntity extends AbstractPersistable implements Serializable {
14 |
15 | @NotNull
16 | private URI photoUrl;
17 |
18 | @NotNull
19 | @Size(min = 1, max = 50)
20 | private String type;
21 |
22 | public ScimPhotoEntity() {
23 | }
24 |
25 | public ScimPhotoEntity(URI photoUrl, String type) {
26 | this.photoUrl = photoUrl;
27 | this.type = type;
28 | }
29 |
30 | public URI getPhotoUrl() {
31 | return photoUrl;
32 | }
33 |
34 | public void setPhotoUrl(URI photoUrl) {
35 | this.photoUrl = photoUrl;
36 | }
37 |
38 | public String getType() {
39 | return type;
40 | }
41 |
42 | public void setType(String type) {
43 | this.type = type;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return new ToStringBuilder(this)
49 | .appendSuper(super.toString())
50 | .append("photoUrl", photoUrl)
51 | .append("type", type)
52 | .toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimResourceEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.annotation.CreatedDate;
5 | import org.springframework.data.annotation.LastModifiedDate;
6 | import org.springframework.data.jpa.domain.AbstractPersistable;
7 | import org.springframework.lang.Nullable;
8 |
9 | import javax.persistence.*;
10 | import javax.validation.constraints.NotNull;
11 | import javax.validation.constraints.Size;
12 | import java.io.Serializable;
13 | import java.time.Instant;
14 | import java.util.Date;
15 | import java.util.UUID;
16 |
17 | @MappedSuperclass
18 | public abstract class ScimResourceEntity extends AbstractPersistable implements Serializable {
19 |
20 | @Version
21 | private Long version;
22 |
23 | @NotNull
24 | private UUID identifier;
25 |
26 | @Size(max = 50)
27 | private String externalId;
28 |
29 | @CreatedDate
30 | private Instant createdDate;
31 |
32 | @LastModifiedDate
33 | private Instant lastModifiedDate;
34 |
35 | public ScimResourceEntity() {
36 | }
37 |
38 | public ScimResourceEntity(UUID identifier, String externalId) {
39 | this.identifier = identifier;
40 | this.externalId = externalId;
41 | }
42 |
43 | public Instant getCreatedDate() {
44 | return createdDate;
45 | }
46 |
47 | public Instant getLastModifiedDate() {
48 | return lastModifiedDate;
49 | }
50 |
51 | public UUID getIdentifier() {
52 | return identifier;
53 | }
54 |
55 | public void setIdentifier(UUID identifier) {
56 | this.identifier = identifier;
57 | }
58 |
59 | public String getExternalId() {
60 | return externalId;
61 | }
62 |
63 | public void setExternalId(String externalId) {
64 | this.externalId = externalId;
65 | }
66 |
67 | public Long getVersion() {
68 | return version;
69 | }
70 |
71 | @Override
72 | public String toString() {
73 | return new ToStringBuilder(this)
74 | .appendSuper(super.toString())
75 | .append("identifier", identifier)
76 | .append("externalId", externalId)
77 | .append("version", version)
78 | .toString();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/model/ScimUserGroupEntity.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.model;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.springframework.data.jpa.domain.AbstractPersistable;
5 |
6 | import javax.persistence.Entity;
7 | import javax.persistence.ManyToOne;
8 | import javax.validation.constraints.NotNull;
9 | import java.io.Serializable;
10 |
11 | @Entity
12 | public class ScimUserGroupEntity extends AbstractPersistable implements Serializable {
13 |
14 | @NotNull
15 | @ManyToOne(optional = false)
16 | private ScimUserEntity user;
17 |
18 | @NotNull
19 | @ManyToOne(optional = false)
20 | private ScimGroupEntity group;
21 |
22 | public ScimUserGroupEntity() {
23 | }
24 |
25 | public ScimUserGroupEntity(ScimUserEntity user, ScimGroupEntity group) {
26 | this.user = user;
27 | this.group = group;
28 | }
29 |
30 | public ScimUserEntity getUser() {
31 | return user;
32 | }
33 |
34 | public void setUser(ScimUserEntity user) {
35 | this.user = user;
36 | }
37 |
38 | public ScimGroupEntity getGroup() {
39 | return group;
40 | }
41 |
42 | public void setGroup(ScimGroupEntity group) {
43 | this.group = group;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return new ToStringBuilder(this)
49 | .appendSuper(super.toString())
50 | .append("user", user)
51 | .append("group", group)
52 | .toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/service/ScimGroupNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.service;
2 |
3 | import java.util.UUID;
4 |
5 | public class ScimGroupNotFoundException extends RuntimeException {
6 |
7 | public ScimGroupNotFoundException(UUID groupIdentifier) {
8 | super(String.format("No group found with identifier %s", groupIdentifier));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/service/ScimUserNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.service;
2 |
3 | import java.util.UUID;
4 |
5 | public class ScimUserNotFoundException extends RuntimeException {
6 |
7 | public ScimUserNotFoundException(UUID userIdentifier) {
8 | super(String.format("No user found with identifier %s", userIdentifier));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/scim/web/ScimWebController.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.scim.web;
2 |
3 | import com.example.authorizationserver.scim.api.resource.ScimUserListResource;
4 | import com.example.authorizationserver.scim.api.resource.mapper.ScimUserListResourceMapper;
5 | import com.example.authorizationserver.scim.service.ScimService;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Controller;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.ModelAttribute;
10 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
11 |
12 | import java.net.URI;
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | import static com.example.authorizationserver.scim.api.ScimUserRestController.USER_ENDPOINT;
17 |
18 | @Controller
19 | public class ScimWebController {
20 |
21 | private final ScimService scimService;
22 | private final ScimUserListResourceMapper scimUserListResourceMapper;
23 |
24 | @Autowired
25 | public ScimWebController(ScimService scimService, ScimUserListResourceMapper scimUserListResourceMapper) {
26 | this.scimService = scimService;
27 | this.scimUserListResourceMapper = scimUserListResourceMapper;
28 | }
29 |
30 | @ModelAttribute("allUsers")
31 | public List populateUsers() {
32 | return this.scimService.findAllUsers().stream()
33 | .map(u -> {
34 | URI location =
35 | ServletUriComponentsBuilder.fromCurrentContextPath()
36 | .path(USER_ENDPOINT + "/{userId}")
37 | .buildAndExpand(u.getIdentifier())
38 | .toUri();
39 | return scimUserListResourceMapper.mapEntityToResource(u, location.toASCIIString());
40 | }).collect(Collectors.toList());
41 | }
42 |
43 | @GetMapping("/admin/userlist")
44 | public String findAll() {
45 | return "userlist";
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/security/client/RegisteredClientAuthenticationService.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.security.client;
2 |
3 | import com.example.authorizationserver.oauth.client.model.RegisteredClient;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
7 | import org.springframework.security.core.Authentication;
8 | import org.springframework.security.core.AuthenticationException;
9 | import org.springframework.security.core.userdetails.UserDetailsService;
10 | import org.springframework.security.crypto.password.PasswordEncoder;
11 | import org.springframework.stereotype.Service;
12 |
13 | @Service
14 | public class RegisteredClientAuthenticationService {
15 |
16 | private final DaoAuthenticationProvider daoAuthenticationProvider;
17 |
18 | public RegisteredClientAuthenticationService(PasswordEncoder passwordEncoder,
19 | @Qualifier("registeredClientDetailsService") UserDetailsService userDetailsService) {
20 | this.daoAuthenticationProvider = new DaoAuthenticationProvider();
21 | this.daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
22 | this.daoAuthenticationProvider.setUserDetailsService(userDetailsService);
23 | }
24 |
25 | public RegisteredClient authenticate(String username, String password) throws AuthenticationException {
26 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
27 | Authentication authentication = this.daoAuthenticationProvider.authenticate(authenticationToken);
28 | return (RegisteredClient) authentication.getPrincipal();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/security/client/RegisteredClientDetails.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.security.client;
2 |
3 | import com.example.authorizationserver.oauth.client.model.RegisteredClient;
4 | import org.springframework.security.core.GrantedAuthority;
5 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
6 | import org.springframework.security.core.userdetails.UserDetails;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.stream.Collectors;
11 |
12 | public class RegisteredClientDetails extends RegisteredClient implements UserDetails {
13 |
14 | public RegisteredClientDetails(RegisteredClient registeredClient) {
15 | super(registeredClient.getIdentifier(),registeredClient.getClientId(),
16 | registeredClient.getClientSecret(),registeredClient.isConfidential(),
17 | registeredClient.getAccessTokenFormat(), registeredClient.getGrantTypes(),
18 | registeredClient.getRedirectUris(), registeredClient.getCorsUris());
19 | }
20 |
21 | @Override
22 | public Collection extends GrantedAuthority> getAuthorities() {
23 | Collection authorities = new ArrayList<>();
24 | authorities.add(new SimpleGrantedAuthority("ROLE_CLIENT"));
25 | authorities.addAll(getGrantTypes().stream()
26 | .map(grantType -> new SimpleGrantedAuthority("ROLE_" + grantType.getGrant().toUpperCase()))
27 | .collect(Collectors.toList()));
28 | return authorities;
29 | }
30 |
31 | @Override
32 | public String getPassword() {
33 | return getClientSecret();
34 | }
35 |
36 | @Override
37 | public String getUsername() {
38 | return getClientId();
39 | }
40 |
41 | @Override
42 | public boolean isAccountNonExpired() {
43 | return true;
44 | }
45 |
46 | @Override
47 | public boolean isAccountNonLocked() {
48 | return true;
49 | }
50 |
51 | @Override
52 | public boolean isCredentialsNonExpired() {
53 | return true;
54 | }
55 |
56 | @Override
57 | public boolean isEnabled() {
58 | return true;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/security/client/RegisteredClientDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.security.client;
2 |
3 | import com.example.authorizationserver.oauth.client.RegisteredClientService;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.security.core.userdetails.UserDetailsService;
7 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
8 | import org.springframework.stereotype.Service;
9 | import org.springframework.transaction.annotation.Transactional;
10 |
11 | @Qualifier("registeredClientDetailsService")
12 | @Service
13 | public class RegisteredClientDetailsService implements UserDetailsService {
14 |
15 | private final RegisteredClientService registeredClientService;
16 |
17 | public RegisteredClientDetailsService(RegisteredClientService registeredClientService) {
18 | this.registeredClientService = registeredClientService;
19 | }
20 |
21 | @Transactional(readOnly = true)
22 | @Override
23 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
24 | return this.registeredClientService.findOneByClientId(username).map(
25 | RegisteredClientDetails::new
26 | ).orElseThrow(() -> new UsernameNotFoundException(String.format("No client found for '%s'", username)));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/security/user/EndUserDetails.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.security.user;
2 |
3 | import com.example.authorizationserver.scim.model.ScimUserEntity;
4 | import org.springframework.security.core.GrantedAuthority;
5 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
6 | import org.springframework.security.core.userdetails.UserDetails;
7 |
8 | import java.util.Collection;
9 | import java.util.stream.Collectors;
10 |
11 | public class EndUserDetails extends ScimUserEntity implements UserDetails {
12 |
13 | public EndUserDetails(ScimUserEntity user) {
14 | super(user.getIdentifier(), null, user.getUserName(),user.getFamilyName(),user.getGivenName(),user.isActive(), user.getPassword(),
15 | user.getEmails(), user.getPhoneNumbers(), user.getIms(), user.getAddresses(), user.getGroups(), user.getEntitlements(), user.getRoles());
16 | super.setId(user.getId());
17 | }
18 |
19 | @Override
20 | public Collection extends GrantedAuthority> getAuthorities() {
21 | return getRoles().stream()
22 | .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())).collect(Collectors.toList());
23 | }
24 |
25 | @Override
26 | public String getUsername() {
27 | return getUserName();
28 | }
29 |
30 | @Override
31 | public boolean isAccountNonExpired() {
32 | return true;
33 | }
34 |
35 | @Override
36 | public boolean isAccountNonLocked() {
37 | return true;
38 | }
39 |
40 | @Override
41 | public boolean isCredentialsNonExpired() {
42 | return true;
43 | }
44 |
45 | @Override
46 | public boolean isEnabled() {
47 | return true;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/security/user/EndUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.security.user;
2 |
3 | import com.example.authorizationserver.scim.service.ScimService;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.security.core.userdetails.UserDetailsService;
7 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
8 | import org.springframework.stereotype.Service;
9 | import org.springframework.transaction.annotation.Transactional;
10 |
11 | @Qualifier("endUserDetailsService")
12 | @Service
13 | public class EndUserDetailsService implements UserDetailsService {
14 |
15 | private final ScimService scimService;
16 |
17 | public EndUserDetailsService(ScimService scimService) {
18 | this.scimService = scimService;
19 | }
20 |
21 | @Transactional(readOnly = true)
22 | @Override
23 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
24 | return this.scimService
25 | .findUserByUserName(username)
26 | .map(EndUserDetails::new)
27 | .orElseThrow(() -> new UsernameNotFoundException("No user found"));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/security/user/UserAuthenticationService.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.security.user;
2 |
3 | import com.example.authorizationserver.scim.model.ScimUserEntity;
4 | import org.springframework.beans.factory.annotation.Qualifier;
5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
7 | import org.springframework.security.core.Authentication;
8 | import org.springframework.security.core.AuthenticationException;
9 | import org.springframework.security.core.userdetails.UserDetailsService;
10 | import org.springframework.security.crypto.password.PasswordEncoder;
11 | import org.springframework.stereotype.Service;
12 |
13 | @Service
14 | public class UserAuthenticationService {
15 |
16 | private final DaoAuthenticationProvider daoAuthenticationProvider;
17 |
18 | public UserAuthenticationService(PasswordEncoder passwordEncoder,
19 | @Qualifier("endUserDetailsService") UserDetailsService userDetailsService) {
20 | this.daoAuthenticationProvider = new DaoAuthenticationProvider();
21 | this.daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
22 | this.daoAuthenticationProvider.setUserDetailsService(userDetailsService);
23 | }
24 |
25 | public ScimUserEntity authenticate(String username, String password) throws AuthenticationException {
26 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
27 | Authentication authentication = this.daoAuthenticationProvider.authenticate(authenticationToken);
28 | return (ScimUserEntity) authentication.getPrincipal();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/opaque/OpaqueTokenService.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.opaque;
2 |
3 | import org.apache.commons.lang3.RandomStringUtils;
4 | import org.springframework.stereotype.Service;
5 |
6 | @Service
7 | public class OpaqueTokenService {
8 |
9 | public String createToken() {
10 | return RandomStringUtils.random(48, true, true);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/TokenServiceException.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store;
2 |
3 | public class TokenServiceException extends RuntimeException {
4 |
5 | public TokenServiceException(String message) {
6 | super(message);
7 | }
8 |
9 | public TokenServiceException(String message, Throwable cause) {
10 | super(message, cause);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/dao/JsonWebTokenRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store.dao;
2 |
3 | import com.example.authorizationserver.token.store.model.JsonWebToken;
4 |
5 | public interface JsonWebTokenRepository extends TokenRepository {
6 |
7 | JsonWebToken findOneByValue(String value);
8 |
9 | JsonWebToken findOneByValueAndAccessToken(String value, boolean accessToken);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/dao/OpaqueTokenRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store.dao;
2 |
3 | import com.example.authorizationserver.token.store.model.OpaqueToken;
4 |
5 | public interface OpaqueTokenRepository extends TokenRepository {
6 |
7 | OpaqueToken findOneByValue(String value);
8 |
9 | OpaqueToken findOneByValueAndRefreshToken(String value, boolean refreshToken);
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/dao/TokenRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store.dao;
2 |
3 | import com.example.authorizationserver.token.store.model.Token;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface TokenRepository extends JpaRepository {}
7 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/model/JsonWebToken.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store.model;
2 |
3 | import javax.persistence.DiscriminatorValue;
4 | import javax.persistence.Entity;
5 | import javax.validation.constraints.NotNull;
6 |
7 | @Entity
8 | @DiscriminatorValue("jwt")
9 | public class JsonWebToken extends Token {
10 |
11 | @NotNull
12 | private boolean accessToken;
13 |
14 | public boolean isAccessToken() {
15 | return accessToken;
16 | }
17 |
18 | public void setAccessToken(boolean idToken) {
19 | this.accessToken = idToken;
20 | }
21 |
22 | @Override
23 | public boolean isReferenceToken() {
24 | return false;
25 | }
26 |
27 | @Override
28 | public String toString() {
29 | return "JsonWebToken{" +
30 | "idToken=" + accessToken +
31 | "} " + super.toString();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/model/OpaqueToken.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store.model;
2 |
3 | import org.springframework.security.authentication.BadCredentialsException;
4 |
5 | import javax.persistence.DiscriminatorValue;
6 | import javax.persistence.ElementCollection;
7 | import javax.persistence.Entity;
8 | import javax.persistence.FetchType;
9 | import javax.validation.constraints.NotBlank;
10 | import javax.validation.constraints.NotNull;
11 | import javax.validation.constraints.Size;
12 | import java.time.LocalDateTime;
13 | import java.util.Set;
14 |
15 | @Entity
16 | @DiscriminatorValue("opaque")
17 | public class OpaqueToken extends Token {
18 |
19 | @NotBlank
20 | @Size(max = 200)
21 | private String subject;
22 |
23 | @NotBlank
24 | @Size(max = 200)
25 | private String clientId;
26 |
27 | @NotBlank
28 | @Size(max = 200)
29 | private String issuer;
30 |
31 | @NotNull
32 | @ElementCollection(fetch = FetchType.EAGER)
33 | private Set scope;
34 |
35 | @NotNull
36 | private LocalDateTime issuedAt;
37 |
38 | @NotNull
39 | private LocalDateTime notBefore;
40 |
41 | @NotNull
42 | private boolean refreshToken;
43 |
44 | public String getSubject() {
45 | return subject;
46 | }
47 |
48 | public void setSubject(String subject) {
49 | this.subject = subject;
50 | }
51 |
52 | public String getClientId() {
53 | return clientId;
54 | }
55 |
56 | public void setClientId(String clientId) {
57 | this.clientId = clientId;
58 | }
59 |
60 | public String getIssuer() {
61 | return issuer;
62 | }
63 |
64 | public void setIssuer(String issuer) {
65 | this.issuer = issuer;
66 | }
67 |
68 | public Set getScope() {
69 | return scope;
70 | }
71 |
72 | public void setScope(Set scope) {
73 | this.scope = scope;
74 | }
75 |
76 | public LocalDateTime getIssuedAt() {
77 | return issuedAt;
78 | }
79 |
80 | public void setIssuedAt(LocalDateTime issuedAt) {
81 | this.issuedAt = issuedAt;
82 | }
83 |
84 | public LocalDateTime getNotBefore() {
85 | return notBefore;
86 | }
87 |
88 | public void setNotBefore(LocalDateTime notBefore) {
89 | this.notBefore = notBefore;
90 | }
91 |
92 | public boolean isRefreshToken() {
93 | return refreshToken;
94 | }
95 |
96 | public void setRefreshToken(boolean refreshToken) {
97 | this.refreshToken = refreshToken;
98 | }
99 |
100 | @Override
101 | public boolean isReferenceToken() {
102 | return true;
103 | }
104 |
105 | public void validate() {
106 | if (LocalDateTime.now().isAfter(this.getExpiry())) {
107 | throw new BadCredentialsException("Expired");
108 | }
109 | if (LocalDateTime.now().isBefore(this.getNotBefore())) {
110 | throw new BadCredentialsException("Not yet valid");
111 | }
112 | }
113 |
114 | @Override
115 | public String toString() {
116 | return "OpaqueToken{" +
117 | "subject='" + subject + '\'' +
118 | ", clientId='" + clientId + '\'' +
119 | ", issuer='" + issuer + '\'' +
120 | ", scope='" + getScope() + '\'' +
121 | ", issuedAt=" + issuedAt +
122 | ", notBefore=" + notBefore +
123 | ", refreshToken=" + refreshToken +
124 | "} " + super.toString();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/token/store/model/Token.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.store.model;
2 |
3 | import org.springframework.data.jpa.domain.AbstractPersistable;
4 |
5 | import javax.persistence.MappedSuperclass;
6 | import javax.validation.constraints.NotBlank;
7 | import javax.validation.constraints.NotNull;
8 | import javax.validation.constraints.Size;
9 | import java.time.LocalDateTime;
10 |
11 | @MappedSuperclass
12 | public abstract class Token extends AbstractPersistable {
13 |
14 | @NotBlank
15 | @Size(max = 2000)
16 | private String value;
17 |
18 | @NotNull private LocalDateTime expiry;
19 |
20 | private boolean revoked;
21 |
22 | public Token() {}
23 |
24 | public String getValue() {
25 | return value;
26 | }
27 |
28 | public void setValue(String value) {
29 | this.value = value;
30 | }
31 |
32 | public LocalDateTime getExpiry() {
33 | return expiry;
34 | }
35 |
36 | public void setExpiry(LocalDateTime expiry) {
37 | this.expiry = expiry;
38 | }
39 |
40 | public boolean isRevoked() {
41 | return revoked;
42 | }
43 |
44 | public void setRevoked(boolean revoked) {
45 | this.revoked = revoked;
46 | }
47 |
48 | public abstract boolean isReferenceToken();
49 |
50 | @Override
51 | public String toString() {
52 | return "Token{"
53 | + "value='"
54 | + value
55 | + '\''
56 | + ", expiry="
57 | + expiry
58 | + ", revoked="
59 | + revoked
60 | + ", referenceToken="
61 | + isReferenceToken()
62 | + '}';
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/example/authorizationserver/web/MainWebController.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.web;
2 |
3 | import org.springframework.stereotype.Controller;
4 | import org.springframework.web.bind.annotation.GetMapping;
5 |
6 | @Controller
7 | public class MainWebController {
8 |
9 | @GetMapping("/")
10 | public String index() {
11 | return "index";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | jpa:
3 | open-in-view: false
4 | jackson:
5 | date-format: com.fasterxml.jackson.databind.util.StdDateFormat
6 | default-property-inclusion: non_null
7 | server:
8 | port: 9090
9 | servlet:
10 | context-path: /auth
11 | error:
12 | include-stacktrace: never
13 |
14 | auth-server:
15 | issuer: http://localhost:${server.port}${server.servlet.context-path}
16 | access-token:
17 | default-format: jwt
18 | lifetime: 10m
19 | id-token:
20 | lifetime: 5m
21 | refresh-token:
22 | lifetime: 8h
23 | max-lifetime: 8h
24 |
25 | logging:
26 | level:
27 | com:
28 | example:
29 | authorizationserver: trace
30 | org:
31 | springframework:
32 | security: debug
--------------------------------------------------------------------------------
/src/main/resources/messages.properties:
--------------------------------------------------------------------------------
1 | client.list=Client Liste
2 | client.clientid=Client-Id
3 | client.clientsecret=Client-Secret
4 | client.confidential=Confidential
5 | client.granttypes=Unterstützte Grants
6 | client.accesstokenformat=Access-Token Format
7 | client.redirecturis=Redirect Uris
8 | client.corsuris=CORS Uris
9 | user.list=Benutzerliste
10 | user.username=Benutzername
11 | user.firstname=First name
12 | user.lastname=Last name
13 | user.email=Email
14 | admin.ui=Verwaltung
15 | admin.userlist=Benutzerliste
16 | admin.clientlist=Liste Registrierter Clients
17 | openid.config=OpenID Connect Discovery Information
18 | index.ui=Authorization Server
19 | user.groups=Gruppen
--------------------------------------------------------------------------------
/src/main/resources/messages_de.properties:
--------------------------------------------------------------------------------
1 | client.list=Client Liste
2 | client.clientid=Client-Id
3 | client.clientsecret=Client-Secret
4 | client.confidential=Confidential
5 | client.granttypes=Unterstützte Grants
6 | client.accesstokenformat=Access-Token Format
7 | client.redirecturis=Redirect Uris
8 | client.corsuris=CORS Uris
9 | user.list=Benutzerliste
10 | user.username=Benutzername
11 | user.firstname=Vorname
12 | user.lastname=Nachname
13 | user.email=Email
14 | admin.ui=Verwaltung
15 | admin.userlist=Benutzerliste
16 | admin.clientlist=Liste Registrierter Clients
17 | openid.config=OpenID Connect Discovery Information
18 | index.ui=Authorization Server
19 | user.groups=Gruppen
--------------------------------------------------------------------------------
/src/main/resources/messages_en.properties:
--------------------------------------------------------------------------------
1 | client.list=Client List
2 | client.clientid=Client-Id
3 | client.clientsecret=Client-Secret
4 | client.confidential=Confidential
5 | client.granttypes=Supported Grants
6 | client.accesstokenformat=Access-Token Format
7 | client.redirecturis=Redirect Uris
8 | client.corsuris=CORS Uris
9 | user.list=Users List
10 | user.username=Username
11 | user.firstname=First name
12 | user.lastname=Last name
13 | user.email=Email
14 | admin.ui=Administration
15 | admin.userlist=User List
16 | admin.clientlist=Registered Client List
17 | openid.config=OpenID Connect Discovery Information
18 | index.ui=Authorization Server
19 | user.groups=Groups
--------------------------------------------------------------------------------
/src/main/resources/static/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.4.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors
4 | * Copyright 2011-2019 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/src/main/resources/templates/admin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Admin
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Admin
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/resources/templates/clientlist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Userlist
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
List of Clients
15 |
16 |
17 |
18 |
19 | Client-Id |
20 | Confidential |
21 | Supported Grants |
22 | Accesstoken Format |
23 | Redirect-Uris |
24 | CORS-Uris |
25 |
26 |
27 |
28 |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/main/resources/templates/consent.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Consent
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Consent
14 |
An OAuth 2.0 client is requesting your permission
15 |
16 |
19 |
20 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/resources/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Overview
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Overview
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/resources/templates/userlist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Userlist
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
List of Users
15 |
16 |
17 |
18 |
19 | First name |
20 | First name |
21 | Last name |
22 |
23 |
24 |
25 |
26 | |
27 | |
28 | |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/test/java/com/example/authorizationserver/AuthorizationServerApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver;
2 |
3 | import com.example.authorizationserver.oauth.endpoint.AuthorizationEndpoint;
4 | import com.example.authorizationserver.oauth.endpoint.token.TokenEndpoint;
5 | import com.example.authorizationserver.oidc.endpoint.userinfo.UserInfoEndpoint;
6 | import org.junit.jupiter.api.DisplayName;
7 | import org.junit.jupiter.api.Test;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.test.annotation.DirtiesContext;
11 | import org.springframework.test.context.ActiveProfiles;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | @ActiveProfiles("integration-test")
16 | @DirtiesContext
17 | @SpringBootTest
18 | class AuthorizationServerApplicationTests {
19 |
20 | @Autowired
21 | private AuthorizationEndpoint authorizationEndpoint;
22 |
23 | @Autowired
24 | private TokenEndpoint tokenEndpoint;
25 |
26 | @Autowired
27 | private UserInfoEndpoint userInfoEndpoint;
28 |
29 | @Test
30 | @DisplayName("Verify that the authorization server application starts")
31 | void verifyApplicationStart() {
32 |
33 | assertThat(authorizationEndpoint).isNotNull();
34 | assertThat(tokenEndpoint).isNotNull();
35 | assertThat(userInfoEndpoint).isNotNull();
36 |
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/com/example/authorizationserver/annotation/WebIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.annotation;
2 |
3 | import org.springframework.boot.test.context.SpringBootTest;
4 | import org.springframework.test.annotation.DirtiesContext;
5 | import org.springframework.test.context.ActiveProfiles;
6 |
7 | import java.lang.annotation.Documented;
8 | import java.lang.annotation.ElementType;
9 | import java.lang.annotation.Inherited;
10 | import java.lang.annotation.Retention;
11 | import java.lang.annotation.RetentionPolicy;
12 | import java.lang.annotation.Target;
13 |
14 | @Target(ElementType.TYPE)
15 | @Retention(RetentionPolicy.RUNTIME)
16 | @Documented
17 | @Inherited
18 | @ActiveProfiles("integration-test")
19 | @DirtiesContext
20 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
21 | public @interface WebIntegrationTest {
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/com/example/authorizationserver/jwks/JwksEndpointIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.jwks;
2 |
3 | import com.example.authorizationserver.annotation.WebIntegrationTest;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.DisplayName;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.test.web.servlet.MockMvc;
10 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
11 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
12 | import org.springframework.web.context.WebApplicationContext;
13 |
14 | import static org.hamcrest.Matchers.equalTo;
15 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
18 |
19 | @WebIntegrationTest
20 | class JwksEndpointIntegrationTest {
21 |
22 | @Autowired private WebApplicationContext webApplicationContext;
23 |
24 | private MockMvc mockMvc;
25 |
26 | @BeforeEach
27 | void initMockMvc() {
28 | this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
29 | }
30 |
31 | @DisplayName("JWKS endpoint is accessible and returns expected result")
32 | @Test
33 | void jwksEndpoint() throws Exception {
34 | this.mockMvc
35 | .perform(
36 | MockMvcRequestBuilders.get(JwksEndpoint.ENDPOINT).accept(MediaType.APPLICATION_JSON))
37 | .andDo(print())
38 | .andExpect(status().isOk())
39 | .andExpect(jsonPath("$.keys.length()", equalTo(1)))
40 | .andExpect(jsonPath("$.keys[0].kty", equalTo("RSA")));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/com/example/authorizationserver/oidc/endpoint/DiscoveryEndpointIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oidc.endpoint;
2 |
3 | import com.example.authorizationserver.annotation.WebIntegrationTest;
4 | import com.example.authorizationserver.oauth.endpoint.AuthorizationEndpoint;
5 | import com.example.authorizationserver.oidc.endpoint.discovery.Discovery;
6 | import com.example.authorizationserver.oidc.endpoint.discovery.DiscoveryEndpoint;
7 | import io.restassured.http.ContentType;
8 | import io.restassured.module.mockmvc.RestAssuredMockMvc;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.web.context.WebApplicationContext;
13 |
14 | import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
15 | import static org.assertj.core.api.Assertions.assertThat;
16 | import static org.hamcrest.Matchers.empty;
17 | import static org.hamcrest.Matchers.not;
18 |
19 | @WebIntegrationTest
20 | class DiscoveryEndpointIntegrationTest {
21 |
22 | @Autowired private WebApplicationContext webApplicationContext;
23 |
24 | @BeforeEach
25 | void initMockMvc() {
26 | RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
27 | }
28 |
29 | @Test
30 | void discoveryEndpoint() {
31 | Discovery discovery =
32 | given()
33 | .when()
34 | .get(DiscoveryEndpoint.ENDPOINT)
35 | .then()
36 | .log()
37 | .ifValidationFails()
38 | .statusCode(200)
39 | .contentType(ContentType.JSON)
40 | .body(not(empty()))
41 | .extract()
42 | .as(Discovery.class);
43 | assertThat(discovery).isNotNull();
44 | assertThat(discovery.getAuthorization_endpoint())
45 | .isEqualTo("http://localhost:8080/auth" + AuthorizationEndpoint.ENDPOINT);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/com/example/authorizationserver/oidc/endpoint/UserInfoEndpointIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.oidc.endpoint;
2 |
3 | import com.example.authorizationserver.annotation.WebIntegrationTest;
4 | import com.example.authorizationserver.oidc.endpoint.userinfo.UserInfo;
5 | import com.example.authorizationserver.scim.model.ScimUserEntity;
6 | import com.example.authorizationserver.scim.service.ScimService;
7 | import com.example.authorizationserver.token.store.TokenService;
8 | import com.example.authorizationserver.token.store.model.JsonWebToken;
9 | import com.example.authorizationserver.token.store.model.OpaqueToken;
10 | import com.nimbusds.jose.JOSEException;
11 | import io.restassured.http.ContentType;
12 | import io.restassured.module.mockmvc.RestAssuredMockMvc;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.web.context.WebApplicationContext;
17 |
18 | import java.time.Duration;
19 | import java.util.Collections;
20 | import java.util.Optional;
21 |
22 | import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
23 | import static org.assertj.core.api.Assertions.assertThat;
24 | import static org.hamcrest.Matchers.empty;
25 | import static org.hamcrest.Matchers.not;
26 |
27 | @WebIntegrationTest
28 | class UserInfoEndpointIntegrationTest {
29 |
30 | @Autowired
31 | private TokenService tokenService;
32 | @Autowired
33 | private ScimService scimService;
34 | @Autowired
35 | private WebApplicationContext webApplicationContext;
36 | private ScimUserEntity bwayne_user;
37 |
38 | @BeforeEach
39 | void initMockMvc() {
40 | RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
41 | Optional bwayne = scimService.findUserByUserName("bwayne");
42 | bwayne.ifPresent(user -> bwayne_user = user);
43 | }
44 |
45 | @Test
46 | void userInfoWithJwtToken() throws JOSEException {
47 | JsonWebToken jsonWebToken =
48 | tokenService.createPersonalizedJwtAccessToken(
49 | bwayne_user, "confidential-demo", "nonce", Collections.singleton("OPENID"), Duration.ofMinutes(5));
50 | UserInfo userInfo =
51 | given()
52 | .header("Authorization", "Bearer " + jsonWebToken.getValue())
53 | .when()
54 | .get("/userinfo")
55 | .then()
56 | .log()
57 | .ifValidationFails()
58 | .statusCode(200)
59 | .contentType(ContentType.JSON)
60 | .body(not(empty()))
61 | .extract()
62 | .as(UserInfo.class);
63 | assertThat(userInfo).isNotNull();
64 | assertThat(userInfo.getName()).isEqualTo("bwayne");
65 | }
66 |
67 | @Test
68 | void userInfoWithOpaqueToken() {
69 |
70 | OpaqueToken opaqueToken =
71 | tokenService.createPersonalizedOpaqueAccessToken(
72 | bwayne_user, "confidential-demo", Collections.singleton("OPENID"), Duration.ofMinutes(5));
73 |
74 | UserInfo userInfo =
75 | given()
76 | .header("Authorization", "Bearer " + opaqueToken.getValue())
77 | .when()
78 | .get("/userinfo")
79 | .then()
80 | .log()
81 | .ifValidationFails()
82 | .statusCode(200)
83 | .contentType(ContentType.JSON)
84 | .body(not(empty()))
85 | .extract()
86 | .as(UserInfo.class);
87 | assertThat(userInfo).isNotNull();
88 | assertThat(userInfo.getName()).isEqualTo("bwayne");
89 | }
90 |
91 | @Test
92 | void userInfoWithInvalidToken() {
93 |
94 | UserInfo userInfo =
95 | given()
96 | .header("Authorization", "Bearer 12345")
97 | .when()
98 | .get("/userinfo")
99 | .then()
100 | .log()
101 | .ifValidationFails()
102 | .statusCode(401)
103 | .contentType(ContentType.JSON)
104 | .body(not(empty()))
105 | .extract()
106 | .as(UserInfo.class);
107 | assertThat(userInfo).isNotNull();
108 | assertThat(userInfo.getError()).isEqualTo("invalid_token");
109 | assertThat(userInfo.getError_description()).isEqualTo("Access Token is invalid");
110 | }
111 |
112 | @Test
113 | void userInfoWithMissingToken() {
114 |
115 | UserInfo userInfo =
116 | given()
117 | .when()
118 | .get("/userinfo")
119 | .then()
120 | .log()
121 | .ifValidationFails()
122 | .statusCode(401)
123 | .contentType(ContentType.JSON)
124 | .body(not(empty()))
125 | .extract()
126 | .as(UserInfo.class);
127 | assertThat(userInfo).isNotNull();
128 | assertThat(userInfo.getError()).isEqualTo("invalid_token");
129 | assertThat(userInfo.getError_description()).isEqualTo("Access Token is required");
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/test/java/com/example/authorizationserver/token/jwt/JsonWebTokenServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.example.authorizationserver.token.jwt;
2 |
3 | import com.example.authorizationserver.jwks.JwtPki;
4 | import com.example.authorizationserver.scim.model.*;
5 | import com.nimbusds.jose.JOSEException;
6 | import com.nimbusds.jwt.JWTClaimsSet;
7 | import org.junit.jupiter.api.Test;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.util.IdGenerator;
11 |
12 | import java.text.ParseException;
13 | import java.time.LocalDateTime;
14 | import java.util.Collections;
15 | import java.util.List;
16 | import java.util.Set;
17 | import java.util.UUID;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | @SpringBootTest
22 | class JsonWebTokenServiceTest {
23 |
24 | private final JsonWebTokenService cut;
25 |
26 | JsonWebTokenServiceTest(@Autowired JwtPki jwtPki, @Autowired IdGenerator idGenerator) {
27 | this.cut = new JsonWebTokenService(jwtPki, idGenerator);
28 | }
29 |
30 | @Test
31 | void createPersonalizedToken() throws JOSEException, ParseException {
32 | String personalizedToken = cut.createPersonalizedToken(true, "myclient", List.of("myaudience"),
33 | Collections.singleton("openid"), new ScimUserEntity(UUID.randomUUID(), "1234", "fname", "First", "Name", true,
34 | "secret", Set.of(new ScimEmailEntity("first.name@example.com", "work", true)),
35 | Set.of(new ScimPhoneNumberEntity("12345", "work")), Set.of(new ScimImsEntity("12345", "work")),
36 | Set.of(new ScimAddressEntity("street", "locality", "region", "12345", "country", "work", true)),
37 | Set.of(new ScimUserGroupEntity(
38 | new ScimUserEntity(UUID.randomUUID(), "username",
39 | "family", "given", true, "secret", null, null, Set.of("USER")),
40 | new ScimGroupEntity(UUID.randomUUID(), "12345", "test_group", null))),
41 | Set.of("entitlement"), Set.of("USER")), "nonce", LocalDateTime.now().plusMinutes(5));
42 | JWTClaimsSet parsedToken = cut.parseAndValidateToken(personalizedToken);
43 | assertThat(parsedToken).isNotNull();
44 | }
45 |
46 | @Test
47 | void createPersonalizedTokenWithAllScopes() throws JOSEException, ParseException {
48 | String personalizedToken = cut.createPersonalizedToken(true, "myclient", List.of("myaudience"),
49 | Set.of("openid", "profile", "email", "phone", "address"), new ScimUserEntity(UUID.randomUUID(), "1234", "fname", "First", "Name", true,
50 | "secret", Set.of(new ScimEmailEntity("first.name@example.com", "work", true)),
51 | Set.of(new ScimPhoneNumberEntity("12345", "work")), Set.of(new ScimImsEntity("12345", "work")),
52 | Set.of(new ScimAddressEntity("street", "locality", "region", "12345", "country", "work", true)),
53 | Set.of(new ScimUserGroupEntity(
54 | new ScimUserEntity(UUID.randomUUID(), "username",
55 | "family", "given", true, "secret", null, null, Set.of("USER")),
56 | new ScimGroupEntity(UUID.randomUUID(), "12345", "test_group", null))),
57 | Set.of("entitlement"), Set.of("USER")), "nonce", LocalDateTime.now().plusMinutes(5));
58 | JWTClaimsSet parsedToken = cut.parseAndValidateToken(personalizedToken);
59 | assertThat(parsedToken).isNotNull();
60 | }
61 |
62 | @Test
63 | void createAnonymousToken() throws JOSEException, ParseException {
64 | String anonymousToken = cut.createAnonymousToken(true, "myclient", List.of("myaudience"),
65 | Collections.singleton("openid"), LocalDateTime.now().plusMinutes(5));
66 | JWTClaimsSet parsedToken = cut.parseAndValidateToken(anonymousToken);
67 | assertThat(parsedToken).isNotNull();
68 | }
69 | }
--------------------------------------------------------------------------------
/src/test/resources/application-integration-test.yml:
--------------------------------------------------------------------------------
1 | server:
2 | error:
3 | include-stacktrace: always
4 |
5 | auth-server:
6 | issuer: http://localhost:8080${server.servlet.context-path}
7 |
8 | logging:
9 | level:
10 | com:
11 | example:
12 | authorizationserver: trace
13 | org:
14 | springframework:
15 | security: info
16 |
17 | spring:
18 | jpa:
19 | hibernate:
20 | ddl-auto: update
21 |
--------------------------------------------------------------------------------