KEYCLOAK-378 KEYCLOAK-379 KEYCLOAK-381 Fix refresh token if token contains app roles. Changed long time fields in AccessCode and AccessToken to int
This commit is contained in:
parent
2ebc32793a
commit
f9aaa16cfe
19 changed files with 244 additions and 52 deletions
|
@ -128,12 +128,12 @@ public class AccessToken extends IDToken {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccessToken expiration(long expiration) {
|
||||
public AccessToken expiration(int expiration) {
|
||||
return (AccessToken) super.expiration(expiration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken notBefore(long notBefore) {
|
||||
public AccessToken notBefore(int notBefore) {
|
||||
return (AccessToken) super.notBefore(notBefore);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.representations;
|
|||
|
||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
@ -13,9 +14,9 @@ public class JsonWebToken implements Serializable {
|
|||
@JsonProperty("jti")
|
||||
protected String id;
|
||||
@JsonProperty("exp")
|
||||
protected long expiration;
|
||||
protected int expiration;
|
||||
@JsonProperty("nbf")
|
||||
protected long notBefore;
|
||||
protected int notBefore;
|
||||
@JsonProperty("iat")
|
||||
protected int issuedAt;
|
||||
@JsonProperty("iss")
|
||||
|
@ -39,26 +40,25 @@ public class JsonWebToken implements Serializable {
|
|||
}
|
||||
|
||||
|
||||
public long getExpiration() {
|
||||
public int getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public JsonWebToken expiration(long expiration) {
|
||||
public JsonWebToken expiration(int expiration) {
|
||||
this.expiration = expiration;
|
||||
return this;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isExpired() {
|
||||
long time = System.currentTimeMillis() / 1000;
|
||||
return time > expiration;
|
||||
return Time.currentTime() > expiration;
|
||||
}
|
||||
|
||||
public long getNotBefore() {
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public JsonWebToken notBefore(long notBefore) {
|
||||
public JsonWebToken notBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
return this;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ public class JsonWebToken implements Serializable {
|
|||
|
||||
@JsonIgnore
|
||||
public boolean isNotBefore() {
|
||||
return (System.currentTimeMillis() / 1000) >= notBefore;
|
||||
return Time.currentTime() >= notBefore;
|
||||
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ public class JsonWebToken implements Serializable {
|
|||
*/
|
||||
@JsonIgnore
|
||||
public JsonWebToken issuedNow() {
|
||||
issuedAt = (int)(System.currentTimeMillis() / 1000);
|
||||
issuedAt = Time.currentTime();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.representations.adapters.action;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
/**
|
||||
* Posted to managed client from admin server.
|
||||
|
@ -34,8 +35,7 @@ public abstract class AdminAction {
|
|||
|
||||
@JsonIgnore
|
||||
public boolean isExpired() {
|
||||
long time = System.currentTimeMillis() / 1000;
|
||||
return time > expiration;
|
||||
return Time.currentTime() > expiration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
12
core/src/main/java/org/keycloak/util/Time.java
Normal file
12
core/src/main/java/org/keycloak/util/Time.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package org.keycloak.util;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Time {
|
||||
|
||||
public static int currentTime() {
|
||||
return (int) (System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ import org.junit.BeforeClass;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.io.IOException;
|
||||
|
@ -145,7 +146,7 @@ public class RSAVerifierTest {
|
|||
|
||||
@Test
|
||||
public void testNotBeforeGood() throws Exception {
|
||||
token.notBefore((System.currentTimeMillis() / 1000) - 100);
|
||||
token.notBefore(Time.currentTime() - 100);
|
||||
|
||||
String encoded = new JWSBuilder()
|
||||
.jsonContent(token)
|
||||
|
@ -161,7 +162,7 @@ public class RSAVerifierTest {
|
|||
|
||||
@Test
|
||||
public void testNotBeforeBad() throws Exception {
|
||||
token.notBefore((System.currentTimeMillis() / 1000) + 100);
|
||||
token.notBefore(Time.currentTime() + 100);
|
||||
|
||||
String encoded = new JWSBuilder()
|
||||
.jsonContent(token)
|
||||
|
@ -178,7 +179,7 @@ public class RSAVerifierTest {
|
|||
|
||||
@Test
|
||||
public void testExpirationGood() throws Exception {
|
||||
token.expiration((System.currentTimeMillis() / 1000) + 100);
|
||||
token.expiration(Time.currentTime() + 100);
|
||||
|
||||
String encoded = new JWSBuilder()
|
||||
.jsonContent(token)
|
||||
|
@ -194,7 +195,7 @@ public class RSAVerifierTest {
|
|||
|
||||
@Test
|
||||
public void testExpirationBad() throws Exception {
|
||||
token.expiration((System.currentTimeMillis() / 1000) - 100);
|
||||
token.expiration(Time.currentTime() - 100);
|
||||
|
||||
String encoded = new JWSBuilder()
|
||||
.jsonContent(token)
|
||||
|
|
8
pom.xml
8
pom.xml
|
@ -229,6 +229,13 @@
|
|||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.javax.persistence</groupId>
|
||||
|
@ -310,6 +317,7 @@
|
|||
<groupId>org.seleniumhq.selenium</groupId>
|
||||
<artifactId>selenium-chrome-driver</artifactId>
|
||||
<version>2.35.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
@ -25,7 +26,7 @@ public class AccessCodeEntry {
|
|||
protected String redirectUri;
|
||||
protected boolean rememberMe;
|
||||
|
||||
protected long expiration;
|
||||
protected int expiration;
|
||||
protected RealmModel realm;
|
||||
protected AccessToken token;
|
||||
protected UserModel user;
|
||||
|
@ -35,7 +36,7 @@ public class AccessCodeEntry {
|
|||
MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedHashMap<String, RoleModel>();
|
||||
|
||||
public boolean isExpired() {
|
||||
return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration;
|
||||
return expiration != 0 && Time.currentTime() > expiration;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
@ -58,11 +59,11 @@ public class AccessCodeEntry {
|
|||
this.code = code;
|
||||
}
|
||||
|
||||
public long getExpiration() {
|
||||
public int getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(long expiration) {
|
||||
public void setExpiration(int expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
|||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
@ -47,7 +48,7 @@ public class AuthenticationManager {
|
|||
token.subject(user.getId());
|
||||
token.audience(realm.getName());
|
||||
if (realm.getCentralLoginLifespan() > 0) {
|
||||
token.expiration((System.currentTimeMillis() / 1000) + realm.getCentralLoginLifespan());
|
||||
token.expiration(Time.currentTime() + realm.getCentralLoginLifespan());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.keycloak.representations.adapters.action.SessionStats;
|
|||
import org.keycloak.representations.adapters.action.SessionStatsAction;
|
||||
import org.keycloak.representations.adapters.action.UserStats;
|
||||
import org.keycloak.representations.adapters.action.UserStatsAction;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -44,7 +45,7 @@ public class ResourceAdminManager {
|
|||
public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users, ResteasyClient client) {
|
||||
String managementUrl = application.getManagementUrl();
|
||||
if (managementUrl != null) {
|
||||
SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, application.getName());
|
||||
SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName());
|
||||
adminAction.setListUsers(users);
|
||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||
logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl);
|
||||
|
@ -91,7 +92,7 @@ public class ResourceAdminManager {
|
|||
public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user, ResteasyClient client) {
|
||||
String managementUrl = application.getManagementUrl();
|
||||
if (managementUrl != null) {
|
||||
UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, application.getName(), user.getId());
|
||||
UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName(), user.getId());
|
||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||
logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl);
|
||||
Response response = client.target(managementUrl).path(AdapterConstants.K_GET_USER_STATS).request().post(Entity.text(token));
|
||||
|
@ -130,7 +131,7 @@ public class ResourceAdminManager {
|
|||
.build();
|
||||
|
||||
try {
|
||||
realm.setNotBefore((int)(System.currentTimeMillis()/1000));
|
||||
realm.setNotBefore(Time.currentTime());
|
||||
List<ApplicationModel> resources = realm.getApplications();
|
||||
logger.debug("logging out {0} resources ", resources.size());
|
||||
for (ApplicationModel resource : resources) {
|
||||
|
@ -147,7 +148,7 @@ public class ResourceAdminManager {
|
|||
.build();
|
||||
|
||||
try {
|
||||
resource.setNotBefore((int)(System.currentTimeMillis()/1000));
|
||||
resource.setNotBefore(Time.currentTime());
|
||||
logoutApplication(realm, resource, user, client, resource.getNotBefore());
|
||||
} finally {
|
||||
client.close();
|
||||
|
@ -159,7 +160,7 @@ public class ResourceAdminManager {
|
|||
protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) {
|
||||
String managementUrl = resource.getManagementUrl();
|
||||
if (managementUrl != null) {
|
||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user, notBefore);
|
||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, notBefore);
|
||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||
logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
|
||||
Response response = client.target(managementUrl).path(AdapterConstants.K_LOGOUT).request().post(Entity.text(token));
|
||||
|
@ -204,7 +205,7 @@ public class ResourceAdminManager {
|
|||
if (notBefore <= 0) return false;
|
||||
String managementUrl = resource.getManagementUrl();
|
||||
if (managementUrl != null) {
|
||||
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), notBefore);
|
||||
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), notBefore);
|
||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||
logger.info("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl);
|
||||
Response response = client.target(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).request().post(Entity.text(token));
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.keycloak.representations.AccessToken;
|
|||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedHashMap;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
@ -82,7 +83,7 @@ public class TokenManager {
|
|||
|
||||
code.setToken(token);
|
||||
code.setRealm(realm);
|
||||
code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
||||
code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
code.setClient(client);
|
||||
code.setUser(user);
|
||||
code.setState(state);
|
||||
|
@ -158,7 +159,7 @@ public class TokenManager {
|
|||
if (app == null) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName());
|
||||
}
|
||||
for (String roleName : refreshToken.getRealmAccess().getRoles()) {
|
||||
for (String roleName : entry.getValue().getRoles()) {
|
||||
RoleModel role = app.getRole(roleName);
|
||||
if (role == null) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName);
|
||||
|
@ -257,7 +258,7 @@ public class TokenManager {
|
|||
token.issuedFor(client.getLoginName());
|
||||
token.issuer(realm.getName());
|
||||
if (realm.getAccessTokenLifespan() > 0) {
|
||||
token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
|
||||
token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
|
||||
}
|
||||
initClaims(token, claimer, user);
|
||||
return token;
|
||||
|
@ -274,7 +275,7 @@ public class TokenManager {
|
|||
token.issuedFor(client.getClientId());
|
||||
token.issuer(realm.getName());
|
||||
if (realm.getAccessTokenLifespan() > 0) {
|
||||
token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
|
||||
token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
|
||||
}
|
||||
Set<String> allowedOrigins = client.getWebOrigins();
|
||||
if (allowedOrigins != null) {
|
||||
|
@ -356,7 +357,7 @@ public class TokenManager {
|
|||
refreshToken = new RefreshToken(accessToken);
|
||||
refreshToken.id(KeycloakModelUtils.generateId());
|
||||
refreshToken.issuedNow();
|
||||
refreshToken.expiration((System.currentTimeMillis() / 1000) + realm.getRefreshTokenLifespan());
|
||||
refreshToken.expiration(Time.currentTime() + realm.getRefreshTokenLifespan());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -372,7 +373,7 @@ public class TokenManager {
|
|||
idToken.issuedFor(accessToken.getIssuedFor());
|
||||
idToken.issuer(accessToken.getIssuer());
|
||||
if (realm.getAccessTokenLifespan() > 0) {
|
||||
idToken.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan());
|
||||
idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
|
||||
}
|
||||
idToken.setPreferredUsername(accessToken.getPreferredUsername());
|
||||
idToken.setGivenName(accessToken.getGivenName());
|
||||
|
@ -412,8 +413,7 @@ public class TokenManager {
|
|||
res.setToken(encodedToken);
|
||||
res.setTokenType("bearer");
|
||||
if (accessToken.getExpiration() != 0) {
|
||||
long time = accessToken.getExpiration() - (System.currentTimeMillis() / 1000);
|
||||
res.setExpiresIn(time);
|
||||
res.setExpiresIn(accessToken.getExpiration() - Time.currentTime());
|
||||
}
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.services.managers.TokenManager;
|
|||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
|
@ -268,7 +269,7 @@ public class RequiredActionsService {
|
|||
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
|
||||
accessCode.setRequiredActions(requiredActions);
|
||||
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
|
||||
try {
|
||||
new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
|
||||
|
@ -312,7 +313,7 @@ public class RequiredActionsService {
|
|||
if (accessCodeEntry.isExpired()) {
|
||||
logger.debug("getAccessCodeEntry: access code id: {0}", accessCodeEntry.getId());
|
||||
logger.debug("getAccessCodeEntry access code entry expired: {0}", accessCodeEntry.getExpiration());
|
||||
logger.debug("getAccessCodeEntry current time: {0}", (System.currentTimeMillis() / 1000));
|
||||
logger.debug("getAccessCodeEntry current time: {0}", Time.currentTime());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -339,7 +340,7 @@ public class RequiredActionsService {
|
|||
.createResponse(requiredActions.iterator().next());
|
||||
} else {
|
||||
logger.debug("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
|
||||
accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
|
||||
accessCode.getState(), accessCode.getRedirectUri());
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.services.resources.flows.Flows;
|
|||
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.Consumes;
|
||||
|
@ -622,7 +623,7 @@ public class TokenService {
|
|||
return redirectAccessDenied(redirect, state);
|
||||
}
|
||||
|
||||
accessCodeEntry.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan());
|
||||
accessCodeEntry.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.services.managers.ResourceAdminManager;
|
|||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.Consumes;
|
||||
|
@ -181,7 +182,7 @@ public class UsersResource {
|
|||
throw new NotFoundException();
|
||||
}
|
||||
// set notBefore so that user will be forced to log in.
|
||||
user.setNotBefore((int) (System.currentTimeMillis() / 1000));
|
||||
user.setNotBefore(Time.currentTime());
|
||||
new ResourceAdminManager().logoutUser(realm, user);
|
||||
}
|
||||
|
||||
|
@ -514,7 +515,7 @@ public class UsersResource {
|
|||
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, realm, client, user);
|
||||
accessCode.setRequiredActions(requiredActions);
|
||||
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
|
||||
try {
|
||||
new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
|
|||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.resources.TokenService;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
|
@ -127,14 +128,14 @@ public class OAuthFlows {
|
|||
Set<RequiredAction> requiredActions = user.getRequiredActions();
|
||||
if (!requiredActions.isEmpty()) {
|
||||
accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
|
||||
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
|
||||
.createResponse(user.getRequiredActions().iterator().next());
|
||||
}
|
||||
|
||||
if (!isResource
|
||||
&& (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) {
|
||||
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).
|
||||
setAccessRequest(accessCode.getRealmRolesRequested(), accessCode.getResourceRolesRequested()).
|
||||
setClient(client).createOAuthGrant();
|
||||
|
|
|
@ -236,6 +236,10 @@
|
|||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.javax.persistence</groupId>
|
||||
<artifactId>hibernate-jpa-2.0-api</artifactId>
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.keycloak.VerificationException;
|
|||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
@ -141,6 +142,40 @@ public class OAuthClient {
|
|||
}
|
||||
}
|
||||
|
||||
public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(getRefreshTokenUrl());
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||
if (grantType != null) {
|
||||
parameters.add(new BasicNameValuePair("grant_type", grantType));
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
parameters.add(new BasicNameValuePair("refresh_token", refreshToken));
|
||||
}
|
||||
if (clientId != null && password != null) {
|
||||
String authorization = BasicAuthHelper.createHeader(clientId, password);
|
||||
post.setHeader("Authorization", authorization);
|
||||
}
|
||||
else if (clientId != null) {
|
||||
parameters.add(new BasicNameValuePair("client_id", clientId));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity = null;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
post.setEntity(formEntity);
|
||||
|
||||
try {
|
||||
return new AccessTokenResponse(client.execute(post));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve access token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public AccessToken verifyToken(String token) {
|
||||
try {
|
||||
return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
|
||||
|
@ -155,6 +190,18 @@ public class OAuthClient {
|
|||
}
|
||||
}
|
||||
|
||||
public RefreshToken verifyRefreshToken(String refreshToken) {
|
||||
try {
|
||||
JWSInput jws = new JWSInput(refreshToken);
|
||||
if (!RSAProvider.verify(jws, realmPublicKey)) {
|
||||
throw new RuntimeException("Invalid refresh token");
|
||||
}
|
||||
return jws.readJsonContent(RefreshToken.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid refresh token", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
@ -218,6 +265,11 @@ public class OAuthClient {
|
|||
return b.build().toString();
|
||||
}
|
||||
|
||||
public String getRefreshTokenUrl() {
|
||||
UriBuilder b = UriBuilder.fromUri(baseUrl + "/realms/" + realm + "/tokens/refresh");
|
||||
return b.build().toString();
|
||||
}
|
||||
|
||||
public OAuthClient realm(String realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
|
|
|
@ -25,10 +25,7 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
@ -37,6 +34,10 @@ import org.keycloak.testsuite.rule.WebResource;
|
|||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -66,7 +67,7 @@ public class AccessTokenTest {
|
|||
|
||||
Assert.assertEquals(200, response.getStatusCode());
|
||||
|
||||
Assert.assertTrue(response.getExpiresIn() <= 600 && response.getExpiresIn() >= 550);
|
||||
Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
|
||||
Assert.assertEquals("bearer", response.getTokenType());
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags. See the copyright.txt file in the
|
||||
* distribution for a full listing of individual contributors.
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this software; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
*/
|
||||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.keycloak.util.Time;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class RefreshTokenTest {
|
||||
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Test
|
||||
public void refreshTokenRequest() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get("code");
|
||||
|
||||
AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
String refreshTokenString = tokenResponse.getRefreshToken();
|
||||
RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
|
||||
|
||||
Assert.assertNotNull(refreshTokenString);
|
||||
|
||||
Assert.assertEquals("bearer", tokenResponse.getTokenType());
|
||||
|
||||
Assert.assertThat(token.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
Assert.assertThat(refreshToken.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(35950), lessThanOrEqualTo(36000)));
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
AccessTokenResponse response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
|
||||
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
|
||||
RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
|
||||
|
||||
Assert.assertEquals(200, response.getStatusCode());
|
||||
|
||||
Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
Assert.assertThat(refreshedToken.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
|
||||
Assert.assertThat(refreshedToken.getExpiration() - token.getExpiration(), allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(3)));
|
||||
Assert.assertThat(refreshedRefreshToken.getExpiration() - refreshToken.getExpiration(), allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(3)));
|
||||
|
||||
Assert.assertNotEquals(token.getId(), refreshedToken.getId());
|
||||
Assert.assertNotEquals(refreshToken.getId(), refreshedRefreshToken.getId());
|
||||
|
||||
Assert.assertEquals("bearer", response.getTokenType());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "test-user@localhost").getId(), refreshedToken.getSubject());
|
||||
Assert.assertNotEquals("test-user@localhost", refreshedToken.getSubject());
|
||||
|
||||
Assert.assertEquals(1, refreshedToken.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
|
||||
|
||||
Assert.assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
Assert.assertTrue(refreshedToken.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
|
||||
}
|
||||
|
||||
}
|
|
@ -2,9 +2,6 @@
|
|||
"id": "test",
|
||||
"realm": "test",
|
||||
"enabled": true,
|
||||
"accessTokenLifespan": 600,
|
||||
"accessCodeLifespan": 600,
|
||||
"accessCodeLifespanUserAction": 600,
|
||||
"sslNotRequired": true,
|
||||
"registrationAllowed": true,
|
||||
"resetPasswordAllowed": true,
|
||||
|
|
Loading…
Reference in a new issue