Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ protected boolean verifyClient() {

try {
TokenVerifier.createWithoutSignature(token)
.withChecks(TokenManager.NotBeforeCheck.forModel(client), TokenVerifier.IS_ACTIVE, new TokenManager.TokenRevocationCheck(session))
.withChecks(TokenManager.NotBeforeCheck.forModel(realm), TokenManager.NotBeforeCheck.forModel(client), TokenVerifier.IS_ACTIVE, new TokenManager.TokenRevocationCheck(session))
.verify();
this.client = client;
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, C

try {
TokenVerifier.createWithoutSignature(oldToken)
.withChecks(NotBeforeCheck.forModel(client), NotBeforeCheck.forModel(session, realm, user))
.withChecks(NotBeforeCheck.forModel(realm), NotBeforeCheck.forModel(client), NotBeforeCheck.forModel(session, realm, user))
.verify();
} catch (VerificationException e) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
Expand Down Expand Up @@ -282,6 +282,10 @@ public static boolean isUserValid(KeycloakSession session, RealmModel realm, Acc
logger.debugf("User '%s' is disabled", user.getUsername());
return false;
}
return validateUserNotBefore(session, realm, token, user);
}

public static boolean validateUserNotBefore(KeycloakSession session, RealmModel realm, AccessToken token, UserModel user) {
try {
TokenVerifier.createWithoutSignature(token)
.withChecks(NotBeforeCheck.forModel(session ,realm, user))
Expand Down Expand Up @@ -514,7 +518,7 @@ public RefreshToken verifyRefreshToken(KeycloakSession session, RealmModel realm
.withChecks(new TokenVerifier.RealmUrlCheck(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())));

if (checkExpiration) {
tokenVerifier.withChecks(NotBeforeCheck.forModel(realm), TokenVerifier.IS_ACTIVE);
tokenVerifier.withChecks(NotBeforeCheck.forModel(realm), NotBeforeCheck.forModel(client), TokenVerifier.IS_ACTIVE);
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ private Response issueUserInfo() {
cors.checkAllowedOrigins(session, clientModel);

TokenVerifier.createWithoutSignature(token)
.withChecks(NotBeforeCheck.forModel(clientModel), new TokenManager.TokenRevocationCheck(session))
.withChecks(NotBeforeCheck.forModel(realm), NotBeforeCheck.forModel(clientModel), new TokenManager.TokenRevocationCheck(session))
.verify();
} catch (VerificationException e) {
if (clientModel == null) {
Expand Down Expand Up @@ -263,6 +263,11 @@ private Response issueUserInfo() {
throw error.invalidToken("User disabled");
}

if (!TokenManager.validateUserNotBefore(session, realm, token, userModel)) {
event.error(Errors.INVALID_USER);
throw error.invalidToken("User not valid");
}

// KEYCLOAK-6771 Certificate Bound Token
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3
if (OIDCAdvancedConfigWrapper.fromClientModel(clientModel).isUseMtlsHokToken()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,66 @@ public void tokenRefreshRequest_ClientPS512_RealmPS256() throws Exception {
conductTokenRefreshRequest(Constants.INTERNAL_SIGNATURE_ALGORITHM, Algorithm.PS512, Algorithm.PS256);
}

@Test
public void refreshTokenWithRealmNotBeforeAndClientNotBefore() {
//Test that refresh token respects realm notBefore even when client notBefore is set
oauth.doLogin("test-user@localhost", "password");

EventRepresentation loginEvent = events.poll();
EventAssertion.assertSuccess(loginEvent)
.userId(user.getId())
.clientId("test-app")
.hasSessionId()
.type(EventType.LOGIN);

String sessionId = loginEvent.getSessionId();

String code = oauth.parseLoginResponse().getCode();

AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code);
String refreshToken = tokenResponse.getRefreshToken();

EventAssertion.assertSuccess(events.poll())
.userId(user.getId())
.sessionId(sessionId)
.clientId("test-app")
.type(EventType.CODE_TO_TOKEN);

int currentTime = (int) (System.currentTimeMillis() / 1000);

ClientRepresentation clientRep = managedClient.admin().toRepresentation();
int originalClientNotBefore = clientRep.getNotBefore() != null ? clientRep.getNotBefore() : 0;

try {
//Test realm notBefore with client notBefore both set
// Set client notBefore to past
clientRep.setNotBefore(currentTime - 100);
managedClient.admin().update(clientRep);

tokenResponse = oauth.doRefreshTokenRequest(refreshToken);
assertEquals(200, tokenResponse.getStatusCode());

// Set realm notBefore to future
RealmRepresentation realmRep = realm.admin().toRepresentation();
int originalRealmNotBefore = realmRep.getNotBefore() != null ? realmRep.getNotBefore() : 0;
try {
realmRep.setNotBefore(currentTime + 100);
realm.admin().update(realmRep);

tokenResponse = oauth.doRefreshTokenRequest(refreshToken);
assertEquals(400, tokenResponse.getStatusCode());
assertEquals(OAuthErrorException.INVALID_GRANT, tokenResponse.getError());
} finally {
realmRep.setNotBefore(originalRealmNotBefore);
realm.admin().update(realmRep);
}

} finally {
clientRep.setNotBefore(originalClientNotBefore);
managedClient.admin().update(clientRep);
}
}

private void conductTokenRefreshRequest(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
try {
// Realm setting is used for ID Token signature algorithm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.AdminApiUtil;
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.TokenUtil;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
import org.keycloak.testsuite.util.userprofile.UserProfileUtil;
Expand Down Expand Up @@ -2085,6 +2086,62 @@ public void testEmailWhenUpdateEmailEnabled() throws Exception {
}
}

@Test
public void testAccountAccessWithRealmNotBeforeAndClientNotBefore() throws IOException {
String accessToken = tokenUtil.getToken();

int status = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(accessToken)
.acceptJson()
.asStatus();
assertEquals(200, status);

int currentTime = (int) (System.currentTimeMillis() / 1000);

org.keycloak.representations.idm.ClientRepresentation clientRep = AdminApiUtil.findClientByClientId(adminClient.realm("test"), "direct-grant").toRepresentation();
int originalClientNotBefore = clientRep.getNotBefore() != null ? clientRep.getNotBefore() : 0;

try {
clientRep.setNotBefore(currentTime + 100);
AdminApiUtil.findClientByClientId(managedRealm.admin(), "direct-grant").update(clientRep);

status = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(accessToken)
.acceptJson()
.asStatus();
assertEquals(401, status);

clientRep.setNotBefore(currentTime - 100);
AdminApiUtil.findClientByClientId(managedRealm.admin(), "direct-grant").update(clientRep);

status = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(accessToken)
.acceptJson()
.asStatus();
assertEquals(200, status);

// Set realm notBefore to future
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(managedRealm.admin()).setNotBefore(currentTime + 100).update()) {

status = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(accessToken)
.acceptJson()
.asStatus();
assertEquals(401, status);
}

status = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(accessToken)
.acceptJson()
.asStatus();
assertEquals(200, status);

} finally {
clientRep.setNotBefore(originalClientNotBefore);
AdminApiUtil.findClientByClientId(managedRealm.admin(), "direct-grant").update(clientRep);
}
}

protected void setUserProfileConfiguration(String configuration) {
UserProfileUtil.setUserProfileConfiguration(managedRealm.admin(), configuration);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.keycloak.testsuite.oidc.AbstractOIDCScopeTest;
import org.keycloak.testsuite.oidc.OIDCScopeTest;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
Expand Down Expand Up @@ -674,6 +675,58 @@ public void testIntrospectRefreshTokenAfterRefreshTokenRequest() throws Exceptio
}
}

@Test
public void testIntrospectionRespectsRealmNotBeforeWithClientNotBefore() throws Exception {
//Test that introspection respects realm notBefore even when client notBefore is set
oauth.doLogin("test-user@localhost", "password");
String code = oauth.parseLoginResponse().getCode();
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code);
events.clear();

int currentTime = (int) (System.currentTimeMillis() / 1000);

ClientRepresentation clientRep = AdminApiUtil.findClientByClientId(managedRealm.admin(), "test-app").toRepresentation();
int originalClientNotBefore = clientRep.getNotBefore() != null ? clientRep.getNotBefore() : 0;

try {
//Test realm notBefore with client notBefore both set
// Set client notBefore to past
clientRep.setNotBefore(currentTime - 100);
AdminApiUtil.findClientByClientId(managedRealm.admin(), "test-app").update(clientRep);

oauth.client("confidential-cli", "secret1");
TokenMetadataRepresentation accessTokenRep = oauth.doIntrospectionAccessTokenRequest(accessTokenResponse.getAccessToken()).asTokenMetadata();
org.junit.Assert.assertTrue(accessTokenRep.isActive());

oauth.client("test-app", "password");
JsonNode refreshTokenJson = oauth.doIntrospectionRefreshTokenRequest(accessTokenResponse.getRefreshToken()).asJsonNode();
org.junit.Assert.assertTrue(refreshTokenJson.get("active").asBoolean());

// Set realm notBefore to future
try (RealmAttributeUpdater rau = new RealmAttributeUpdater(managedRealm.admin()).setNotBefore(currentTime + 100).update()) {
oauth.client("confidential-cli", "secret1");
accessTokenRep = oauth.doIntrospectionAccessTokenRequest(accessTokenResponse.getAccessToken()).asTokenMetadata();
org.junit.Assert.assertFalse(accessTokenRep.isActive());

oauth.client("test-app", "password");
refreshTokenJson = oauth.doIntrospectionRefreshTokenRequest(accessTokenResponse.getRefreshToken()).asJsonNode();
org.junit.Assert.assertFalse(refreshTokenJson.get("active").asBoolean());
}

oauth.client("confidential-cli", "secret1");
accessTokenRep = oauth.doIntrospectionAccessTokenRequest(accessTokenResponse.getAccessToken()).asTokenMetadata();
org.junit.Assert.assertTrue(accessTokenRep.isActive());

oauth.client("test-app", "password");
refreshTokenJson = oauth.doIntrospectionRefreshTokenRequest(accessTokenResponse.getRefreshToken()).asJsonNode();
org.junit.Assert.assertTrue(refreshTokenJson.get("active").asBoolean());

} finally {
clientRep.setNotBefore(originalClientNotBefore);
AdminApiUtil.findClientByClientId(managedRealm.admin(), "test-app").update(clientRep);
}
}

private String introspectUnknownTokenType(String clientId, String clientSecret, String tokenToIntrospect) {
HttpPost post = new HttpPost(oauth.getEndpoints().getIntrospection());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
Expand Down Expand Up @@ -736,6 +738,51 @@ public void testNotBeforeTokens() {

clientRep.setNotBefore(0);
clientResource.update(clientRep);

//Test realm notBefore with client notBefore both set
// Set client notBefore to past
clientRep.setNotBefore(time - 200);
clientResource.update(clientRep);

// Set realm notBefore to future
rep.setNotBefore(time);
realm.update(rep);

response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());

assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());

events.expect(EventType.USER_INFO_REQUEST_ERROR)
.error(Errors.INVALID_TOKEN)
.user(Matchers.nullValue(String.class))
.session(Matchers.nullValue(String.class))
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN)
.client((String) null)
.assertEvent();

rep.setNotBefore(0);
realm.update(rep);
clientRep.setNotBefore(0);
clientResource.update(clientRep);

testingClient.server("test").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
UserModel userModel = session.users().getUserByUsername(realmModel, "test-user@localhost");
session.users().setNotBeforeForUser(realmModel, userModel, time);
});

response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());

testingClient.server("test").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
UserModel userModel = session.users().getUserByUsername(realmModel, "test-user@localhost");
session.users().setNotBeforeForUser(realmModel, userModel, 0);
});

response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());
assertEquals(Status.OK.getStatusCode(), response.getStatus());

} finally {
client.close();
}
Expand Down
Loading