blob: 48faecd7491d3e3fe3593ecf32430300eda25c20 [file] [log] [blame]
margarethaa452c5e2018-04-25 22:48:09 +02001package de.ids_mannheim.korap.oauth2.service;
margaretha0e8f4e72018-04-05 14:11:52 +02002
margaretha7ac20b12023-09-27 09:40:16 +02003import java.net.URI;
margarethaa2ce63d2018-06-28 10:11:43 +02004import java.time.ZoneId;
5import java.time.ZonedDateTime;
margaretha7ac20b12023-09-27 09:40:16 +02006import java.time.format.DateTimeFormatter;
7import java.time.temporal.ChronoUnit;
8import java.util.ArrayList;
margaretha6374f722018-04-17 18:45:57 +02009import java.util.HashMap;
margaretha7ac20b12023-09-27 09:40:16 +020010import java.util.HashSet;
11import java.util.List;
margaretha6374f722018-04-17 18:45:57 +020012import java.util.Map;
margarethaf839dde2018-04-16 17:52:57 +020013import java.util.Set;
14
margaretha0e8f4e72018-04-05 14:11:52 +020015import org.springframework.beans.factory.annotation.Autowired;
16import org.springframework.stereotype.Service;
17
margarethab8a9d4e2023-10-25 12:00:10 +020018import com.nimbusds.oauth2.sdk.AccessTokenResponse;
margaretha7ac20b12023-09-27 09:40:16 +020019import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
20import com.nimbusds.oauth2.sdk.AuthorizationGrant;
21import com.nimbusds.oauth2.sdk.GrantType;
22import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
23import com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant;
24import com.nimbusds.oauth2.sdk.Scope;
25import com.nimbusds.oauth2.sdk.TokenRequest;
margarethab8a9d4e2023-10-25 12:00:10 +020026import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
27import com.nimbusds.oauth2.sdk.token.Tokens;
margaretha7ac20b12023-09-27 09:40:16 +020028import com.unboundid.ldap.sdk.LDAPException;
29
margaretha34954472018-10-24 20:05:17 +020030import de.ids_mannheim.korap.authentication.AuthenticationManager;
margaretha7ac20b12023-09-27 09:40:16 +020031import de.ids_mannheim.korap.authentication.LdapAuth3;
margaretha6374f722018-04-17 18:45:57 +020032import de.ids_mannheim.korap.config.Attributes;
margarethaa0486272018-04-12 19:59:31 +020033import de.ids_mannheim.korap.config.FullConfiguration;
margaretha7ac20b12023-09-27 09:40:16 +020034import de.ids_mannheim.korap.constant.AuthenticationMethod;
35import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
margaretha0e8f4e72018-04-05 14:11:52 +020036import de.ids_mannheim.korap.exceptions.KustvaktException;
margaretha05122312018-04-16 15:01:34 +020037import de.ids_mannheim.korap.exceptions.StatusCodes;
margarethaa452c5e2018-04-25 22:48:09 +020038import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
margaretha7ac20b12023-09-27 09:40:16 +020039import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
40import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
41import de.ids_mannheim.korap.oauth2.dto.OAuth2TokenDto;
42import de.ids_mannheim.korap.oauth2.entity.AccessScope;
43import de.ids_mannheim.korap.oauth2.entity.AccessToken;
margarethaa452c5e2018-04-25 22:48:09 +020044import de.ids_mannheim.korap.oauth2.entity.Authorization;
margaretha7ac20b12023-09-27 09:40:16 +020045import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
46import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
margaretha7ac20b12023-09-27 09:40:16 +020047import jakarta.persistence.NoResultException;
margaretha0e8f4e72018-04-05 14:11:52 +020048
margaretha07402f42018-05-07 19:07:45 +020049/**
50 * OAuth2TokenService manages business logic related to OAuth2
51 * requesting and creating access token.
52 *
53 * @author margaretha
54 *
55 */
margaretha0e8f4e72018-04-05 14:11:52 +020056@Service
margarethab4ce6602018-04-26 20:23:57 +020057public class OAuth2TokenService {
margaretha0e8f4e72018-04-05 14:11:52 +020058
59 @Autowired
margaretha20f31232018-07-09 17:49:39 +020060 protected OAuth2ClientService clientService;
margarethab36b1a32018-06-20 20:13:07 +020061
margarethaa0486272018-04-12 19:59:31 +020062 @Autowired
margarethaa452c5e2018-04-25 22:48:09 +020063 private OAuth2AuthorizationService authorizationService;
margarethaa452c5e2018-04-25 22:48:09 +020064
65 @Autowired
margaretha2df06602018-11-14 19:10:30 +010066 protected OAuth2ScopeServiceImpl scopeService;
margarethab36b1a32018-06-20 20:13:07 +020067
68 @Autowired
69 protected FullConfiguration config;
margaretha6374f722018-04-17 18:45:57 +020070 @Autowired
margaretha34954472018-10-24 20:05:17 +020071 private AuthenticationManager authenticationManager;
margaretha35e1ca22023-11-16 22:00:01 +010072
margaretha7ac20b12023-09-27 09:40:16 +020073 @Autowired
74 private RandomCodeGenerator randomGenerator;
75
76 @Autowired
77 private AccessTokenDao tokenDao;
78 @Autowired
79 private RefreshTokenDao refreshDao;
margarethaa0486272018-04-12 19:59:31 +020080
margarethafb027f92018-04-23 20:00:13 +020081 /**
margarethaa452c5e2018-04-25 22:48:09 +020082 * RFC 6749:
83 * If the client type is confidential or the client was issued
84 * client credentials, the client MUST authenticate with the
85 * authorization server.
margarethaa0486272018-04-12 19:59:31 +020086 *
margarethaa0486272018-04-12 19:59:31 +020087 * @param authorizationCode
margarethaa452c5e2018-04-25 22:48:09 +020088 * @param redirectURI
89 * required if included in the authorization request
90 * @param clientId
91 * required if there is no authorization header
92 * @param clientSecret
margaretha249a0aa2018-06-28 22:25:14 +020093 * client_secret, required if client_secret was issued
margarethaa452c5e2018-04-25 22:48:09 +020094 * for the client in client registration.
margarethab36b1a32018-06-20 20:13:07 +020095 * @return an authorization
margarethaa0486272018-04-12 19:59:31 +020096 * @throws KustvaktException
margarethaa0486272018-04-12 19:59:31 +020097 */
margaretha35e1ca22023-11-16 22:00:01 +010098 protected Authorization retrieveAuthorization (String authorizationCode,
99 String redirectURI, String clientId, String clientSecret)
100 throws KustvaktException {
margarethafb027f92018-04-23 20:00:13 +0200101
margaretha35e1ca22023-11-16 22:00:01 +0100102 Authorization authorization = authorizationService
103 .retrieveAuthorization(authorizationCode);
margarethabe4c5c92018-05-03 18:55:49 +0200104 try {
105 clientService.authenticateClient(clientId, clientSecret);
106 authorization = authorizationService
107 .verifyAuthorization(authorization, clientId, redirectURI);
108 }
109 catch (KustvaktException e) {
110 authorizationService.addTotalAttempts(authorization);
111 throw e;
112 }
margarethab36b1a32018-06-20 20:13:07 +0200113 return authorization;
margarethaa0486272018-04-12 19:59:31 +0200114 }
115
margarethaa2ce63d2018-06-28 10:11:43 +0200116 public ZonedDateTime authenticateUser (String username, String password,
margaretha6374f722018-04-17 18:45:57 +0200117 Set<String> scopes) throws KustvaktException {
118 if (username == null || username.isEmpty()) {
119 throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
margarethaa452c5e2018-04-25 22:48:09 +0200120 "username is missing.", OAuth2Error.INVALID_REQUEST);
margaretha6374f722018-04-17 18:45:57 +0200121 }
122 if (password == null || password.isEmpty()) {
123 throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
margarethaa452c5e2018-04-25 22:48:09 +0200124 "password is missing", OAuth2Error.INVALID_REQUEST);
margaretha6374f722018-04-17 18:45:57 +0200125 }
126
127 Map<String, Object> attributes = new HashMap<>();
128 if (scopes != null && !scopes.isEmpty()) {
margaretha20f31232018-07-09 17:49:39 +0200129 attributes.put(Attributes.SCOPE, scopes);
margaretha6374f722018-04-17 18:45:57 +0200130 }
131 authenticationManager.authenticate(
132 config.getOAuth2passwordAuthentication(), username, password,
133 attributes);
margarethaa2ce63d2018-06-28 10:11:43 +0200134
margaretha35e1ca22023-11-16 22:00:01 +0100135 ZonedDateTime authenticationTime = ZonedDateTime
136 .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
margarethaa2ce63d2018-06-28 10:11:43 +0200137 return authenticationTime;
margarethaa0486272018-04-12 19:59:31 +0200138 }
139
margaretha35e1ca22023-11-16 22:00:01 +0100140 public AccessTokenResponse requestAccessToken (TokenRequest tokenRequest,
141 String clientId, String clientSecret) throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200142
143 AuthorizationGrant authGrant = tokenRequest.getAuthorizationGrant();
144 GrantType grantType = authGrant.getType();
145 Scope scope = tokenRequest.getScope();
146 Set<String> scopeSet = new HashSet<>();
margaretha35e1ca22023-11-16 22:00:01 +0100147 if (scope != null)
margaretha7ac20b12023-09-27 09:40:16 +0200148 scopeSet.addAll(scope.toStringList());
margaretha35e1ca22023-11-16 22:00:01 +0100149
margaretha7ac20b12023-09-27 09:40:16 +0200150 if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
151 AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) authGrant;
152 String authCode = codeGrant.getAuthorizationCode().getValue();
153 URI uri = codeGrant.getRedirectionURI();
154 String redirectionURI = (uri != null) ? uri.toString() : null;
margaretha35e1ca22023-11-16 22:00:01 +0100155
margaretha7ac20b12023-09-27 09:40:16 +0200156 return requestAccessTokenWithAuthorizationCode(authCode,
157 redirectionURI, clientId, clientSecret);
158 }
159 else if (grantType.equals(GrantType.PASSWORD)) {
margaretha35e1ca22023-11-16 22:00:01 +0100160 ResourceOwnerPasswordCredentialsGrant passwordGrant = (ResourceOwnerPasswordCredentialsGrant) authGrant;
margaretha7ac20b12023-09-27 09:40:16 +0200161 String username = passwordGrant.getUsername();
162 String password = passwordGrant.getPassword().getValue();
163 return requestAccessTokenWithPassword(clientId, clientSecret,
164 username, password, scopeSet);
165 }
166 else if (grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
167 return requestAccessTokenWithClientCredentials(clientId,
168 clientSecret, scopeSet);
169 }
170 else if (grantType.equals(GrantType.REFRESH_TOKEN)) {
171 RefreshTokenGrant refreshGrant = (RefreshTokenGrant) authGrant;
172 String refreshToken = refreshGrant.getRefreshToken().getValue();
173 return requestAccessTokenWithRefreshToken(refreshToken, scopeSet,
174 clientId, clientSecret);
175 }
176 else {
177 throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
178 grantType + " is not supported.",
179 OAuth2Error.UNSUPPORTED_GRANT_TYPE);
180 }
181
182 }
margaretha7ac20b12023-09-27 09:40:16 +0200183
184 /**
185 * Revokes all access token associated with the given refresh
186 * token, and creates a new access token and a new refresh
187 * token with the same scopes. Thus, at one point of time,
188 * there is only one active access token associated with
189 * a refresh token.
190 *
191 * Client authentication is done using the given client
192 * credentials.
193 *
194 * TODO: should create a new refresh token when the old refresh
195 * token is used (DONE)
196 *
197 * @param refreshTokenStr
198 * @param requestScopes
199 * @param clientId
200 * @param clientSecret
201 * @return if successful, a new access token
202 * @throws KustvaktException
margaretha7ac20b12023-09-27 09:40:16 +0200203 */
margarethab8a9d4e2023-10-25 12:00:10 +0200204 private AccessTokenResponse requestAccessTokenWithRefreshToken (
margaretha7ac20b12023-09-27 09:40:16 +0200205 String refreshTokenStr, Set<String> requestScopes, String clientId,
margaretha35e1ca22023-11-16 22:00:01 +0100206 String clientSecret) throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200207
208 if (refreshTokenStr == null || refreshTokenStr.isEmpty()) {
209 throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
210 "Missing parameter: refresh_token",
211 OAuth2Error.INVALID_REQUEST);
212 }
213
margaretha35e1ca22023-11-16 22:00:01 +0100214 OAuth2Client oAuth2Client = clientService.authenticateClient(clientId,
215 clientSecret);
margaretha7ac20b12023-09-27 09:40:16 +0200216
217 RefreshToken refreshToken;
218 try {
219 refreshToken = refreshDao.retrieveRefreshToken(refreshTokenStr);
220 }
221 catch (NoResultException e) {
222 throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
223 "Refresh token is not found", OAuth2Error.INVALID_GRANT);
224 }
225
226 if (!clientId.equals(refreshToken.getClient().getId())) {
227 throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
228 "Client " + clientId + " is not authorized",
229 OAuth2Error.INVALID_CLIENT);
230 }
231 else if (refreshToken.isRevoked()) {
232 throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
233 "Refresh token has been revoked",
234 OAuth2Error.INVALID_GRANT);
235 }
236 else if (ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))
237 .isAfter(refreshToken.getExpiryDate())) {
238 throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
239 "Refresh token is expired", OAuth2Error.INVALID_GRANT);
240 }
241
margaretha35e1ca22023-11-16 22:00:01 +0100242 Set<AccessScope> tokenScopes = new HashSet<>(refreshToken.getScopes());
margaretha7ac20b12023-09-27 09:40:16 +0200243 if (requestScopes != null && !requestScopes.isEmpty()) {
margaretha35e1ca22023-11-16 22:00:01 +0100244 tokenScopes = scopeService.verifyRefreshScope(requestScopes,
245 tokenScopes);
margaretha7ac20b12023-09-27 09:40:16 +0200246 requestScopes = scopeService
247 .convertAccessScopesToStringSet(tokenScopes);
248 }
249
250 // revoke the refresh token and all access tokens associated to it
251 revokeRefreshToken(refreshTokenStr);
252
253 return createsAccessTokenResponse(requestScopes, tokenScopes, clientId,
254 refreshToken.getUserId(),
255 refreshToken.getUserAuthenticationTime(), oAuth2Client);
256
257 // without new refresh token
258 // return createsAccessTokenResponse(scopes, requestedScopes,
259 // clientId,
260 // refreshToken.getUserId(),
261 // refreshToken.getUserAuthenticationTime(), refreshToken);
262 }
263
264 /**
265 * Issues an access token for the specified client if the
266 * authorization code is valid and client successfully
267 * authenticates.
268 *
269 * @param code
270 * authorization code, required
271 * @param redirectionURI
272 * client redirect uri, required if specified in the
273 * authorization request
274 * @param clientId
275 * client id, required
276 * @param clientSecret
277 * client secret, required
margarethab8a9d4e2023-10-25 12:00:10 +0200278 * @return an {@link AccessTokenResponse}
margaretha7ac20b12023-09-27 09:40:16 +0200279 * @throws KustvaktException
280 */
margaretha35e1ca22023-11-16 22:00:01 +0100281 private AccessTokenResponse requestAccessTokenWithAuthorizationCode (
282 String code, String redirectionURI, String clientId,
283 String clientSecret) throws KustvaktException {
284 Authorization authorization = retrieveAuthorization(code,
285 redirectionURI, clientId, clientSecret);
margaretha7ac20b12023-09-27 09:40:16 +0200286
287 Set<String> scopes = scopeService
288 .convertAccessScopesToStringSet(authorization.getScopes());
289 OAuth2Client oAuth2Client = clientService.retrieveClient(clientId);
290 return createsAccessTokenResponse(scopes, authorization.getScopes(),
291 authorization.getClientId(), authorization.getUserId(),
292 authorization.getUserAuthenticationTime(), oAuth2Client);
293 }
294
295 /**
296 * Third party apps must not be allowed to use password grant.
297 * MH: password grant is only allowed for trusted clients (korap
298 * frontend)
299 *
300 * According to RFC 6749, client authentication is only required
301 * for confidential clients and whenever client credentials are
302 * provided. Moreover, client_id is optional for password grant,
303 * but without it, the authentication server cannot check the
304 * client type. To make sure that confidential clients
305 * authenticate, client_id is made required (similar to
306 * authorization code grant).
307 *
308 * TODO: FORCE client secret
309 *
310 * @param clientId
311 * client_id, required
312 * @param clientSecret
313 * client_secret, required if client_secret was issued
314 * for the client in client registration.
315 * @param username
316 * username, required
317 * @param password
318 * password, required
319 * @param scopes
320 * authorization scopes, optional
margarethab8a9d4e2023-10-25 12:00:10 +0200321 * @return an {@link AccessTokenResponse}
margaretha7ac20b12023-09-27 09:40:16 +0200322 * @throws KustvaktException
margaretha7ac20b12023-09-27 09:40:16 +0200323 */
margarethab8a9d4e2023-10-25 12:00:10 +0200324 private AccessTokenResponse requestAccessTokenWithPassword (String clientId,
margaretha7ac20b12023-09-27 09:40:16 +0200325 String clientSecret, String username, String password,
margarethab8a9d4e2023-10-25 12:00:10 +0200326 Set<String> scopes) throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200327
margaretha35e1ca22023-11-16 22:00:01 +0100328 OAuth2Client client = clientService.authenticateClient(clientId,
329 clientSecret);
margaretha7ac20b12023-09-27 09:40:16 +0200330 if (!client.isSuper()) {
331 throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
332 "Password grant is not allowed for third party clients",
333 OAuth2Error.UNAUTHORIZED_CLIENT);
334 }
335
336 if (scopes == null || scopes.isEmpty()) {
337 scopes = new HashSet<String>(1);
338 scopes.add("all");
339 // scopes = config.getDefaultAccessScopes();
340 }
341
margaretha35e1ca22023-11-16 22:00:01 +0100342 ZonedDateTime authenticationTime = authenticateUser(username, password,
343 scopes);
margaretha7ac20b12023-09-27 09:40:16 +0200344
margaretha35e1ca22023-11-16 22:00:01 +0100345 Set<AccessScope> accessScopes = scopeService
346 .convertToAccessScope(scopes);
347
margaretha7ac20b12023-09-27 09:40:16 +0200348 if (config.getOAuth2passwordAuthentication()
349 .equals(AuthenticationMethod.LDAP)) {
350 try {
351 //username = LdapAuth3.getEmail(username, config.getLdapConfig());
margaretha35e1ca22023-11-16 22:00:01 +0100352 username = LdapAuth3.getUsername(username,
353 config.getLdapConfig());
margaretha7ac20b12023-09-27 09:40:16 +0200354 }
355 catch (LDAPException e) {
356 throw new KustvaktException(StatusCodes.LDAP_BASE_ERRCODE,
357 e.getExceptionMessage());
358 }
359 }
margaretha35e1ca22023-11-16 22:00:01 +0100360
margaretha7ac20b12023-09-27 09:40:16 +0200361 return createsAccessTokenResponse(scopes, accessScopes, clientId,
362 username, authenticationTime, client);
363 }
364
365 /**
366 * Clients must authenticate.
367 * Client credentials grant is limited to native clients.
368 *
369 * @param clientId
370 * client_id parameter, required
371 * @param clientSecret
372 * client_secret parameter, required
373 * @param scopes
374 * authorization scopes, optional
margarethab8a9d4e2023-10-25 12:00:10 +0200375 * @return an {@link AccessTokenResponse}
margaretha7ac20b12023-09-27 09:40:16 +0200376 * @throws KustvaktException
margaretha7ac20b12023-09-27 09:40:16 +0200377 */
margarethab8a9d4e2023-10-25 12:00:10 +0200378 protected AccessTokenResponse requestAccessTokenWithClientCredentials (
margaretha7ac20b12023-09-27 09:40:16 +0200379 String clientId, String clientSecret, Set<String> scopes)
margarethab8a9d4e2023-10-25 12:00:10 +0200380 throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200381
382 if (clientSecret == null || clientSecret.isEmpty()) {
383 throw new KustvaktException(
384 StatusCodes.CLIENT_AUTHENTICATION_FAILED,
385 "Missing parameter: client_secret",
386 OAuth2Error.INVALID_REQUEST);
387 }
388
389 // OAuth2Client client =
margaretha35e1ca22023-11-16 22:00:01 +0100390 OAuth2Client oAuth2Client = clientService.authenticateClient(clientId,
391 clientSecret);
margaretha7ac20b12023-09-27 09:40:16 +0200392
393 // if (!client.isNative()) {
394 // throw new KustvaktException(
395 // StatusCodes.CLIENT_AUTHENTICATION_FAILED,
396 // "Client credentials grant is not allowed for third party
397 // clients",
398 // OAuth2Error.UNAUTHORIZED_CLIENT);
399 // }
400
margaretha35e1ca22023-11-16 22:00:01 +0100401 ZonedDateTime authenticationTime = ZonedDateTime
402 .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
margaretha7ac20b12023-09-27 09:40:16 +0200403
404 scopes = scopeService.filterScopes(scopes,
405 config.getClientCredentialsScopes());
margaretha35e1ca22023-11-16 22:00:01 +0100406 Set<AccessScope> accessScopes = scopeService
407 .convertToAccessScope(scopes);
margaretha7ac20b12023-09-27 09:40:16 +0200408 return createsAccessTokenResponse(scopes, accessScopes, clientId, null,
margaretha35e1ca22023-11-16 22:00:01 +0100409 authenticationTime, oAuth2Client);
margaretha7ac20b12023-09-27 09:40:16 +0200410 }
411
412 /**
margarethab8a9d4e2023-10-25 12:00:10 +0200413 * Creates an OAuth response containing an access token of type
margaretha7ac20b12023-09-27 09:40:16 +0200414 * Bearer. By default, MD generator is used to generates access
415 * token of 128 bit values, represented in hexadecimal comprising
416 * 32 bytes. The generated value is subsequently encoded in
417 * Base64.
418 *
419 * <br /><br />
margaretha35e1ca22023-11-16 22:00:01 +0100420 * Additionally, a refresh token is issued for confidential
421 * clients.
422 * It can be used to request a new access token without requiring
423 * user
margaretha7ac20b12023-09-27 09:40:16 +0200424 * re-authentication.
425 *
426 * @param scopes
427 * a set of access token scopes in String
428 * @param accessScopes
429 * a set of access token scopes in {@link AccessScope}
430 * @param clientId
431 * a client id
432 * @param userId
433 * a user id
434 * @param authenticationTime
435 * the user authentication time
margaretha93bfbea2023-11-06 21:09:21 +0100436 * @param client
437 * an OAuth2Client
margarethab8a9d4e2023-10-25 12:00:10 +0200438 * @return an {@link AccessTokenResponse}
margaretha7ac20b12023-09-27 09:40:16 +0200439 * @throws KustvaktException
440 */
margarethab8a9d4e2023-10-25 12:00:10 +0200441 private AccessTokenResponse createsAccessTokenResponse (Set<String> scopes,
margaretha7ac20b12023-09-27 09:40:16 +0200442 Set<AccessScope> accessScopes, String clientId, String userId,
443 ZonedDateTime authenticationTime, OAuth2Client client)
margarethab8a9d4e2023-10-25 12:00:10 +0200444 throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200445
446 String random = randomGenerator.createRandomCode();
447 random += randomGenerator.createRandomCode();
margaretha35e1ca22023-11-16 22:00:01 +0100448
margarethab8a9d4e2023-10-25 12:00:10 +0200449 if (clientService.isPublicClient(client)) {
margaretha7ac20b12023-09-27 09:40:16 +0200450 // refresh token == null, getAccessTokenLongExpiry
margarethab8a9d4e2023-10-25 12:00:10 +0200451 return createsAccessTokenResponse(null, scopes, accessScopes,
452 clientId, userId, authenticationTime);
453 }
margaretha7ac20b12023-09-27 09:40:16 +0200454 else {
455 // refresh token != null, getAccessTokenExpiry
456 // default refresh token expiry: 365 days in seconds
457 RefreshToken refreshToken = refreshDao.storeRefreshToken(random,
458 userId, authenticationTime, client, accessScopes);
margarethab8a9d4e2023-10-25 12:00:10 +0200459 return createsAccessTokenResponse(refreshToken, scopes,
460 accessScopes, clientId, userId, authenticationTime);
margaretha7ac20b12023-09-27 09:40:16 +0200461 }
462 }
463
margarethab8a9d4e2023-10-25 12:00:10 +0200464 private AccessTokenResponse createsAccessTokenResponse (
465 RefreshToken refreshToken, Set<String> scopes,
margaretha7ac20b12023-09-27 09:40:16 +0200466 Set<AccessScope> accessScopes, String clientId, String userId,
margarethab8a9d4e2023-10-25 12:00:10 +0200467 ZonedDateTime authenticationTime) throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200468
469 String accessToken = randomGenerator.createRandomCode();
margaretha35e1ca22023-11-16 22:00:01 +0100470 accessToken += randomGenerator.createRandomCode();
margaretha7ac20b12023-09-27 09:40:16 +0200471 tokenDao.storeAccessToken(accessToken, refreshToken, accessScopes,
472 userId, clientId, authenticationTime);
473
margarethab8a9d4e2023-10-25 12:00:10 +0200474 Tokens tokens = null;
margaretha35e1ca22023-11-16 22:00:01 +0100475 if (refreshToken != null) {
margarethab8a9d4e2023-10-25 12:00:10 +0200476 BearerAccessToken bearerToken = new BearerAccessToken(accessToken,
477 (long) config.getAccessTokenExpiry(), Scope.parse(scopes));
margaretha35e1ca22023-11-16 22:00:01 +0100478 com.nimbusds.oauth2.sdk.token.RefreshToken rf = new com.nimbusds.oauth2.sdk.token.RefreshToken(
479 refreshToken.getToken());
margarethab8a9d4e2023-10-25 12:00:10 +0200480 tokens = new Tokens(bearerToken, rf);
481 }
482 else {
margaretha35e1ca22023-11-16 22:00:01 +0100483 BearerAccessToken bearerToken = new BearerAccessToken(accessToken,
484 (long) config.getAccessTokenLongExpiry(),
485 Scope.parse(scopes));
486 tokens = new Tokens(bearerToken, null);
margarethab8a9d4e2023-10-25 12:00:10 +0200487 }
488 return new AccessTokenResponse(tokens);
margaretha35e1ca22023-11-16 22:00:01 +0100489 }
margaretha7ac20b12023-09-27 09:40:16 +0200490
margaretha35e1ca22023-11-16 22:00:01 +0100491 public void revokeToken (String clientId, String clientSecret, String token,
492 String tokenType) throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200493 clientService.authenticateClient(clientId, clientSecret);
494 if (tokenType != null && tokenType.equals("refresh_token")) {
495 if (!revokeRefreshToken(token)) {
496 revokeAccessToken(token);
497 }
498 return;
499 }
500
501 if (!revokeAccessToken(token)) {
502 revokeRefreshToken(token);
503 }
504 }
505
506 private boolean revokeAccessToken (String token) throws KustvaktException {
507 try {
508 AccessToken accessToken = tokenDao.retrieveAccessToken(token);
509 revokeAccessToken(accessToken);
510 return true;
511 }
512 catch (KustvaktException e) {
513 if (!e.getStatusCode().equals(StatusCodes.INVALID_ACCESS_TOKEN)) {
514 return false;
515 }
516 throw e;
517 }
518 }
margaretha35e1ca22023-11-16 22:00:01 +0100519
margaretha7ac20b12023-09-27 09:40:16 +0200520 private void revokeAccessToken (AccessToken accessToken)
521 throws KustvaktException {
margaretha35e1ca22023-11-16 22:00:01 +0100522 if (accessToken != null) {
margaretha7ac20b12023-09-27 09:40:16 +0200523 accessToken.setRevoked(true);
524 tokenDao.updateAccessToken(accessToken);
525 }
526 }
527
528 private boolean revokeRefreshToken (String token) throws KustvaktException {
529 RefreshToken refreshToken = null;
530 try {
531 refreshToken = refreshDao.retrieveRefreshToken(token);
532 }
533 catch (NoResultException e) {
534 return false;
535 }
536
537 return revokeRefreshToken(refreshToken);
538 }
539
540 public boolean revokeRefreshToken (RefreshToken refreshToken)
541 throws KustvaktException {
margaretha35e1ca22023-11-16 22:00:01 +0100542 if (refreshToken != null) {
margaretha7ac20b12023-09-27 09:40:16 +0200543 refreshToken.setRevoked(true);
544 refreshDao.updateRefreshToken(refreshToken);
margaretha35e1ca22023-11-16 22:00:01 +0100545
margaretha7ac20b12023-09-27 09:40:16 +0200546 Set<AccessToken> accessTokenList = refreshToken.getAccessTokens();
547 for (AccessToken accessToken : accessTokenList) {
548 accessToken.setRevoked(true);
549 tokenDao.updateAccessToken(accessToken);
550 }
551 return true;
552 }
553 return false;
554 }
555
556 public void revokeAllClientTokensViaSuperClient (String username,
margaretha4993eb72023-09-27 10:54:34 +0200557 String superClientId, String superClientSecret, String clientId)
margaretha7ac20b12023-09-27 09:40:16 +0200558 throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200559 OAuth2Client superClient = clientService
560 .authenticateClient(superClientId, superClientSecret);
561 if (!superClient.isSuper()) {
562 throw new KustvaktException(
563 StatusCodes.CLIENT_AUTHENTICATION_FAILED);
564 }
565
margaretha7ac20b12023-09-27 09:40:16 +0200566 revokeAllClientTokensForUser(clientId, username);
567 }
margaretha35e1ca22023-11-16 22:00:01 +0100568
margaretha7ac20b12023-09-27 09:40:16 +0200569 public void revokeAllClientTokensForUser (String clientId, String username)
570 throws KustvaktException {
571 OAuth2Client client = clientService.retrieveClient(clientId);
572 if (clientService.isPublicClient(client)) {
margaretha35e1ca22023-11-16 22:00:01 +0100573 List<AccessToken> accessTokens = tokenDao
574 .retrieveAccessTokenByClientId(clientId, username);
margaretha7ac20b12023-09-27 09:40:16 +0200575 for (AccessToken t : accessTokens) {
576 revokeAccessToken(t);
577 }
578 }
579 else {
580 List<RefreshToken> refreshTokens = refreshDao
581 .retrieveRefreshTokenByClientId(clientId, username);
582 for (RefreshToken r : refreshTokens) {
583 revokeRefreshToken(r);
584 }
585 }
586 }
margaretha35e1ca22023-11-16 22:00:01 +0100587
margaretha7ac20b12023-09-27 09:40:16 +0200588 public void revokeTokensViaSuperClient (String username,
margaretha4993eb72023-09-27 10:54:34 +0200589 String superClientId, String superClientSecret, String token)
590 throws KustvaktException {
margaretha7ac20b12023-09-27 09:40:16 +0200591 OAuth2Client superClient = clientService
592 .authenticateClient(superClientId, superClientSecret);
593 if (!superClient.isSuper()) {
594 throw new KustvaktException(
595 StatusCodes.CLIENT_AUTHENTICATION_FAILED);
596 }
margaretha4993eb72023-09-27 10:54:34 +0200597
margaretha35e1ca22023-11-16 22:00:01 +0100598 RefreshToken refreshToken = refreshDao.retrieveRefreshToken(token,
599 username);
margaretha4993eb72023-09-27 10:54:34 +0200600 if (!revokeRefreshToken(refreshToken)) {
margaretha35e1ca22023-11-16 22:00:01 +0100601 AccessToken accessToken = tokenDao.retrieveAccessToken(token,
602 username);
margaretha7ac20b12023-09-27 09:40:16 +0200603 revokeAccessToken(accessToken);
604 }
605 }
margaretha35e1ca22023-11-16 22:00:01 +0100606
607 public List<OAuth2TokenDto> listUserRefreshToken (String username,
608 String superClientId, String superClientSecret, String clientId)
609 throws KustvaktException {
610
611 OAuth2Client client = clientService.authenticateClient(superClientId,
612 superClientSecret);
margaretha7ac20b12023-09-27 09:40:16 +0200613 if (!client.isSuper()) {
614 throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
615 "Only super client is allowed.",
616 OAuth2Error.UNAUTHORIZED_CLIENT);
617 }
618
margaretha35e1ca22023-11-16 22:00:01 +0100619 List<RefreshToken> tokens = refreshDao
620 .retrieveRefreshTokenByUser(username, clientId);
margaretha7ac20b12023-09-27 09:40:16 +0200621 List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
margaretha35e1ca22023-11-16 22:00:01 +0100622 for (RefreshToken t : tokens) {
margaretha7ac20b12023-09-27 09:40:16 +0200623 OAuth2Client tokenClient = t.getClient();
margaretha35e1ca22023-11-16 22:00:01 +0100624 if (tokenClient.getId().equals(client.getId())) {
margaretha7ac20b12023-09-27 09:40:16 +0200625 continue;
626 }
627 OAuth2TokenDto dto = new OAuth2TokenDto();
628 dto.setClientId(tokenClient.getId());
629 dto.setClientName(tokenClient.getName());
630 dto.setClientUrl(tokenClient.getUrl());
631 dto.setClientDescription(tokenClient.getDescription());
margaretha35e1ca22023-11-16 22:00:01 +0100632
margaretha7ac20b12023-09-27 09:40:16 +0200633 DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
634 dto.setCreatedDate(t.getCreatedDate().format(f));
margaretha35e1ca22023-11-16 22:00:01 +0100635 long difference = ChronoUnit.SECONDS.between(ZonedDateTime.now(),
636 t.getExpiryDate());
margaretha7ac20b12023-09-27 09:40:16 +0200637 dto.setExpiresIn(difference);
margaretha35e1ca22023-11-16 22:00:01 +0100638
margaretha7ac20b12023-09-27 09:40:16 +0200639 dto.setUserAuthenticationTime(
640 t.getUserAuthenticationTime().format(f));
641 dto.setToken(t.getToken());
margaretha35e1ca22023-11-16 22:00:01 +0100642
margaretha7ac20b12023-09-27 09:40:16 +0200643 Set<AccessScope> accessScopes = t.getScopes();
644 Set<String> scopes = new HashSet<>(accessScopes.size());
margaretha35e1ca22023-11-16 22:00:01 +0100645 for (AccessScope s : accessScopes) {
margaretha7ac20b12023-09-27 09:40:16 +0200646 scopes.add(s.getId().toString());
647 }
648 dto.setScope(scopes);
649 dtoList.add(dto);
650 }
651 return dtoList;
652 }
margaretha35e1ca22023-11-16 22:00:01 +0100653
654 public List<OAuth2TokenDto> listUserAccessToken (String username,
655 String superClientId, String superClientSecret, String clientId)
656 throws KustvaktException {
657
658 OAuth2Client superClient = clientService
659 .authenticateClient(superClientId, superClientSecret);
margaretha7ac20b12023-09-27 09:40:16 +0200660 if (!superClient.isSuper()) {
661 throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
662 "Only super client is allowed.",
663 OAuth2Error.UNAUTHORIZED_CLIENT);
664 }
665
margaretha35e1ca22023-11-16 22:00:01 +0100666 List<AccessToken> tokens = tokenDao.retrieveAccessTokenByUser(username,
667 clientId);
margaretha7ac20b12023-09-27 09:40:16 +0200668 List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
margaretha35e1ca22023-11-16 22:00:01 +0100669 for (AccessToken t : tokens) {
margaretha7ac20b12023-09-27 09:40:16 +0200670 OAuth2Client tokenClient = t.getClient();
margaretha35e1ca22023-11-16 22:00:01 +0100671 if (tokenClient.getId().equals(superClient.getId())) {
margaretha7ac20b12023-09-27 09:40:16 +0200672 continue;
673 }
674 OAuth2TokenDto dto = new OAuth2TokenDto();
675 dto.setClientId(tokenClient.getId());
676 dto.setClientName(tokenClient.getName());
677 dto.setClientUrl(tokenClient.getUrl());
678 dto.setClientDescription(tokenClient.getDescription());
margaretha35e1ca22023-11-16 22:00:01 +0100679
margaretha7ac20b12023-09-27 09:40:16 +0200680 DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
681 dto.setCreatedDate(t.getCreatedDate().format(f));
margaretha35e1ca22023-11-16 22:00:01 +0100682
683 long difference = ChronoUnit.SECONDS.between(ZonedDateTime.now(),
684 t.getExpiryDate());
margaretha7ac20b12023-09-27 09:40:16 +0200685 dto.setExpiresIn(difference);
margaretha35e1ca22023-11-16 22:00:01 +0100686
margaretha7ac20b12023-09-27 09:40:16 +0200687 dto.setUserAuthenticationTime(
688 t.getUserAuthenticationTime().format(f));
689 dto.setToken(t.getToken());
margaretha35e1ca22023-11-16 22:00:01 +0100690
margaretha7ac20b12023-09-27 09:40:16 +0200691 Set<AccessScope> accessScopes = t.getScopes();
692 Set<String> scopes = new HashSet<>(accessScopes.size());
margaretha35e1ca22023-11-16 22:00:01 +0100693 for (AccessScope s : accessScopes) {
margaretha7ac20b12023-09-27 09:40:16 +0200694 scopes.add(s.getId().toString());
695 }
696 dto.setScope(scopes);
697 dtoList.add(dto);
698 }
699 return dtoList;
700 }
margaretha0e8f4e72018-04-05 14:11:52 +0200701}