pl.edu.icm.unity.oauth.as.token.access.RefreshTokenHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unity-server-oauth Show documentation
Show all versions of unity-server-oauth Show documentation
Client and server OAuth support
The newest version!
/*
* Copyright (c) 2021 Bixbit - Krzysztof Benedyczak. All rights reserved.
* See LICENCE.txt file for licensing information.
*/
package pl.edu.icm.unity.oauth.as.token.access;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import jakarta.ws.rs.core.Response;
import org.apache.logging.log4j.Logger;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
import com.nimbusds.oauth2.sdk.GrantType;
import com.nimbusds.oauth2.sdk.OAuth2Error;
import com.nimbusds.oauth2.sdk.client.ClientType;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import pl.edu.icm.unity.base.entity.EntityParam;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.token.Token;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.authn.InvocationContext;
import pl.edu.icm.unity.oauth.as.OAuthASProperties;
import pl.edu.icm.unity.oauth.as.OAuthToken;
import pl.edu.icm.unity.oauth.as.token.BaseOAuthResource;
import pl.edu.icm.unity.oauth.as.token.OAuthErrorException;
class RefreshTokenHandler
{
private static final Logger log = Log.getLogger(Log.U_SERVER_OAUTH, RefreshTokenHandler.class);
private final OAuthASProperties config;
private final OAuthRefreshTokenRepository refreshTokensRepository;
private final AccessTokenFactory accessTokenFactory;
private final OAuthAccessTokenRepository accessTokensRepository;
private final OAuthClientTokensCleaner tokenCleaner;
private final TokenService tokenService;
RefreshTokenHandler(OAuthASProperties config, OAuthRefreshTokenRepository refreshTokensDAO,
AccessTokenFactory accessTokenFactory, OAuthAccessTokenRepository accessTokensDAO,
OAuthClientTokensCleaner tokenCleaner, TokenService tokenService)
{
this.config = config;
this.refreshTokensRepository = refreshTokensDAO;
this.accessTokenFactory = accessTokenFactory;
this.accessTokensRepository = accessTokensDAO;
this.tokenCleaner = tokenCleaner;
this.tokenService = tokenService;
}
Response handleRefreshTokenGrant(String refToken, String scope, String acceptHeader)
throws EngineException, JsonProcessingException
{
Optional usedRefreshToken = getUsedRefreshTokenIfRotationIsActive(refToken);
if (usedRefreshToken.isPresent())
{
clearTokensForClient(usedRefreshToken.get());
return BaseOAuthResource.makeError(OAuth2Error.INVALID_REQUEST, "refresh token has already been used");
}
Token refreshToken = null;
OAuthToken parsedRefreshToken = null;
try
{
refreshToken = refreshTokensRepository.readRefreshToken(refToken);
parsedRefreshToken = BaseOAuthResource.parseInternalToken(refreshToken);
} catch (Exception e)
{
return BaseOAuthResource.makeError(OAuth2Error.INVALID_REQUEST, "wrong refresh token");
}
if (isRequiredAuthenticationMissing(parsedRefreshToken.getClientType()))
{
return BaseOAuthResource.makeError(OAuth2Error.INVALID_CLIENT, "not authenticated");
}
long callerEntityId = parsedRefreshToken.getClientType().equals(ClientType.CONFIDENTIAL)
? InvocationContext.getCurrent().getLoginSession().getEntityId()
: parsedRefreshToken.getClientId();
if (parsedRefreshToken.getClientId() != callerEntityId)
{
log.warn("Client with id {} presented use refresh code issued for client",
parsedRefreshToken.getClientId());
// intended - we mask the reason
return BaseOAuthResource.makeError(OAuth2Error.INVALID_GRANT, "wrong refresh token");
}
List oldRequestedScopesList = Arrays.asList(parsedRefreshToken.getRequestedScope());
OAuthToken newToken = null;
// When no scopes are requested RFC mandates to assign all originally assigned
if (scope == null)
scope = String.join(" ", oldRequestedScopesList);
try
{
newToken = tokenService.prepareNewTokenBasedOnOldToken(parsedRefreshToken, scope, oldRequestedScopesList,
refreshToken.getOwner(), callerEntityId, parsedRefreshToken.getClientUsername(), true,
GrantType.REFRESH_TOKEN.getValue());
} catch (OAuthErrorException e)
{
return e.response;
}
Date now = new Date();
Date accessExpiration = TokenUtils.getAccessTokenExpiration(config, now);
AccessToken accessToken = accessTokenFactory.create(newToken, now, acceptHeader);
newToken.setAccessToken(accessToken.getValue());
RefreshToken rotatedRefreshToken = refreshTokensRepository
.rotateRefreshTokenIfNeeded(config, now, newToken, parsedRefreshToken, refreshToken.getOwner())
.orElse(null);
AccessTokenResponse oauthResponse = tokenService.getAccessTokenResponse(newToken, accessToken, rotatedRefreshToken,
null);
log.info("Refreshed access token {} of entity {}, valid until {}",
BaseOAuthResource.tokenToLog(accessToken.getValue()), refreshToken.getOwner(), accessExpiration);
accessTokensRepository.storeAccessToken(accessToken, newToken, new EntityParam(refreshToken.getOwner()), now,
accessExpiration);
return BaseOAuthResource.toResponse(Response.ok(BaseOAuthResource.getResponseContent(oauthResponse)));
}
private boolean isRequiredAuthenticationMissing(ClientType clientType)
{
if (clientType.equals(ClientType.PUBLIC))
return false;
return InvocationContext.getCurrent().getLoginSession() == null;
}
private Optional getUsedRefreshTokenIfRotationIsActive(String refToken)
{
if (config.getBooleanValue(OAuthASProperties.ENABLE_REFRESH_TOKENS_FOR_PUBLIC_CLIENTS_WITH_ROTATION))
{
return refreshTokensRepository.getUsedRefreshToken(refToken);
}
return Optional.empty();
}
private void clearTokensForClient(Token usedRefreshToken)
{
OAuthToken oldRefreshToken = OAuthToken.getInstanceFromJson(usedRefreshToken.getContents());
tokenCleaner.removeTokensForClient(oldRefreshToken.getClientId(), usedRefreshToken.getOwner(),
oldRefreshToken.getFirstRefreshRollingToken());
log.warn(
"Trying to reuse already used refresh token, revoke the currently active oauth tokens for client {} {}",
oldRefreshToken.getClientId(), oldRefreshToken.getClientName());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy