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:
Stian Thorgersen 2014-03-15 10:15:10 +00:00
parent 2ebc32793a
commit f9aaa16cfe
19 changed files with 244 additions and 52 deletions

View file

@ -128,12 +128,12 @@ public class AccessToken extends IDToken {
} }
@Override @Override
public AccessToken expiration(long expiration) { public AccessToken expiration(int expiration) {
return (AccessToken) super.expiration(expiration); return (AccessToken) super.expiration(expiration);
} }
@Override @Override
public AccessToken notBefore(long notBefore) { public AccessToken notBefore(int notBefore) {
return (AccessToken) super.notBefore(notBefore); return (AccessToken) super.notBefore(notBefore);
} }

View file

@ -2,6 +2,7 @@ package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.annotate.JsonProperty;
import org.keycloak.util.Time;
import java.io.Serializable; import java.io.Serializable;
@ -13,9 +14,9 @@ public class JsonWebToken implements Serializable {
@JsonProperty("jti") @JsonProperty("jti")
protected String id; protected String id;
@JsonProperty("exp") @JsonProperty("exp")
protected long expiration; protected int expiration;
@JsonProperty("nbf") @JsonProperty("nbf")
protected long notBefore; protected int notBefore;
@JsonProperty("iat") @JsonProperty("iat")
protected int issuedAt; protected int issuedAt;
@JsonProperty("iss") @JsonProperty("iss")
@ -39,26 +40,25 @@ public class JsonWebToken implements Serializable {
} }
public long getExpiration() { public int getExpiration() {
return expiration; return expiration;
} }
public JsonWebToken expiration(long expiration) { public JsonWebToken expiration(int expiration) {
this.expiration = expiration; this.expiration = expiration;
return this; return this;
} }
@JsonIgnore @JsonIgnore
public boolean isExpired() { public boolean isExpired() {
long time = System.currentTimeMillis() / 1000; return Time.currentTime() > expiration;
return time > expiration;
} }
public long getNotBefore() { public int getNotBefore() {
return notBefore; return notBefore;
} }
public JsonWebToken notBefore(long notBefore) { public JsonWebToken notBefore(int notBefore) {
this.notBefore = notBefore; this.notBefore = notBefore;
return this; return this;
} }
@ -66,7 +66,7 @@ public class JsonWebToken implements Serializable {
@JsonIgnore @JsonIgnore
public boolean isNotBefore() { public boolean isNotBefore() {
return (System.currentTimeMillis() / 1000) >= notBefore; return Time.currentTime() >= notBefore;
} }
@ -89,7 +89,7 @@ public class JsonWebToken implements Serializable {
*/ */
@JsonIgnore @JsonIgnore
public JsonWebToken issuedNow() { public JsonWebToken issuedNow() {
issuedAt = (int)(System.currentTimeMillis() / 1000); issuedAt = Time.currentTime();
return this; return this;
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.representations.adapters.action; package org.keycloak.representations.adapters.action;
import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonIgnore;
import org.keycloak.util.Time;
/** /**
* Posted to managed client from admin server. * Posted to managed client from admin server.
@ -34,8 +35,7 @@ public abstract class AdminAction {
@JsonIgnore @JsonIgnore
public boolean isExpired() { public boolean isExpired() {
long time = System.currentTimeMillis() / 1000; return Time.currentTime() > expiration;
return time > expiration;
} }
/** /**

View 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);
}
}

View file

@ -9,6 +9,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.util.Time;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import java.io.IOException; import java.io.IOException;
@ -145,7 +146,7 @@ public class RSAVerifierTest {
@Test @Test
public void testNotBeforeGood() throws Exception { public void testNotBeforeGood() throws Exception {
token.notBefore((System.currentTimeMillis() / 1000) - 100); token.notBefore(Time.currentTime() - 100);
String encoded = new JWSBuilder() String encoded = new JWSBuilder()
.jsonContent(token) .jsonContent(token)
@ -161,7 +162,7 @@ public class RSAVerifierTest {
@Test @Test
public void testNotBeforeBad() throws Exception { public void testNotBeforeBad() throws Exception {
token.notBefore((System.currentTimeMillis() / 1000) + 100); token.notBefore(Time.currentTime() + 100);
String encoded = new JWSBuilder() String encoded = new JWSBuilder()
.jsonContent(token) .jsonContent(token)
@ -178,7 +179,7 @@ public class RSAVerifierTest {
@Test @Test
public void testExpirationGood() throws Exception { public void testExpirationGood() throws Exception {
token.expiration((System.currentTimeMillis() / 1000) + 100); token.expiration(Time.currentTime() + 100);
String encoded = new JWSBuilder() String encoded = new JWSBuilder()
.jsonContent(token) .jsonContent(token)
@ -194,7 +195,7 @@ public class RSAVerifierTest {
@Test @Test
public void testExpirationBad() throws Exception { public void testExpirationBad() throws Exception {
token.expiration((System.currentTimeMillis() / 1000) - 100); token.expiration(Time.currentTime() - 100);
String encoded = new JWSBuilder() String encoded = new JWSBuilder()
.jsonContent(token) .jsonContent(token)

View file

@ -229,6 +229,13 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.11</version> <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>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
@ -310,6 +317,7 @@
<groupId>org.seleniumhq.selenium</groupId> <groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId> <artifactId>selenium-chrome-driver</artifactId>
<version>2.35.0</version> <version>2.35.0</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>

View file

@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.util.Time;
import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
@ -25,7 +26,7 @@ public class AccessCodeEntry {
protected String redirectUri; protected String redirectUri;
protected boolean rememberMe; protected boolean rememberMe;
protected long expiration; protected int expiration;
protected RealmModel realm; protected RealmModel realm;
protected AccessToken token; protected AccessToken token;
protected UserModel user; protected UserModel user;
@ -35,7 +36,7 @@ public class AccessCodeEntry {
MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedHashMap<String, RoleModel>(); MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedHashMap<String, RoleModel>();
public boolean isExpired() { public boolean isExpired() {
return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration; return expiration != 0 && Time.currentTime() > expiration;
} }
public String getId() { public String getId() {
@ -58,11 +59,11 @@ public class AccessCodeEntry {
this.code = code; this.code = code;
} }
public long getExpiration() { public int getExpiration() {
return expiration; return expiration;
} }
public void setExpiration(long expiration) { public void setExpiration(int expiration) {
this.expiration = expiration; this.expiration = expiration;
} }

View file

@ -16,6 +16,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
import org.keycloak.util.Time;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
@ -47,7 +48,7 @@ public class AuthenticationManager {
token.subject(user.getId()); token.subject(user.getId());
token.audience(realm.getName()); token.audience(realm.getName());
if (realm.getCentralLoginLifespan() > 0) { if (realm.getCentralLoginLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getCentralLoginLifespan()); token.expiration(Time.currentTime() + realm.getCentralLoginLifespan());
} }
return token; return token;
} }

View file

@ -14,6 +14,7 @@ import org.keycloak.representations.adapters.action.SessionStats;
import org.keycloak.representations.adapters.action.SessionStatsAction; import org.keycloak.representations.adapters.action.SessionStatsAction;
import org.keycloak.representations.adapters.action.UserStats; import org.keycloak.representations.adapters.action.UserStats;
import org.keycloak.representations.adapters.action.UserStatsAction; import org.keycloak.representations.adapters.action.UserStatsAction;
import org.keycloak.util.Time;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -44,7 +45,7 @@ public class ResourceAdminManager {
public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users, ResteasyClient client) { public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users, ResteasyClient client) {
String managementUrl = application.getManagementUrl(); String managementUrl = application.getManagementUrl();
if (managementUrl != null) { 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); adminAction.setListUsers(users);
String token = new TokenManager().encodeToken(realm, adminAction); String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl); 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) { public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user, ResteasyClient client) {
String managementUrl = application.getManagementUrl(); String managementUrl = application.getManagementUrl();
if (managementUrl != null) { 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); String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("session stats for application: {0} url: {1}", application.getName(), managementUrl); 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)); Response response = client.target(managementUrl).path(AdapterConstants.K_GET_USER_STATS).request().post(Entity.text(token));
@ -130,7 +131,7 @@ public class ResourceAdminManager {
.build(); .build();
try { try {
realm.setNotBefore((int)(System.currentTimeMillis()/1000)); realm.setNotBefore(Time.currentTime());
List<ApplicationModel> resources = realm.getApplications(); List<ApplicationModel> resources = realm.getApplications();
logger.debug("logging out {0} resources ", resources.size()); logger.debug("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) { for (ApplicationModel resource : resources) {
@ -147,7 +148,7 @@ public class ResourceAdminManager {
.build(); .build();
try { try {
resource.setNotBefore((int)(System.currentTimeMillis()/1000)); resource.setNotBefore(Time.currentTime());
logoutApplication(realm, resource, user, client, resource.getNotBefore()); logoutApplication(realm, resource, user, client, resource.getNotBefore());
} finally { } finally {
client.close(); client.close();
@ -159,7 +160,7 @@ public class ResourceAdminManager {
protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) { protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) {
String managementUrl = resource.getManagementUrl(); String managementUrl = resource.getManagementUrl();
if (managementUrl != null) { 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); String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl); 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)); 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; if (notBefore <= 0) return false;
String managementUrl = resource.getManagementUrl(); String managementUrl = resource.getManagementUrl();
if (managementUrl != null) { 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); String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("pushRevocation resource: {0} url: {1}", resource.getName(), managementUrl); 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)); Response response = client.target(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).request().post(Entity.text(token));

View file

@ -16,6 +16,7 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.util.Time;
import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
@ -82,7 +83,7 @@ public class TokenManager {
code.setToken(token); code.setToken(token);
code.setRealm(realm); code.setRealm(realm);
code.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan()); code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
code.setClient(client); code.setClient(client);
code.setUser(user); code.setUser(user);
code.setState(state); code.setState(state);
@ -158,7 +159,7 @@ public class TokenManager {
if (app == null) { if (app == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName()); 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); RoleModel role = app.getRole(roleName);
if (role == null) { if (role == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown application role: " + roleName); 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.issuedFor(client.getLoginName());
token.issuer(realm.getName()); token.issuer(realm.getName());
if (realm.getAccessTokenLifespan() > 0) { if (realm.getAccessTokenLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan()); token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
} }
initClaims(token, claimer, user); initClaims(token, claimer, user);
return token; return token;
@ -274,7 +275,7 @@ public class TokenManager {
token.issuedFor(client.getClientId()); token.issuedFor(client.getClientId());
token.issuer(realm.getName()); token.issuer(realm.getName());
if (realm.getAccessTokenLifespan() > 0) { if (realm.getAccessTokenLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan()); token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
} }
Set<String> allowedOrigins = client.getWebOrigins(); Set<String> allowedOrigins = client.getWebOrigins();
if (allowedOrigins != null) { if (allowedOrigins != null) {
@ -356,7 +357,7 @@ public class TokenManager {
refreshToken = new RefreshToken(accessToken); refreshToken = new RefreshToken(accessToken);
refreshToken.id(KeycloakModelUtils.generateId()); refreshToken.id(KeycloakModelUtils.generateId());
refreshToken.issuedNow(); refreshToken.issuedNow();
refreshToken.expiration((System.currentTimeMillis() / 1000) + realm.getRefreshTokenLifespan()); refreshToken.expiration(Time.currentTime() + realm.getRefreshTokenLifespan());
return this; return this;
} }
@ -372,7 +373,7 @@ public class TokenManager {
idToken.issuedFor(accessToken.getIssuedFor()); idToken.issuedFor(accessToken.getIssuedFor());
idToken.issuer(accessToken.getIssuer()); idToken.issuer(accessToken.getIssuer());
if (realm.getAccessTokenLifespan() > 0) { if (realm.getAccessTokenLifespan() > 0) {
idToken.expiration((System.currentTimeMillis() / 1000) + realm.getAccessTokenLifespan()); idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
} }
idToken.setPreferredUsername(accessToken.getPreferredUsername()); idToken.setPreferredUsername(accessToken.getPreferredUsername());
idToken.setGivenName(accessToken.getGivenName()); idToken.setGivenName(accessToken.getGivenName());
@ -412,8 +413,7 @@ public class TokenManager {
res.setToken(encodedToken); res.setToken(encodedToken);
res.setTokenType("bearer"); res.setTokenType("bearer");
if (accessToken.getExpiration() != 0) { if (accessToken.getExpiration() != 0) {
long time = accessToken.getExpiration() - (System.currentTimeMillis() / 1000); res.setExpiresIn(accessToken.getExpiration() - Time.currentTime());
res.setExpiresIn(time);
} }
} }
if (refreshToken != null) { if (refreshToken != null) {

View file

@ -41,6 +41,7 @@ import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.util.Time;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
@ -268,7 +269,7 @@ public class RequiredActionsService {
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user); AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
accessCode.setRequiredActions(requiredActions); accessCode.setRequiredActions(requiredActions);
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction()); accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
try { try {
new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo); new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
@ -312,7 +313,7 @@ public class RequiredActionsService {
if (accessCodeEntry.isExpired()) { if (accessCodeEntry.isExpired()) {
logger.debug("getAccessCodeEntry: access code id: {0}", accessCodeEntry.getId()); logger.debug("getAccessCodeEntry: access code id: {0}", accessCodeEntry.getId());
logger.debug("getAccessCodeEntry access code entry expired: {0}", accessCodeEntry.getExpiration()); 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; return null;
} }
@ -339,7 +340,7 @@ public class RequiredActionsService {
.createResponse(requiredActions.iterator().next()); .createResponse(requiredActions.iterator().next());
} else { } else {
logger.debug("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri()); 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, return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
accessCode.getState(), accessCode.getRedirectUri()); accessCode.getState(), accessCode.getRedirectUri());
} }

View file

@ -28,6 +28,7 @@ import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows; import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.Time;
import javax.ws.rs.BadRequestException; import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -622,7 +623,7 @@ public class TokenService {
return redirectAccessDenied(redirect, state); return redirectAccessDenied(redirect, state);
} }
accessCodeEntry.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan()); accessCodeEntry.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
return oauth.redirectAccessCode(accessCodeEntry, state, redirect); return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
} }

View file

@ -25,6 +25,7 @@ import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.Urls; import org.keycloak.services.resources.flows.Urls;
import org.keycloak.util.Time;
import javax.ws.rs.BadRequestException; import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
@ -181,7 +182,7 @@ public class UsersResource {
throw new NotFoundException(); throw new NotFoundException();
} }
// set notBefore so that user will be forced to log in. // 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); new ResourceAdminManager().logoutUser(realm, user);
} }
@ -514,7 +515,7 @@ public class UsersResource {
AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, realm, client, user); AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, realm, client, user);
accessCode.setRequiredActions(requiredActions); accessCode.setRequiredActions(requiredActions);
accessCode.setExpiration(System.currentTimeMillis() / 1000 + realm.getAccessCodeLifespanUserAction()); accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
try { try {
new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo); new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);

View file

@ -37,6 +37,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.TokenService; import org.keycloak.services.resources.TokenService;
import org.keycloak.util.Time;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Cookie;
@ -127,14 +128,14 @@ public class OAuthFlows {
Set<RequiredAction> requiredActions = user.getRequiredActions(); Set<RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) { if (!requiredActions.isEmpty()) {
accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions)); 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) return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
.createResponse(user.getRequiredActions().iterator().next()); .createResponse(user.getRequiredActions().iterator().next());
} }
if (!isResource if (!isResource
&& (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested().size() > 0)) { && (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()). return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).
setAccessRequest(accessCode.getRealmRolesRequested(), accessCode.getResourceRolesRequested()). setAccessRequest(accessCode.getRealmRolesRequested(), accessCode.getResourceRolesRequested()).
setClient(client).createOAuthGrant(); setClient(client).createOAuthGrant();

View file

@ -236,6 +236,10 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.hibernate.javax.persistence</groupId> <groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId> <artifactId>hibernate-jpa-2.0-api</artifactId>

View file

@ -38,6 +38,7 @@ import org.keycloak.VerificationException;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; 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) { public AccessToken verifyToken(String token) {
try { try {
return RSATokenVerifier.verifyToken(token, realmPublicKey, realm); 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() { public String getClientId() {
return clientId; return clientId;
} }
@ -218,6 +265,11 @@ public class OAuthClient {
return b.build().toString(); 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) { public OAuthClient realm(String realm) {
this.realm = realm; this.realm = realm;
return this; return this;

View file

@ -25,10 +25,7 @@ import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken; 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;
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse; import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
@ -37,6 +34,10 @@ import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule; import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver; 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> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@ -66,7 +67,7 @@ public class AccessTokenTest {
Assert.assertEquals(200, response.getStatusCode()); 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()); Assert.assertEquals("bearer", response.getTokenType());

View file

@ -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"));
}
}

View file

@ -2,9 +2,6 @@
"id": "test", "id": "test",
"realm": "test", "realm": "test",
"enabled": true, "enabled": true,
"accessTokenLifespan": 600,
"accessCodeLifespan": 600,
"accessCodeLifespanUserAction": 600,
"sslNotRequired": true, "sslNotRequired": true,
"registrationAllowed": true, "registrationAllowed": true,
"resetPasswordAllowed": true, "resetPasswordAllowed": true,