Changed OAuth2 token request using Nimbus (#650)
Change-Id: I838c03013878675613901f62b6a047a845dd09a3
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
deleted file mode 100644
index 2311240..0000000
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
+++ /dev/null
@@ -1,645 +0,0 @@
-package de.ids_mannheim.korap.oauth2.oltu.service;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import jakarta.persistence.NoResultException;
-
-import org.apache.oltu.oauth2.as.request.AbstractOAuthTokenRequest;
-import org.apache.oltu.oauth2.as.response.OAuthASResponse;
-import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
-import org.apache.oltu.oauth2.common.message.OAuthResponse;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
-import org.apache.oltu.oauth2.common.message.types.TokenType;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import com.unboundid.ldap.sdk.LDAPException;
-
-import de.ids_mannheim.korap.authentication.LdapAuth3;
-import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.constant.AuthenticationMethod;
-import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
-import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
-import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
-import de.ids_mannheim.korap.oauth2.dto.OAuth2TokenDto;
-import de.ids_mannheim.korap.oauth2.entity.AccessScope;
-import de.ids_mannheim.korap.oauth2.entity.AccessToken;
-import de.ids_mannheim.korap.oauth2.entity.Authorization;
-import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
-import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeAllTokenSuperRequest;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
-import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
-import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
-import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
-import jakarta.ws.rs.core.Response.Status;
-
-/** Implementation of token service using Apache Oltu.
- *
- * @author margaretha
- *
- */
-@Service
-public class OltuTokenService extends OAuth2TokenService {
-
- @Autowired
- private RandomCodeGenerator randomGenerator;
-
- @Autowired
- private AccessTokenDao tokenDao;
- @Autowired
- private RefreshTokenDao refreshDao;
-
- @Autowired
- private OAuth2ClientService clientService;
-
- public OAuthResponse requestAccessToken (
- AbstractOAuthTokenRequest oAuthRequest)
- throws KustvaktException, OAuthSystemException {
-
- String grantType = oAuthRequest.getGrantType();
-
- if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
- return requestAccessTokenWithAuthorizationCode(
- oAuthRequest.getCode(), oAuthRequest.getRedirectURI(),
- oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
- }
- else if (grantType.equals(GrantType.PASSWORD.toString())) {
- return requestAccessTokenWithPassword(oAuthRequest.getClientId(),
- oAuthRequest.getClientSecret(), oAuthRequest.getUsername(),
- oAuthRequest.getPassword(), oAuthRequest.getScopes());
- }
- else if (grantType.equals(GrantType.CLIENT_CREDENTIALS.toString())) {
- return requestAccessTokenWithClientCredentials(
- oAuthRequest.getClientId(), oAuthRequest.getClientSecret(),
- oAuthRequest.getScopes());
- }
- else if (grantType.equals(GrantType.REFRESH_TOKEN.toString())) {
- return requestAccessTokenWithRefreshToken(
- oAuthRequest.getRefreshToken(), oAuthRequest.getScopes(),
- oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
- }
- else {
- throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
- grantType + " is not supported.",
- OAuth2Error.UNSUPPORTED_GRANT_TYPE);
- }
-
- }
-
- private boolean isInternalClient (String clientIpAddress) {
- try {
- InetAddress ip = InetAddress.getByName(clientIpAddress);
- if (ip.isSiteLocalAddress()) {
- return true;
- }
- }
- catch (UnknownHostException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return false;
- }
-
- /**
- * Revokes all access token associated with the given refresh
- * token, and creates a new access token and a new refresh
- * token with the same scopes. Thus, at one point of time,
- * there is only one active access token associated with
- * a refresh token.
- *
- * Client authentication is done using the given client
- * credentials.
- *
- * TODO: should create a new refresh token when the old refresh
- * token is used (DONE)
- *
- * @param refreshTokenStr
- * @param requestScopes
- * @param clientId
- * @param clientSecret
- * @return if successful, a new access token
- * @throws KustvaktException
- * @throws OAuthSystemException
- */
- private OAuthResponse requestAccessTokenWithRefreshToken (
- String refreshTokenStr, Set<String> requestScopes, String clientId,
- String clientSecret)
- throws KustvaktException, OAuthSystemException {
-
- if (refreshTokenStr == null || refreshTokenStr.isEmpty()) {
- throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
- "Missing parameter: refresh_token",
- OAuth2Error.INVALID_REQUEST);
- }
-
- OAuth2Client oAuth2Client = clientService.authenticateClient(clientId, clientSecret);
-
- RefreshToken refreshToken;
- try {
- refreshToken = refreshDao.retrieveRefreshToken(refreshTokenStr);
- }
- catch (NoResultException e) {
- throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
- "Refresh token is not found", OAuth2Error.INVALID_GRANT);
- }
-
- if (!clientId.equals(refreshToken.getClient().getId())) {
- throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
- "Client " + clientId + " is not authorized",
- OAuth2Error.INVALID_CLIENT);
- }
- else if (refreshToken.isRevoked()) {
- throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
- "Refresh token has been revoked",
- OAuth2Error.INVALID_GRANT);
- }
- else if (ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))
- .isAfter(refreshToken.getExpiryDate())) {
- throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
- "Refresh token is expired", OAuth2Error.INVALID_GRANT);
- }
-
- Set<AccessScope> tokenScopes =
- new HashSet<>(refreshToken.getScopes());
- if (requestScopes != null && !requestScopes.isEmpty()) {
- tokenScopes =
- scopeService.verifyRefreshScope(requestScopes, tokenScopes);
- requestScopes = scopeService
- .convertAccessScopesToStringSet(tokenScopes);
- }
-
- // revoke the refresh token and all access tokens associated to it
- revokeRefreshToken(refreshTokenStr);
-
- return createsAccessTokenResponse(requestScopes, tokenScopes, clientId,
- refreshToken.getUserId(),
- refreshToken.getUserAuthenticationTime(), oAuth2Client);
-
- // without new refresh token
- // return createsAccessTokenResponse(scopes, requestedScopes,
- // clientId,
- // refreshToken.getUserId(),
- // refreshToken.getUserAuthenticationTime(), refreshToken);
- }
-
- /**
- * Issues an access token for the specified client if the
- * authorization code is valid and client successfully
- * authenticates.
- *
- * @param code
- * authorization code, required
- * @param redirectUri
- * client redirect uri, required if specified in the
- * authorization request
- * @param clientId
- * client id, required
- * @param clientSecret
- * client secret, required
- * @return an {@link OAuthResponse}
- * @throws OAuthSystemException
- * @throws KustvaktException
- */
- private OAuthResponse requestAccessTokenWithAuthorizationCode (String code,
- String redirectUri, String clientId, String clientSecret)
- throws OAuthSystemException, KustvaktException {
- Authorization authorization = retrieveAuthorization(code, redirectUri,
- clientId, clientSecret);
-
- Set<String> scopes = scopeService
- .convertAccessScopesToStringSet(authorization.getScopes());
- OAuth2Client oAuth2Client = clientService.retrieveClient(clientId);
- return createsAccessTokenResponse(scopes, authorization.getScopes(),
- authorization.getClientId(), authorization.getUserId(),
- authorization.getUserAuthenticationTime(), oAuth2Client);
- }
-
- /**
- * Third party apps must not be allowed to use password grant.
- * MH: password grant is only allowed for trusted clients (korap
- * frontend)
- *
- * According to RFC 6749, client authentication is only required
- * for confidential clients and whenever client credentials are
- * provided. Moreover, client_id is optional for password grant,
- * but without it, the authentication server cannot check the
- * client type. To make sure that confidential clients
- * authenticate, client_id is made required (similar to
- * authorization code grant).
- *
- * TODO: FORCE client secret
- *
- * @param clientId
- * client_id, required
- * @param clientSecret
- * client_secret, required if client_secret was issued
- * for the client in client registration.
- * @param username
- * username, required
- * @param password
- * password, required
- * @param scopes
- * authorization scopes, optional
- * @return an {@link OAuthResponse}
- * @throws KustvaktException
- * @throws OAuthSystemException
- */
- private OAuthResponse requestAccessTokenWithPassword (String clientId,
- String clientSecret, String username, String password,
- Set<String> scopes) throws KustvaktException, OAuthSystemException {
-
- OAuth2Client client =
- clientService.authenticateClient(clientId, clientSecret);
- if (!client.isSuper()) {
- throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
- "Password grant is not allowed for third party clients",
- OAuth2Error.UNAUTHORIZED_CLIENT);
- }
-
- if (scopes == null || scopes.isEmpty()) {
- scopes = new HashSet<String>(1);
- scopes.add("all");
- // scopes = config.getDefaultAccessScopes();
- }
-
- ZonedDateTime authenticationTime =
- authenticateUser(username, password, scopes);
-
- Set<AccessScope> accessScopes =
- scopeService.convertToAccessScope(scopes);
-
- if (config.getOAuth2passwordAuthentication()
- .equals(AuthenticationMethod.LDAP)) {
- try {
- //username = LdapAuth3.getEmail(username, config.getLdapConfig());
- username = LdapAuth3.getUsername(username, config.getLdapConfig());
- }
- catch (LDAPException e) {
- throw new KustvaktException(StatusCodes.LDAP_BASE_ERRCODE,
- e.getExceptionMessage());
- }
- }
-
- return createsAccessTokenResponse(scopes, accessScopes, clientId,
- username, authenticationTime, client);
- }
-
- /**
- * Clients must authenticate.
- * Client credentials grant is limited to native clients.
- *
- * @param clientId
- * client_id parameter, required
- * @param clientSecret
- * client_secret parameter, required
- * @param scopes
- * authorization scopes, optional
- * @return an {@link OAuthResponse}
- * @throws KustvaktException
- * @throws OAuthSystemException
- */
- protected OAuthResponse requestAccessTokenWithClientCredentials (
- String clientId, String clientSecret, Set<String> scopes)
- throws KustvaktException, OAuthSystemException {
-
- if (clientSecret == null || clientSecret.isEmpty()) {
- throw new KustvaktException(
- StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- "Missing parameter: client_secret",
- OAuth2Error.INVALID_REQUEST);
- }
-
- // OAuth2Client client =
- OAuth2Client oAuth2Client = clientService.authenticateClient(clientId, clientSecret);
-
- // if (!client.isNative()) {
- // throw new KustvaktException(
- // StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- // "Client credentials grant is not allowed for third party
- // clients",
- // OAuth2Error.UNAUTHORIZED_CLIENT);
- // }
-
- ZonedDateTime authenticationTime =
- ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
-
- scopes = scopeService.filterScopes(scopes,
- config.getClientCredentialsScopes());
- Set<AccessScope> accessScopes =
- scopeService.convertToAccessScope(scopes);
- return createsAccessTokenResponse(scopes, accessScopes, clientId, null,
- authenticationTime,oAuth2Client);
- }
-
- /**
- * Creates an OAuthResponse containing an access token of type
- * Bearer. By default, MD generator is used to generates access
- * token of 128 bit values, represented in hexadecimal comprising
- * 32 bytes. The generated value is subsequently encoded in
- * Base64.
- *
- * <br /><br />
- * Additionally, a refresh token is issued for confidential clients.
- * It can be used to request a new access token without requiring user
- * re-authentication.
- *
- * @param scopes
- * a set of access token scopes in String
- * @param accessScopes
- * a set of access token scopes in {@link AccessScope}
- * @param clientId
- * a client id
- * @param userId
- * a user id
- * @param authenticationTime
- * the user authentication time
- * @return an {@link OAuthResponse}
- * @throws OAuthSystemException
- * @throws KustvaktException
- */
- private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
- Set<AccessScope> accessScopes, String clientId, String userId,
- ZonedDateTime authenticationTime, OAuth2Client client)
- throws OAuthSystemException, KustvaktException {
-
- String random = randomGenerator.createRandomCode();
- random += randomGenerator.createRandomCode();
-
- if (clientService.isPublicClient(client)){
- // refresh token == null, getAccessTokenLongExpiry
- return createsAccessTokenResponse(scopes, accessScopes, clientId,
- userId, authenticationTime);
- }
- else {
- // refresh token != null, getAccessTokenExpiry
- // default refresh token expiry: 365 days in seconds
- RefreshToken refreshToken = refreshDao.storeRefreshToken(random,
- userId, authenticationTime, client, accessScopes);
- return createsAccessTokenResponse(scopes, accessScopes, clientId,
- userId, authenticationTime, refreshToken);
- }
- }
-
- private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
- Set<AccessScope> accessScopes, String clientId, String userId,
- ZonedDateTime authenticationTime, RefreshToken refreshToken)
- throws OAuthSystemException, KustvaktException {
-
- String accessToken = randomGenerator.createRandomCode();
- accessToken +=randomGenerator.createRandomCode();
- tokenDao.storeAccessToken(accessToken, refreshToken, accessScopes,
- userId, clientId, authenticationTime);
-
- return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
- .setAccessToken(accessToken)
- .setTokenType(TokenType.BEARER.toString())
- .setExpiresIn(String.valueOf(config.getAccessTokenExpiry()))
- .setRefreshToken(refreshToken.getToken())
- .setScope(String.join(" ", scopes)).buildJSONMessage();
- }
-
- private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
- Set<AccessScope> accessScopes, String clientId, String userId,
- ZonedDateTime authenticationTime)
- throws OAuthSystemException, KustvaktException {
-
- String accessToken = randomGenerator.createRandomCode();
- accessToken +=randomGenerator.createRandomCode();
- tokenDao.storeAccessToken(accessToken, null, accessScopes,
- userId, clientId, authenticationTime);
-
- return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
- .setAccessToken(accessToken)
- .setTokenType(TokenType.BEARER.toString())
- .setExpiresIn(String.valueOf(config.getAccessTokenLongExpiry()))
- .setScope(String.join(" ", scopes)).buildJSONMessage();
- }
-
- public void revokeToken (OAuth2RevokeTokenRequest revokeTokenRequest)
- throws KustvaktException {
- String clientId = revokeTokenRequest.getClientId();
- String clientSecret = revokeTokenRequest.getClientSecret();
- String token = revokeTokenRequest.getToken();
- String tokenType = revokeTokenRequest.getTokenType();
-
- clientService.authenticateClient(clientId, clientSecret);
- if (tokenType != null && tokenType.equals("refresh_token")) {
- if (!revokeRefreshToken(token)) {
- revokeAccessToken(token);
- }
- return;
- }
-
- if (!revokeAccessToken(token)) {
- revokeRefreshToken(token);
- }
- }
-
- private boolean revokeAccessToken (String token) throws KustvaktException {
- try {
- AccessToken accessToken = tokenDao.retrieveAccessToken(token);
- revokeAccessToken(accessToken);
- return true;
- }
- catch (KustvaktException e) {
- if (!e.getStatusCode().equals(StatusCodes.INVALID_ACCESS_TOKEN)) {
- return false;
- }
- throw e;
- }
- }
-
- private void revokeAccessToken (AccessToken accessToken)
- throws KustvaktException {
- if (accessToken != null){
- accessToken.setRevoked(true);
- tokenDao.updateAccessToken(accessToken);
- }
- }
-
- private boolean revokeRefreshToken (String token) throws KustvaktException {
- RefreshToken refreshToken = null;
- try {
- refreshToken = refreshDao.retrieveRefreshToken(token);
- }
- catch (NoResultException e) {
- return false;
- }
-
- return revokeRefreshToken(refreshToken);
- }
-
- public boolean revokeRefreshToken (RefreshToken refreshToken)
- throws KustvaktException {
- if (refreshToken != null){
- refreshToken.setRevoked(true);
- refreshDao.updateRefreshToken(refreshToken);
-
- Set<AccessToken> accessTokenList = refreshToken.getAccessTokens();
- for (AccessToken accessToken : accessTokenList) {
- accessToken.setRevoked(true);
- tokenDao.updateAccessToken(accessToken);
- }
- return true;
- }
- return false;
- }
-
- public void revokeAllClientTokensViaSuperClient (String username,
- OAuth2RevokeAllTokenSuperRequest revokeTokenRequest)
- throws KustvaktException {
- String superClientId = revokeTokenRequest.getSuperClientId();
- String superClientSecret = revokeTokenRequest.getSuperClientSecret();
-
- OAuth2Client superClient = clientService
- .authenticateClient(superClientId, superClientSecret);
- if (!superClient.isSuper()) {
- throw new KustvaktException(
- StatusCodes.CLIENT_AUTHENTICATION_FAILED);
- }
-
- String clientId = revokeTokenRequest.getClientId();
- revokeAllClientTokensForUser(clientId, username);
- }
-
- public void revokeAllClientTokensForUser (String clientId, String username)
- throws KustvaktException {
- OAuth2Client client = clientService.retrieveClient(clientId);
- if (clientService.isPublicClient(client)) {
- List<AccessToken> accessTokens =
- tokenDao.retrieveAccessTokenByClientId(clientId, username);
- for (AccessToken t : accessTokens) {
- revokeAccessToken(t);
- }
- }
- else {
- List<RefreshToken> refreshTokens = refreshDao
- .retrieveRefreshTokenByClientId(clientId, username);
- for (RefreshToken r : refreshTokens) {
- revokeRefreshToken(r);
- }
- }
- }
-
- public void revokeTokensViaSuperClient (String username,
- OAuth2RevokeTokenSuperRequest revokeTokenRequest) throws KustvaktException {
- String superClientId = revokeTokenRequest.getSuperClientId();
- String superClientSecret = revokeTokenRequest.getSuperClientSecret();
-
- OAuth2Client superClient = clientService
- .authenticateClient(superClientId, superClientSecret);
- if (!superClient.isSuper()) {
- throw new KustvaktException(
- StatusCodes.CLIENT_AUTHENTICATION_FAILED);
- }
-
- String token = revokeTokenRequest.getToken();
- RefreshToken refreshToken = refreshDao.retrieveRefreshToken(token, username);
- if (!revokeRefreshToken(refreshToken)){
- AccessToken accessToken = tokenDao.retrieveAccessToken(token, username);
- revokeAccessToken(accessToken);
- }
- }
-
- public List<OAuth2TokenDto> listUserRefreshToken (String username, String superClientId,
- String superClientSecret, String clientId) throws KustvaktException {
-
- OAuth2Client client = clientService.authenticateClient(superClientId, superClientSecret);
- if (!client.isSuper()) {
- throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
- "Only super client is allowed.",
- OAuth2Error.UNAUTHORIZED_CLIENT);
- }
-
- List<RefreshToken> tokens = refreshDao.retrieveRefreshTokenByUser(username, clientId);
- List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
- for (RefreshToken t : tokens){
- OAuth2Client tokenClient = t.getClient();
- if (tokenClient.getId().equals(client.getId())){
- continue;
- }
- OAuth2TokenDto dto = new OAuth2TokenDto();
- dto.setClientId(tokenClient.getId());
- dto.setClientName(tokenClient.getName());
- dto.setClientUrl(tokenClient.getUrl());
- dto.setClientDescription(tokenClient.getDescription());
-
- DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
- dto.setCreatedDate(t.getCreatedDate().format(f));
- long difference = ChronoUnit.SECONDS.between(ZonedDateTime.now(), t.getExpiryDate());
- dto.setExpiresIn(difference);
-
- dto.setUserAuthenticationTime(
- t.getUserAuthenticationTime().format(f));
- dto.setToken(t.getToken());
-
- Set<AccessScope> accessScopes = t.getScopes();
- Set<String> scopes = new HashSet<>(accessScopes.size());
- for (AccessScope s : accessScopes){
- scopes.add(s.getId().toString());
- }
- dto.setScope(scopes);
- dtoList.add(dto);
- }
- return dtoList;
- }
-
- public List<OAuth2TokenDto> listUserAccessToken (String username, String superClientId,
- String superClientSecret, String clientId) throws KustvaktException {
-
- OAuth2Client superClient = clientService.authenticateClient(superClientId, superClientSecret);
- if (!superClient.isSuper()) {
- throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
- "Only super client is allowed.",
- OAuth2Error.UNAUTHORIZED_CLIENT);
- }
-
- List<AccessToken> tokens =
- tokenDao.retrieveAccessTokenByUser(username, clientId);
- List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
- for (AccessToken t : tokens){
- OAuth2Client tokenClient = t.getClient();
- if (tokenClient.getId().equals(superClient.getId())){
- continue;
- }
- OAuth2TokenDto dto = new OAuth2TokenDto();
- dto.setClientId(tokenClient.getId());
- dto.setClientName(tokenClient.getName());
- dto.setClientUrl(tokenClient.getUrl());
- dto.setClientDescription(tokenClient.getDescription());
-
- DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
- dto.setCreatedDate(t.getCreatedDate().format(f));
-
- long difference = ChronoUnit.SECONDS.between(ZonedDateTime.now(), t.getExpiryDate());
- dto.setExpiresIn(difference);
-
- dto.setUserAuthenticationTime(
- t.getUserAuthenticationTime().format(f));
- dto.setToken(t.getToken());
-
- Set<AccessScope> accessScopes = t.getScopes();
- Set<String> scopes = new HashSet<>(accessScopes.size());
- for (AccessScope s : accessScopes){
- scopes.add(s.getId().toString());
- }
- dto.setScope(scopes);
- dtoList.add(dto);
- }
- return dtoList;
- }
-
-}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
index 93692ad..e92c2be 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
@@ -71,7 +71,7 @@
* @author margaretha
*
*/
-@Service
+//@Service
public class OpenIdTokenService extends OAuth2TokenService {
@Autowired
@@ -79,7 +79,7 @@
@Autowired
private RefreshTokenDao refreshDao;
- public AccessTokenResponse requestAccessToken (TokenRequest tokenRequest)
+ public AccessTokenResponse requestAccessTokenWithOpenID (TokenRequest tokenRequest)
throws KustvaktException {
AuthorizationGrant grant = tokenRequest.getAuthorizationGrant();
GrantType grantType = grant.getType();
@@ -88,13 +88,13 @@
ClientID clientId = tokenRequest.getClientID();
if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
- return requestAccessTokenWithAuthorizationCode(grant,
+ return requestAccessTokenWithAuthorizationCodeAndOpenID(grant,
clientAuthentication, clientId);
}
else if (grantType.equals(GrantType.PASSWORD)) {
ResourceOwnerPasswordCredentialsGrant passwordGrant =
(ResourceOwnerPasswordCredentialsGrant) grant;
- return requestAccessTokenWithPassword(passwordGrant.getUsername(),
+ return requestAccessTokenWithPasswordAndOpenID(passwordGrant.getUsername(),
passwordGrant.getPassword().getValue(),
tokenRequest.getScope(), clientAuthentication, clientId);
}
@@ -133,7 +133,7 @@
* @return
* @throws KustvaktException
*/
- private AccessTokenResponse requestAccessTokenWithPassword (String username,
+ private AccessTokenResponse requestAccessTokenWithPasswordAndOpenID (String username,
String password, Scope scope,
ClientAuthentication clientAuthentication, ClientID clientId)
throws KustvaktException {
@@ -193,7 +193,7 @@
clientIdStr, username, authenticationTime, null);
}
- private AccessTokenResponse requestAccessTokenWithAuthorizationCode (
+ private AccessTokenResponse requestAccessTokenWithAuthorizationCodeAndOpenID (
AuthorizationGrant grant, ClientAuthentication clientAuthentication,
ClientID clientId) throws KustvaktException {
AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) grant;
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index 819a839..55d85ab 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -35,7 +35,6 @@
import de.ids_mannheim.korap.oauth2.entity.Authorization;
import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
-import de.ids_mannheim.korap.oauth2.oltu.service.OltuTokenService;
import de.ids_mannheim.korap.utils.ParameterChecker;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
@@ -67,7 +66,7 @@
// UrlValidator.NO_FRAGMENTS + UrlValidator.ALLOW_LOCAL_URLS);
@Autowired
- private OltuTokenService tokenService;
+ private OAuth2TokenService tokenService;
@Autowired
private InstalledPluginDao pluginDao;
@Autowired
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
index 9647748..c809daa 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
@@ -1,22 +1,60 @@
package de.ids_mannheim.korap.oauth2.service;
+import java.net.URI;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.RefreshTokenGrant;
+import com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.unboundid.ldap.sdk.LDAPException;
+
import de.ids_mannheim.korap.authentication.AuthenticationManager;
+import de.ids_mannheim.korap.authentication.LdapAuth3;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.AuthenticationMethod;
+import de.ids_mannheim.korap.encryption.RandomCodeGenerator;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+import de.ids_mannheim.korap.oauth2.dto.OAuth2TokenDto;
+import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeAllTokenSuperRequest;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
+import jakarta.persistence.NoResultException;
+import jakarta.ws.rs.core.Response.Status;
/**
* OAuth2TokenService manages business logic related to OAuth2
@@ -41,6 +79,14 @@
protected FullConfiguration config;
@Autowired
private AuthenticationManager authenticationManager;
+
+ @Autowired
+ private RandomCodeGenerator randomGenerator;
+
+ @Autowired
+ private AccessTokenDao tokenDao;
+ @Autowired
+ private RefreshTokenDao refreshDao;
/**
* RFC 6749:
@@ -103,4 +149,579 @@
return authenticationTime;
}
+ public OAuthResponse requestAccessToken (
+ TokenRequest tokenRequest, String clientId, String clientSecret)
+ throws KustvaktException, OAuthSystemException {
+
+ AuthorizationGrant authGrant = tokenRequest.getAuthorizationGrant();
+ GrantType grantType = authGrant.getType();
+ Scope scope = tokenRequest.getScope();
+ Set<String> scopeSet = new HashSet<>();
+ if (scope !=null)
+ scopeSet.addAll(scope.toStringList());
+
+ if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
+ AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) authGrant;
+ String authCode = codeGrant.getAuthorizationCode().getValue();
+ URI uri = codeGrant.getRedirectionURI();
+ String redirectionURI = (uri != null) ? uri.toString() : null;
+
+ return requestAccessTokenWithAuthorizationCode(authCode,
+ redirectionURI, clientId, clientSecret);
+ }
+ else if (grantType.equals(GrantType.PASSWORD)) {
+ ResourceOwnerPasswordCredentialsGrant passwordGrant =
+ (ResourceOwnerPasswordCredentialsGrant) authGrant;
+ String username = passwordGrant.getUsername();
+ String password = passwordGrant.getPassword().getValue();
+ return requestAccessTokenWithPassword(clientId, clientSecret,
+ username, password, scopeSet);
+ }
+ else if (grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
+ return requestAccessTokenWithClientCredentials(clientId,
+ clientSecret, scopeSet);
+ }
+ else if (grantType.equals(GrantType.REFRESH_TOKEN)) {
+ RefreshTokenGrant refreshGrant = (RefreshTokenGrant) authGrant;
+ String refreshToken = refreshGrant.getRefreshToken().getValue();
+ return requestAccessTokenWithRefreshToken(refreshToken, scopeSet,
+ clientId, clientSecret);
+ }
+ else {
+ throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
+ grantType + " is not supported.",
+ OAuth2Error.UNSUPPORTED_GRANT_TYPE);
+ }
+
+ }
+
+
+ /**
+ * Revokes all access token associated with the given refresh
+ * token, and creates a new access token and a new refresh
+ * token with the same scopes. Thus, at one point of time,
+ * there is only one active access token associated with
+ * a refresh token.
+ *
+ * Client authentication is done using the given client
+ * credentials.
+ *
+ * TODO: should create a new refresh token when the old refresh
+ * token is used (DONE)
+ *
+ * @param refreshTokenStr
+ * @param requestScopes
+ * @param clientId
+ * @param clientSecret
+ * @return if successful, a new access token
+ * @throws KustvaktException
+ * @throws OAuthSystemException
+ */
+ private OAuthResponse requestAccessTokenWithRefreshToken (
+ String refreshTokenStr, Set<String> requestScopes, String clientId,
+ String clientSecret)
+ throws KustvaktException, OAuthSystemException {
+
+ if (refreshTokenStr == null || refreshTokenStr.isEmpty()) {
+ throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+ "Missing parameter: refresh_token",
+ OAuth2Error.INVALID_REQUEST);
+ }
+
+ OAuth2Client oAuth2Client = clientService.authenticateClient(clientId, clientSecret);
+
+ RefreshToken refreshToken;
+ try {
+ refreshToken = refreshDao.retrieveRefreshToken(refreshTokenStr);
+ }
+ catch (NoResultException e) {
+ throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
+ "Refresh token is not found", OAuth2Error.INVALID_GRANT);
+ }
+
+ if (!clientId.equals(refreshToken.getClient().getId())) {
+ throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+ "Client " + clientId + " is not authorized",
+ OAuth2Error.INVALID_CLIENT);
+ }
+ else if (refreshToken.isRevoked()) {
+ throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
+ "Refresh token has been revoked",
+ OAuth2Error.INVALID_GRANT);
+ }
+ else if (ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))
+ .isAfter(refreshToken.getExpiryDate())) {
+ throw new KustvaktException(StatusCodes.INVALID_REFRESH_TOKEN,
+ "Refresh token is expired", OAuth2Error.INVALID_GRANT);
+ }
+
+ Set<AccessScope> tokenScopes =
+ new HashSet<>(refreshToken.getScopes());
+ if (requestScopes != null && !requestScopes.isEmpty()) {
+ tokenScopes =
+ scopeService.verifyRefreshScope(requestScopes, tokenScopes);
+ requestScopes = scopeService
+ .convertAccessScopesToStringSet(tokenScopes);
+ }
+
+ // revoke the refresh token and all access tokens associated to it
+ revokeRefreshToken(refreshTokenStr);
+
+ return createsAccessTokenResponse(requestScopes, tokenScopes, clientId,
+ refreshToken.getUserId(),
+ refreshToken.getUserAuthenticationTime(), oAuth2Client);
+
+ // without new refresh token
+ // return createsAccessTokenResponse(scopes, requestedScopes,
+ // clientId,
+ // refreshToken.getUserId(),
+ // refreshToken.getUserAuthenticationTime(), refreshToken);
+ }
+
+ /**
+ * Issues an access token for the specified client if the
+ * authorization code is valid and client successfully
+ * authenticates.
+ *
+ * @param code
+ * authorization code, required
+ * @param redirectionURI
+ * client redirect uri, required if specified in the
+ * authorization request
+ * @param clientId
+ * client id, required
+ * @param clientSecret
+ * client secret, required
+ * @return an {@link OAuthResponse}
+ * @throws OAuthSystemException
+ * @throws KustvaktException
+ */
+ private OAuthResponse requestAccessTokenWithAuthorizationCode (String code,
+ String redirectionURI, String clientId, String clientSecret)
+ throws OAuthSystemException, KustvaktException {
+ Authorization authorization = retrieveAuthorization(code, redirectionURI,
+ clientId, clientSecret);
+
+ Set<String> scopes = scopeService
+ .convertAccessScopesToStringSet(authorization.getScopes());
+ OAuth2Client oAuth2Client = clientService.retrieveClient(clientId);
+ return createsAccessTokenResponse(scopes, authorization.getScopes(),
+ authorization.getClientId(), authorization.getUserId(),
+ authorization.getUserAuthenticationTime(), oAuth2Client);
+ }
+
+ /**
+ * Third party apps must not be allowed to use password grant.
+ * MH: password grant is only allowed for trusted clients (korap
+ * frontend)
+ *
+ * According to RFC 6749, client authentication is only required
+ * for confidential clients and whenever client credentials are
+ * provided. Moreover, client_id is optional for password grant,
+ * but without it, the authentication server cannot check the
+ * client type. To make sure that confidential clients
+ * authenticate, client_id is made required (similar to
+ * authorization code grant).
+ *
+ * TODO: FORCE client secret
+ *
+ * @param clientId
+ * client_id, required
+ * @param clientSecret
+ * client_secret, required if client_secret was issued
+ * for the client in client registration.
+ * @param username
+ * username, required
+ * @param password
+ * password, required
+ * @param scopes
+ * authorization scopes, optional
+ * @return an {@link OAuthResponse}
+ * @throws KustvaktException
+ * @throws OAuthSystemException
+ */
+ private OAuthResponse requestAccessTokenWithPassword (String clientId,
+ String clientSecret, String username, String password,
+ Set<String> scopes) throws KustvaktException, OAuthSystemException {
+
+ OAuth2Client client =
+ clientService.authenticateClient(clientId, clientSecret);
+ if (!client.isSuper()) {
+ throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+ "Password grant is not allowed for third party clients",
+ OAuth2Error.UNAUTHORIZED_CLIENT);
+ }
+
+ if (scopes == null || scopes.isEmpty()) {
+ scopes = new HashSet<String>(1);
+ scopes.add("all");
+ // scopes = config.getDefaultAccessScopes();
+ }
+
+ ZonedDateTime authenticationTime =
+ authenticateUser(username, password, scopes);
+
+ Set<AccessScope> accessScopes =
+ scopeService.convertToAccessScope(scopes);
+
+ if (config.getOAuth2passwordAuthentication()
+ .equals(AuthenticationMethod.LDAP)) {
+ try {
+ //username = LdapAuth3.getEmail(username, config.getLdapConfig());
+ username = LdapAuth3.getUsername(username, config.getLdapConfig());
+ }
+ catch (LDAPException e) {
+ throw new KustvaktException(StatusCodes.LDAP_BASE_ERRCODE,
+ e.getExceptionMessage());
+ }
+ }
+
+ return createsAccessTokenResponse(scopes, accessScopes, clientId,
+ username, authenticationTime, client);
+ }
+
+ /**
+ * Clients must authenticate.
+ * Client credentials grant is limited to native clients.
+ *
+ * @param clientId
+ * client_id parameter, required
+ * @param clientSecret
+ * client_secret parameter, required
+ * @param scopes
+ * authorization scopes, optional
+ * @return an {@link OAuthResponse}
+ * @throws KustvaktException
+ * @throws OAuthSystemException
+ */
+ protected OAuthResponse requestAccessTokenWithClientCredentials (
+ String clientId, String clientSecret, Set<String> scopes)
+ throws KustvaktException, OAuthSystemException {
+
+ if (clientSecret == null || clientSecret.isEmpty()) {
+ throw new KustvaktException(
+ StatusCodes.CLIENT_AUTHENTICATION_FAILED,
+ "Missing parameter: client_secret",
+ OAuth2Error.INVALID_REQUEST);
+ }
+
+ // OAuth2Client client =
+ OAuth2Client oAuth2Client = clientService.authenticateClient(clientId, clientSecret);
+
+ // if (!client.isNative()) {
+ // throw new KustvaktException(
+ // StatusCodes.CLIENT_AUTHENTICATION_FAILED,
+ // "Client credentials grant is not allowed for third party
+ // clients",
+ // OAuth2Error.UNAUTHORIZED_CLIENT);
+ // }
+
+ ZonedDateTime authenticationTime =
+ ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
+
+ scopes = scopeService.filterScopes(scopes,
+ config.getClientCredentialsScopes());
+ Set<AccessScope> accessScopes =
+ scopeService.convertToAccessScope(scopes);
+ return createsAccessTokenResponse(scopes, accessScopes, clientId, null,
+ authenticationTime,oAuth2Client);
+ }
+
+ /**
+ * Creates an OAuthResponse containing an access token of type
+ * Bearer. By default, MD generator is used to generates access
+ * token of 128 bit values, represented in hexadecimal comprising
+ * 32 bytes. The generated value is subsequently encoded in
+ * Base64.
+ *
+ * <br /><br />
+ * Additionally, a refresh token is issued for confidential clients.
+ * It can be used to request a new access token without requiring user
+ * re-authentication.
+ *
+ * @param scopes
+ * a set of access token scopes in String
+ * @param accessScopes
+ * a set of access token scopes in {@link AccessScope}
+ * @param clientId
+ * a client id
+ * @param userId
+ * a user id
+ * @param authenticationTime
+ * the user authentication time
+ * @return an {@link OAuthResponse}
+ * @throws OAuthSystemException
+ * @throws KustvaktException
+ */
+ private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
+ Set<AccessScope> accessScopes, String clientId, String userId,
+ ZonedDateTime authenticationTime, OAuth2Client client)
+ throws OAuthSystemException, KustvaktException {
+
+ String random = randomGenerator.createRandomCode();
+ random += randomGenerator.createRandomCode();
+
+ if (clientService.isPublicClient(client)){
+ // refresh token == null, getAccessTokenLongExpiry
+ return createsAccessTokenResponse(scopes, accessScopes, clientId,
+ userId, authenticationTime);
+ }
+ else {
+ // refresh token != null, getAccessTokenExpiry
+ // default refresh token expiry: 365 days in seconds
+ RefreshToken refreshToken = refreshDao.storeRefreshToken(random,
+ userId, authenticationTime, client, accessScopes);
+ return createsAccessTokenResponse(scopes, accessScopes, clientId,
+ userId, authenticationTime, refreshToken);
+ }
+ }
+
+ private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
+ Set<AccessScope> accessScopes, String clientId, String userId,
+ ZonedDateTime authenticationTime, RefreshToken refreshToken)
+ throws OAuthSystemException, KustvaktException {
+
+ String accessToken = randomGenerator.createRandomCode();
+ accessToken +=randomGenerator.createRandomCode();
+ tokenDao.storeAccessToken(accessToken, refreshToken, accessScopes,
+ userId, clientId, authenticationTime);
+
+ return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
+ .setAccessToken(accessToken)
+ .setTokenType(TokenType.BEARER.toString())
+ .setExpiresIn(String.valueOf(config.getAccessTokenExpiry()))
+ .setRefreshToken(refreshToken.getToken())
+ .setScope(String.join(" ", scopes)).buildJSONMessage();
+ }
+
+ private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
+ Set<AccessScope> accessScopes, String clientId, String userId,
+ ZonedDateTime authenticationTime)
+ throws OAuthSystemException, KustvaktException {
+
+ String accessToken = randomGenerator.createRandomCode();
+ accessToken +=randomGenerator.createRandomCode();
+ tokenDao.storeAccessToken(accessToken, null, accessScopes,
+ userId, clientId, authenticationTime);
+
+ return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
+ .setAccessToken(accessToken)
+ .setTokenType(TokenType.BEARER.toString())
+ .setExpiresIn(String.valueOf(config.getAccessTokenLongExpiry()))
+ .setScope(String.join(" ", scopes)).buildJSONMessage();
+ }
+
+ public void revokeToken (OAuth2RevokeTokenRequest revokeTokenRequest)
+ throws KustvaktException {
+ String clientId = revokeTokenRequest.getClientId();
+ String clientSecret = revokeTokenRequest.getClientSecret();
+ String token = revokeTokenRequest.getToken();
+ String tokenType = revokeTokenRequest.getTokenType();
+
+ clientService.authenticateClient(clientId, clientSecret);
+ if (tokenType != null && tokenType.equals("refresh_token")) {
+ if (!revokeRefreshToken(token)) {
+ revokeAccessToken(token);
+ }
+ return;
+ }
+
+ if (!revokeAccessToken(token)) {
+ revokeRefreshToken(token);
+ }
+ }
+
+ private boolean revokeAccessToken (String token) throws KustvaktException {
+ try {
+ AccessToken accessToken = tokenDao.retrieveAccessToken(token);
+ revokeAccessToken(accessToken);
+ return true;
+ }
+ catch (KustvaktException e) {
+ if (!e.getStatusCode().equals(StatusCodes.INVALID_ACCESS_TOKEN)) {
+ return false;
+ }
+ throw e;
+ }
+ }
+
+ private void revokeAccessToken (AccessToken accessToken)
+ throws KustvaktException {
+ if (accessToken != null){
+ accessToken.setRevoked(true);
+ tokenDao.updateAccessToken(accessToken);
+ }
+ }
+
+ private boolean revokeRefreshToken (String token) throws KustvaktException {
+ RefreshToken refreshToken = null;
+ try {
+ refreshToken = refreshDao.retrieveRefreshToken(token);
+ }
+ catch (NoResultException e) {
+ return false;
+ }
+
+ return revokeRefreshToken(refreshToken);
+ }
+
+ public boolean revokeRefreshToken (RefreshToken refreshToken)
+ throws KustvaktException {
+ if (refreshToken != null){
+ refreshToken.setRevoked(true);
+ refreshDao.updateRefreshToken(refreshToken);
+
+ Set<AccessToken> accessTokenList = refreshToken.getAccessTokens();
+ for (AccessToken accessToken : accessTokenList) {
+ accessToken.setRevoked(true);
+ tokenDao.updateAccessToken(accessToken);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void revokeAllClientTokensViaSuperClient (String username,
+ OAuth2RevokeAllTokenSuperRequest revokeTokenRequest)
+ throws KustvaktException {
+ String superClientId = revokeTokenRequest.getSuperClientId();
+ String superClientSecret = revokeTokenRequest.getSuperClientSecret();
+
+ OAuth2Client superClient = clientService
+ .authenticateClient(superClientId, superClientSecret);
+ if (!superClient.isSuper()) {
+ throw new KustvaktException(
+ StatusCodes.CLIENT_AUTHENTICATION_FAILED);
+ }
+
+ String clientId = revokeTokenRequest.getClientId();
+ revokeAllClientTokensForUser(clientId, username);
+ }
+
+ public void revokeAllClientTokensForUser (String clientId, String username)
+ throws KustvaktException {
+ OAuth2Client client = clientService.retrieveClient(clientId);
+ if (clientService.isPublicClient(client)) {
+ List<AccessToken> accessTokens =
+ tokenDao.retrieveAccessTokenByClientId(clientId, username);
+ for (AccessToken t : accessTokens) {
+ revokeAccessToken(t);
+ }
+ }
+ else {
+ List<RefreshToken> refreshTokens = refreshDao
+ .retrieveRefreshTokenByClientId(clientId, username);
+ for (RefreshToken r : refreshTokens) {
+ revokeRefreshToken(r);
+ }
+ }
+ }
+
+ public void revokeTokensViaSuperClient (String username,
+ OAuth2RevokeTokenSuperRequest revokeTokenRequest) throws KustvaktException {
+ String superClientId = revokeTokenRequest.getSuperClientId();
+ String superClientSecret = revokeTokenRequest.getSuperClientSecret();
+
+ OAuth2Client superClient = clientService
+ .authenticateClient(superClientId, superClientSecret);
+ if (!superClient.isSuper()) {
+ throw new KustvaktException(
+ StatusCodes.CLIENT_AUTHENTICATION_FAILED);
+ }
+
+ String token = revokeTokenRequest.getToken();
+ RefreshToken refreshToken = refreshDao.retrieveRefreshToken(token, username);
+ if (!revokeRefreshToken(refreshToken)){
+ AccessToken accessToken = tokenDao.retrieveAccessToken(token, username);
+ revokeAccessToken(accessToken);
+ }
+ }
+
+ public List<OAuth2TokenDto> listUserRefreshToken (String username, String superClientId,
+ String superClientSecret, String clientId) throws KustvaktException {
+
+ OAuth2Client client = clientService.authenticateClient(superClientId, superClientSecret);
+ if (!client.isSuper()) {
+ throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+ "Only super client is allowed.",
+ OAuth2Error.UNAUTHORIZED_CLIENT);
+ }
+
+ List<RefreshToken> tokens = refreshDao.retrieveRefreshTokenByUser(username, clientId);
+ List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
+ for (RefreshToken t : tokens){
+ OAuth2Client tokenClient = t.getClient();
+ if (tokenClient.getId().equals(client.getId())){
+ continue;
+ }
+ OAuth2TokenDto dto = new OAuth2TokenDto();
+ dto.setClientId(tokenClient.getId());
+ dto.setClientName(tokenClient.getName());
+ dto.setClientUrl(tokenClient.getUrl());
+ dto.setClientDescription(tokenClient.getDescription());
+
+ DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
+ dto.setCreatedDate(t.getCreatedDate().format(f));
+ long difference = ChronoUnit.SECONDS.between(ZonedDateTime.now(), t.getExpiryDate());
+ dto.setExpiresIn(difference);
+
+ dto.setUserAuthenticationTime(
+ t.getUserAuthenticationTime().format(f));
+ dto.setToken(t.getToken());
+
+ Set<AccessScope> accessScopes = t.getScopes();
+ Set<String> scopes = new HashSet<>(accessScopes.size());
+ for (AccessScope s : accessScopes){
+ scopes.add(s.getId().toString());
+ }
+ dto.setScope(scopes);
+ dtoList.add(dto);
+ }
+ return dtoList;
+ }
+
+ public List<OAuth2TokenDto> listUserAccessToken (String username, String superClientId,
+ String superClientSecret, String clientId) throws KustvaktException {
+
+ OAuth2Client superClient = clientService.authenticateClient(superClientId, superClientSecret);
+ if (!superClient.isSuper()) {
+ throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+ "Only super client is allowed.",
+ OAuth2Error.UNAUTHORIZED_CLIENT);
+ }
+
+ List<AccessToken> tokens =
+ tokenDao.retrieveAccessTokenByUser(username, clientId);
+ List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
+ for (AccessToken t : tokens){
+ OAuth2Client tokenClient = t.getClient();
+ if (tokenClient.getId().equals(superClient.getId())){
+ continue;
+ }
+ OAuth2TokenDto dto = new OAuth2TokenDto();
+ dto.setClientId(tokenClient.getId());
+ dto.setClientName(tokenClient.getName());
+ dto.setClientUrl(tokenClient.getUrl());
+ dto.setClientDescription(tokenClient.getDescription());
+
+ DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
+ dto.setCreatedDate(t.getCreatedDate().format(f));
+
+ long difference = ChronoUnit.SECONDS.between(ZonedDateTime.now(), t.getExpiryDate());
+ dto.setExpiresIn(difference);
+
+ dto.setUserAuthenticationTime(
+ t.getUserAuthenticationTime().format(f));
+ dto.setToken(t.getToken());
+
+ Set<AccessScope> accessScopes = t.getScopes();
+ Set<String> scopes = new HashSet<>(accessScopes.size());
+ for (AccessScope s : accessScopes){
+ scopes.add(s.getId().toString());
+ }
+ dto.setScope(scopes);
+ dtoList.add(dto);
+ }
+ return dtoList;
+ }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index 6e58ac8..5792be4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -4,17 +4,30 @@
import java.net.URISyntaxException;
import java.time.ZonedDateTime;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.nimbusds.oauth2.sdk.AuthorizationErrorResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.ClientCredentialsGrant;
import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.id.ClientID;
import de.ids_mannheim.korap.constant.OAuth2Scope;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.oauth2.service.OAuth2AuthorizationService;
import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
+import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
import de.ids_mannheim.korap.security.context.TokenContext;
import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
import de.ids_mannheim.korap.web.filter.APIVersionFilter;
@@ -57,7 +70,8 @@
@Autowired
private OAuth2ResponseHandler responseHandler;
-
+ @Autowired
+ private OAuth2TokenService tokenService;
@Autowired
private OAuth2AuthorizationService authorizationService;
@@ -237,42 +251,83 @@
* if successful, an error code and an error description
* otherwise.
*/
-// @POST
-// @Path("token")
-// @ResourceFilters({APIVersionFilter.class})
-// @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-// public Response requestAccessToken (@Context HttpServletRequest request,
-// @FormParam("grant_type") String grantType,
-// MultivaluedMap<String, String> form) {
-//
-// try {
-// boolean grantTypeExist = grantType != null && !grantType.isEmpty();
-// AbstractOAuthTokenRequest oAuthRequest = null;
-// if (grantTypeExist && grantType
-// .equals(GrantType.CLIENT_CREDENTIALS.toString())) {
-// oAuthRequest = new OAuthTokenRequest(
-// new FormRequestWrapper(request, form));
-// }
-// else {
-// oAuthRequest = new OAuthUnauthenticatedTokenRequest(
-// new FormRequestWrapper(request, form));
-// }
-//
-// OAuthResponse oAuthResponse =
-// tokenService.requestAccessToken(oAuthRequest);
-//
-// return responseHandler.createResponse(oAuthResponse);
-// }
-// catch (KustvaktException e) {
-// throw responseHandler.throwit(e);
-// }
-// catch (OAuthProblemException e) {
-// throw responseHandler.throwit(e);
-// }
-// catch (OAuthSystemException e) {
-// throw responseHandler.throwit(e);
-// }
-// }
+ @POST
+ @Path("token")
+ @ResourceFilters({APIVersionFilter.class})
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response requestAccessToken (@Context HttpServletRequest request,
+ @FormParam("client_id") String clientId,
+ @FormParam("client_secret") String clientSecret,
+ MultivaluedMap<String, String> form) {
+
+ OAuthResponse oAuthResponse = null;
+ try {
+ URI requestURI;
+ UriBuilder builder = UriBuilder.fromPath(
+ request.getRequestURL().toString());
+ for (String key : form.keySet()) {
+ builder.queryParam(key, form.get(key).toArray());
+ }
+ requestURI = builder.build();
+
+ try {
+ AuthorizationGrant authGrant = AuthorizationGrant.parse(form);
+
+ ClientAuthentication clientAuth = null;
+ String authorizationHeader = request.getHeader("Authorization");
+ if (authorizationHeader!=null && !authorizationHeader.isEmpty() ) {
+ clientAuth = ClientSecretBasic.parse(authorizationHeader);
+ }
+ else if (authGrant instanceof ClientCredentialsGrant) {
+ // this doesn't allow public clients
+ clientAuth = ClientSecretPost.parse(form);
+ }
+
+ TokenRequest tokenRequest = null;
+ if (clientAuth!=null) {
+ ClientAuthenticationMethod method = clientAuth.getMethod();
+ if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
+ ClientSecretBasic basic = (ClientSecretBasic) clientAuth;
+ clientSecret = basic.getClientSecret().getValue();
+ clientId = basic.getClientID().getValue();
+ }
+ else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
+ ClientSecretPost post = (ClientSecretPost) clientAuth;
+ clientSecret = post.getClientSecret().getValue();
+ clientId = post.getClientID().getValue();
+ }
+
+ tokenRequest = new TokenRequest(requestURI,
+ clientAuth,
+ AuthorizationGrant.parse(form),
+ Scope.parse(form.getFirst("scope")));
+ }
+ else {
+ // requires ClientAuthentication for client_credentials grant
+ tokenRequest = new TokenRequest(requestURI,
+ new ClientID(clientId),
+ AuthorizationGrant.parse(form),
+ Scope.parse(form.getFirst("scope")));
+ }
+
+ oAuthResponse = tokenService.requestAccessToken(tokenRequest,
+ clientId, clientSecret);
+ }
+ catch (ParseException | IllegalArgumentException e) {
+ throw new KustvaktException(StatusCodes.INVALID_REQUEST,
+ e.getMessage(), OAuth2Error.INVALID_REQUEST);
+ }
+
+ }
+ catch (KustvaktException e) {
+ throw responseHandler.throwit(e);
+ }
+ catch (OAuthSystemException e) {
+ throw responseHandler.throwit(e);
+ }
+
+ return responseHandler.createResponse(oAuthResponse);
+ }
/**
* Revokes either an access token or a refresh token. Revoking a
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
index dea240c..f2c60d2 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
@@ -64,7 +64,7 @@
@Autowired
private OpenIdAuthorizationService authzService;
- @Autowired
+ //@Autowired
private OpenIdTokenService tokenService;
@Autowired
private JWKService jwkService;
@@ -197,7 +197,7 @@
TokenRequest tokenRequest = TokenRequest.parse(httpRequest);
AccessTokenResponse tokenResponse =
- tokenService.requestAccessToken(tokenRequest);
+ tokenService.requestAccessTokenWithOpenID(tokenRequest);
return openIdResponseHandler.createResponse(tokenResponse,
Status.OK);
}
diff --git a/full/src/main/resources/basic-config.xml b/full/src/main/resources/basic-config.xml
index 87cb8f7..c8cfdaa 100644
--- a/full/src/main/resources/basic-config.xml
+++ b/full/src/main/resources/basic-config.xml
@@ -221,12 +221,6 @@
<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
</bean>
- <bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
- </bean>
- <bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
- <constructor-arg index="0" ref="mdGenerator" />
- </bean>
-
<bean id="kustvakt_userdb" class="de.ids_mannheim.korap.handlers.EntityDao">
<constructor-arg ref="kustvakt_db" />
</bean>
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index a375b44..fa3ed22 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -242,12 +242,6 @@
<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
</bean>
- <bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
- </bean>
- <bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
- <constructor-arg index="0" ref="mdGenerator" />
- </bean>
-
<bean name="kustvakt_encryption" class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
<constructor-arg ref="kustvakt_config" />
</bean>
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index 8ac612d..a529090 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -78,9 +78,6 @@
MultivaluedMap<String, String> params =
getQueryParamsFromURI(response.getLocation());
String code = params.get("code").get(0);
- String scopes = params.get("scope").get(0);
-
- assertEquals(scopes, "search");
response = requestTokenWithAuthorizationCodeAndForm(
confidentialClientId, clientSecret, code);
@@ -362,6 +359,20 @@
public void testRequestTokenPasswordGrantMissingClientSecret ()
throws KustvaktException {
Response response =
+ requestTokenWithDoryPassword(confidentialClientId, null);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ String entity = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+ node.at("/error").asText());
+ assertNotNull(node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantEmptyClientSecret ()
+ throws KustvaktException {
+ Response response =
requestTokenWithDoryPassword(confidentialClientId, "");
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
@@ -384,8 +395,21 @@
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
node.at("/error").asText());
- assertEquals("Missing parameters: client_id",
- node.at("/error_description").asText());
+ assertNotNull(node.at("/error_description").asText());
+ }
+
+ @Test
+ public void testRequestTokenPasswordGrantEmptyClientId ()
+ throws KustvaktException {
+ Response response =
+ requestTokenWithDoryPassword("", clientSecret);
+ String entity = response.readEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+ node.at("/error").asText());
+ assertNotNull(node.at("/error_description").asText());
}
@Test
@@ -398,6 +422,7 @@
form.param("client_secret", "secret");
Response response = requestToken(form);
String entity = response.readEntity(String.class);
+ System.out.println(entity);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
@@ -428,8 +453,7 @@
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
node.at("/error").asText());
- assertEquals("Missing parameters: client_secret",
- node.at("/error_description").asText());
+ assertNotNull(node.at("/error_description").asText());
}
@Test
@@ -486,8 +510,7 @@
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
- assertEquals("Invalid grant_type parameter value",
- node.get("error_description").asText());
+ assertNotNull(node.get("error_description").asText());
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
node.get("error").asText());
}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index bb5440c..a5189d0 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -234,7 +234,9 @@
Form form = new Form();
form.param("grant_type", "password");
form.param("client_id", clientId);
- form.param("client_secret", clientSecret);
+ if (clientSecret !=null && !clientSecret.isEmpty()) {
+ form.param("client_secret", clientSecret);
+ }
form.param("username", username);
form.param("password", password);
@@ -262,7 +264,7 @@
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
- assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+ assertEquals(OAuth2Error.INVALID_GRANT.getCode(), node.at("/error").asText());
assertEquals("Refresh token has been revoked",
node.at("/error_description").asText());
}
diff --git a/full/src/test/resources/test-config-icc.xml b/full/src/test/resources/test-config-icc.xml
index 383f378..466b052 100644
--- a/full/src/test/resources/test-config-icc.xml
+++ b/full/src/test/resources/test-config-icc.xml
@@ -229,12 +229,6 @@
<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
</bean>
- <bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
- </bean>
- <bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
- <constructor-arg index="0" ref="mdGenerator" />
- </bean>
-
<bean name="kustvakt_encryption" class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
<constructor-arg ref="kustvakt_config" />
</bean>
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index a85673b..2c16410 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -224,12 +224,6 @@
<bean id="oauth2ResponseHandler" class="de.ids_mannheim.korap.web.OAuth2ResponseHandler">
</bean>
- <bean id="mdGenerator" class="org.apache.oltu.oauth2.as.issuer.MD5Generator">
- </bean>
- <bean id="oauthIssuer" class="org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl">
- <constructor-arg index="0" ref="mdGenerator" />
- </bean>
-
<bean name="kustvakt_encryption" class="de.ids_mannheim.korap.encryption.KustvaktEncryption">
<constructor-arg ref="kustvakt_config" />
</bean>