-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(jans-auth-server): introduced TokenExchangeService
Native SSO #2518
- Loading branch information
Showing
4 changed files
with
258 additions
and
164 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
jans-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenCreatorService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package io.jans.as.server.token.ws.rs; | ||
|
||
import com.google.common.base.Strings; | ||
import io.jans.as.common.model.common.User; | ||
import io.jans.as.common.model.registration.Client; | ||
import io.jans.as.common.service.AttributeService; | ||
import io.jans.as.model.common.GrantType; | ||
import io.jans.as.model.common.ScopeConstants; | ||
import io.jans.as.model.configuration.AppConfiguration; | ||
import io.jans.as.model.error.ErrorResponseFactory; | ||
import io.jans.as.model.token.TokenErrorResponseType; | ||
import io.jans.as.server.model.common.AbstractAuthorizationGrant; | ||
import io.jans.as.server.model.common.AuthorizationGrant; | ||
import io.jans.as.server.model.common.ExecutionContext; | ||
import io.jans.as.server.model.common.RefreshToken; | ||
import io.jans.as.server.service.external.ExternalUpdateTokenService; | ||
import io.jans.as.server.service.external.context.ExternalUpdateTokenContext; | ||
import jakarta.ejb.Stateless; | ||
import jakarta.inject.Inject; | ||
import jakarta.inject.Named; | ||
import jakarta.ws.rs.WebApplicationException; | ||
import jakarta.ws.rs.core.MediaType; | ||
import jakarta.ws.rs.core.Response; | ||
import org.apache.commons.lang.BooleanUtils; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.slf4j.Logger; | ||
|
||
import java.util.Arrays; | ||
|
||
import static org.apache.commons.lang.BooleanUtils.isTrue; | ||
|
||
/** | ||
* @author Yuriy Z | ||
*/ | ||
@Stateless | ||
@Named | ||
public class TokenCreatorService { | ||
|
||
@Inject | ||
private Logger log; | ||
|
||
@Inject | ||
private AppConfiguration appConfiguration; | ||
|
||
@Inject | ||
private ExternalUpdateTokenService externalUpdateTokenService; | ||
|
||
@Inject | ||
private ErrorResponseFactory errorResponseFactory; | ||
|
||
@Inject | ||
private AttributeService attributeService; | ||
|
||
public boolean isRefreshTokenAllowed(Client client, String requestedScope, AbstractAuthorizationGrant grant) { | ||
if (isTrue(appConfiguration.getForceOfflineAccessScopeToEnableRefreshToken()) && !grant.getScopes().contains(ScopeConstants.OFFLINE_ACCESS) && !Strings.nullToEmpty(requestedScope).contains(ScopeConstants.OFFLINE_ACCESS)) { | ||
return false; | ||
} | ||
return Arrays.asList(client.getGrantTypes()).contains(GrantType.REFRESH_TOKEN); | ||
} | ||
|
||
@Nullable | ||
public RefreshToken createRefreshToken(@NotNull ExecutionContext executionContext, @NotNull String scope) { | ||
final AuthorizationGrant grant = executionContext.getGrant(); | ||
if (!isRefreshTokenAllowed(executionContext.getClient(), scope, grant)) { | ||
return null; | ||
} | ||
|
||
checkUser(grant); | ||
|
||
final ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(executionContext.getHttpRequest(), grant, executionContext.getClient(), appConfiguration, attributeService); | ||
context.setExecutionContext(executionContext); | ||
|
||
final int refreshTokenLifetimeInSeconds = externalUpdateTokenService.getRefreshTokenLifetimeInSeconds(context); | ||
if (refreshTokenLifetimeInSeconds > 0) { | ||
return grant.createRefreshToken(executionContext, refreshTokenLifetimeInSeconds); | ||
} | ||
return grant.createRefreshToken(executionContext); | ||
} | ||
|
||
private void checkUser(AuthorizationGrant authorizationGrant) { | ||
if (BooleanUtils.isFalse(appConfiguration.getCheckUserPresenceOnRefreshToken())) { | ||
return; | ||
} | ||
|
||
final User user = authorizationGrant.getUser(); | ||
if (user == null || "inactive".equalsIgnoreCase(user.getStatus())) { | ||
log.trace("The user associated with this grant is not found or otherwise with status=inactive."); | ||
throw new WebApplicationException(error(400, TokenErrorResponseType.INVALID_GRANT, "The user associated with this grant is not found or otherwise with status=inactive.").build()); | ||
} | ||
} | ||
|
||
public Response.ResponseBuilder error(int status, TokenErrorResponseType type, String reason) { | ||
return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(errorResponseFactory.errorAsJson(type, reason)); | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
...-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenExchangeService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package io.jans.as.server.token.ws.rs; | ||
|
||
import io.jans.as.common.model.common.User; | ||
import io.jans.as.common.model.registration.Client; | ||
import io.jans.as.common.model.session.SessionId; | ||
import io.jans.as.common.service.AttributeService; | ||
import io.jans.as.model.configuration.AppConfiguration; | ||
import io.jans.as.model.token.JsonWebResponse; | ||
import io.jans.as.server.model.audit.OAuth2AuditLog; | ||
import io.jans.as.server.model.common.*; | ||
import io.jans.as.server.model.token.HandleTokenFactory; | ||
import io.jans.as.server.service.SessionIdService; | ||
import io.jans.as.server.service.external.ExternalUpdateTokenService; | ||
import io.jans.as.server.service.external.context.ExternalUpdateTokenContext; | ||
import jakarta.ejb.Stateless; | ||
import jakarta.inject.Inject; | ||
import jakarta.inject.Named; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.apache.commons.lang3.BooleanUtils; | ||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
import org.slf4j.Logger; | ||
|
||
import java.util.List; | ||
import java.util.function.Function; | ||
|
||
import static com.google.common.base.Preconditions.checkNotNull; | ||
import static io.jans.as.model.config.Constants.OPENID; | ||
import static io.jans.as.model.config.Constants.TOKEN_TYPE_ACCESS_TOKEN; | ||
import static org.apache.commons.lang.BooleanUtils.isTrue; | ||
|
||
/** | ||
* @author Yuriy Z | ||
*/ | ||
@Stateless | ||
@Named | ||
public class TokenExchangeService { | ||
|
||
@Inject | ||
private Logger log; | ||
|
||
@Inject | ||
private AppConfiguration appConfiguration; | ||
|
||
@Inject | ||
private SessionIdService sessionIdService; | ||
|
||
@Inject | ||
private TokenRestWebServiceValidator tokenRestWebServiceValidator; | ||
|
||
@Inject | ||
private AuthorizationGrantList authorizationGrantList; | ||
|
||
@Inject | ||
private ExternalUpdateTokenService externalUpdateTokenService; | ||
|
||
@Inject | ||
private TokenCreatorService tokenCreatorService; | ||
|
||
@Inject | ||
private AttributeService attributeService; | ||
|
||
public String rotateDeviceSecret(SessionId sessionId, String actorToken) { | ||
if (BooleanUtils.isFalse(appConfiguration.getRotateDeviceSecret())) { | ||
return null; | ||
} | ||
|
||
String newDeviceSecret = HandleTokenFactory.generateDeviceSecret(); | ||
|
||
final List<String> deviceSecrets = sessionId.getDeviceSecrets(); | ||
deviceSecrets.remove(actorToken); | ||
deviceSecrets.add(newDeviceSecret); | ||
|
||
sessionIdService.updateSessionId(sessionId, false); | ||
|
||
return newDeviceSecret; | ||
} | ||
|
||
public JSONObject processTokenExchange(String scope, Function<JsonWebResponse, Void> idTokenPreProcessing, ExecutionContext executionContext) { | ||
final HttpServletRequest httpRequest = executionContext.getHttpRequest(); | ||
final Client client = executionContext.getClient(); | ||
final OAuth2AuditLog auditLog = executionContext.getAuditLog(); | ||
|
||
final String audience = httpRequest.getParameter("audience"); | ||
final String subjectToken = httpRequest.getParameter("subject_token"); | ||
final String subjectTokenType = httpRequest.getParameter("subject_token_type"); | ||
final String actorToken = httpRequest.getParameter("actor_token"); | ||
final String actorTokenType = httpRequest.getParameter("actor_token_type"); | ||
|
||
tokenRestWebServiceValidator.validateAudience(audience, auditLog); | ||
tokenRestWebServiceValidator.validateSubjectTokenType(subjectTokenType, auditLog); | ||
tokenRestWebServiceValidator.validateActorTokenType(actorTokenType, auditLog); | ||
tokenRestWebServiceValidator.validateActorToken(actorToken, auditLog); | ||
|
||
final SessionId sessionId = sessionIdService.getSessionByDeviceSecret(actorToken); | ||
tokenRestWebServiceValidator.validateSessionForTokenExchange(sessionId, actorToken, auditLog); | ||
checkNotNull(sessionId); // it checked by validator, added it only to relax static bugs checker | ||
|
||
tokenRestWebServiceValidator.validateSubjectToken(subjectToken, sessionId, auditLog); | ||
|
||
TokenExchangeGrant tokenExchangeGrant = authorizationGrantList.createTokenExchangeGrant(new User(), client); | ||
tokenExchangeGrant.setSessionDn(sessionId.getDn()); | ||
|
||
executionContext.setGrant(tokenExchangeGrant); | ||
|
||
scope = tokenExchangeGrant.checkScopesPolicy(scope); | ||
|
||
AccessToken accessToken = tokenExchangeGrant.createAccessToken(executionContext); // create token after scopes are checked | ||
|
||
IdToken idToken = null; | ||
if (isTrue(appConfiguration.getOpenidScopeBackwardCompatibility()) && tokenExchangeGrant.getScopes().contains(OPENID)) { | ||
boolean includeIdTokenClaims = Boolean.TRUE.equals( | ||
appConfiguration.getLegacyIdTokenClaims()); | ||
|
||
ExternalUpdateTokenContext context = new ExternalUpdateTokenContext(httpRequest, tokenExchangeGrant, client, appConfiguration, attributeService); | ||
|
||
executionContext.setIncludeIdTokenClaims(includeIdTokenClaims); | ||
executionContext.setPreProcessing(idTokenPreProcessing); | ||
executionContext.setPostProcessor(externalUpdateTokenService.buildModifyIdTokenProcessor(context)); | ||
|
||
idToken = tokenExchangeGrant.createIdToken( | ||
null, null, null, null, null, executionContext); | ||
} | ||
|
||
RefreshToken reToken = tokenCreatorService.createRefreshToken(executionContext, scope); | ||
|
||
executionContext.getAuditLog().updateOAuth2AuditLog(tokenExchangeGrant, true); | ||
|
||
String rotatedDeviceSecret = rotateDeviceSecret(sessionId, actorToken); | ||
|
||
JSONObject jsonObj = new JSONObject(); | ||
try { | ||
TokenRestWebServiceImpl.fillJsonObject(jsonObj, accessToken, accessToken.getTokenType(), accessToken.getExpiresIn(), reToken, scope, idToken); | ||
jsonObj.put("issued_token_type", TOKEN_TYPE_ACCESS_TOKEN); | ||
if (StringUtils.isNotBlank(rotatedDeviceSecret)) { | ||
jsonObj.put("device_secret", rotatedDeviceSecret); | ||
} | ||
} catch (JSONException e) { | ||
log.error(e.getMessage(), e); | ||
} | ||
|
||
return jsonObj; | ||
} | ||
} |
46 changes: 0 additions & 46 deletions
46
...-auth-server/server/src/main/java/io/jans/as/server/token/ws/rs/TokenInternalService.java
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.