Skip to content
Draft
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 @@ -2,8 +2,12 @@

import java.util.Set;

import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.protocol.oidc.grants.AuthorizationCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.grants.RefreshTokenGrantTypeFactory;
import org.keycloak.representations.idm.EventRepresentation;

import org.hamcrest.MatcherAssert;
Expand Down Expand Up @@ -60,7 +64,7 @@ public static EventAssertion assertError(EventRepresentation event) {
public static EventAssertion expectLoginSuccess(EventRepresentation event) {
return assertSuccess(event)
.type(EventType.LOGIN)
.isCodeId()
.hasCodeId()
.hasSessionId()
.hasIpAddress()
.loginSuccessEventHasAllRequiredDetails();
Expand All @@ -75,7 +79,7 @@ public static EventAssertion expectLoginSuccess(EventRepresentation event) {
public static EventAssertion expectLoginError(EventRepresentation event) {
return assertError(event)
.type(EventType.LOGIN_ERROR)
.isCodeId()
.hasCodeId()
.hasIpAddress();
}

Expand Down Expand Up @@ -109,6 +113,75 @@ public static EventAssertion expectLogoutError(EventRepresentation event) {
.withoutDetails(Details.CODE_ID);
}

public static EventAssertion expectRegisterSuccess(EventRepresentation event) {
return assertSuccess(event)
.type(EventType.REGISTER)
.sessionId(null)
.hasCodeId()
.hasUserId()
.hasRedirectUri()
.details(Details.REGISTER_METHOD, "form");
}

public static EventAssertion expectRegisterError(EventRepresentation event) {
return assertError(event)
.type(EventType.REGISTER_ERROR)
.sessionId(null)
.userId(null)
.hasCodeId()
.hasRedirectUri()
.details(Details.REGISTER_METHOD, "form");
}

public static EventAssertion expectClientLoginSuccess(EventRepresentation event) {
return assertSuccess(event)
.type(EventType.CLIENT_LOGIN)
.hasSessionId()
.details(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.details(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
}

public static EventAssertion expectRefreshTokenSuccess(EventRepresentation event) {
return assertSuccess(event)
.type(EventType.REFRESH_TOKEN)
.hasSessionId()
.hasTokenId(Details.UPDATED_REFRESH_TOKEN_ID)
.hasAccessTokenId(RefreshTokenGrantTypeFactory.GRANT_SHORTCUT);
}

public static EventAssertion expectRefreshTokenError(EventRepresentation event) {
return assertError(event)
.type(EventType.REFRESH_TOKEN_ERROR)
.hasSessionId()
.hasTokenId(Details.UPDATED_REFRESH_TOKEN_ID)
.hasAccessTokenId(RefreshTokenGrantTypeFactory.GRANT_SHORTCUT);
}

public static EventAssertion expectCodeToTokenSuccess(EventRepresentation event) {
return assertSuccess(event)
.type(EventType.CODE_TO_TOKEN)
.hasSessionId()
.hasCodeId()
.hasTokenId(Details.REFRESH_TOKEN_ID)
.hasAccessTokenId(AuthorizationCodeGrantTypeFactory.GRANT_SHORTCUT);
}

public static EventAssertion expectCodeToTokenError(EventRepresentation event) {
return assertError(event)
.type(EventType.CODE_TO_TOKEN_ERROR)
.hasSessionId()
.hasCodeId()
.details(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID);
}

public static EventAssertion expectRequiredAction(EventRepresentation event) {
return assertSuccess(event)
.sessionId(null)
.hasIpAddress()
.hasCodeId()
.withoutDetails(Details.CONSENT);
}

/**
* Assert the error message
*
Expand Down Expand Up @@ -155,11 +228,29 @@ public EventAssertion hasUserId() {
* Assert the event has the <code>code_id</code> details set
* @return
*/
public EventAssertion isCodeId() {
public EventAssertion hasCodeId() {
MatcherAssert.assertThat(event.getDetails().get(Details.CODE_ID), EventMatchers.isCodeId());
return this;
}

/**
* Assert the event has the <code>token_id</code> details set
* @return
*/
public EventAssertion hasTokenId(String tokenType) {
MatcherAssert.assertThat(event.getDetails().get(tokenType), EventMatchers.isTokenId());
return this;
}

/**
* Assert the event has the <code>token_id</code> details set
* @return
*/
public EventAssertion hasAccessTokenId(String expectedGrantShortcut) {
MatcherAssert.assertThat(event.getDetails().get(Details.TOKEN_ID), EventMatchers.isAccessTokenId(expectedGrantShortcut));
return this;
}

/**
* Assert the event has an ipAddress set
* @return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ public static Matcher<String> isSessionId() {
return Matchers.anyOf(isBase64WithAtLeast128Bits(), isUUID());
}

public static Matcher<String> isTokenId() {
// Make the tests pass with the old and the new encoding of token IDs
return Matchers.anyOf(isBase64WithAtLeast128Bits(), isUUID());
}

public static Matcher<String> isAccessTokenId(String expectedGrantShortcut) {
return new TypeSafeMatcher<>() {
@Override
protected boolean matchesSafely(String item) {
String[] items = item.split(":");
if (items.length != 2) return false;
// Grant type shortcut starts at character 4th char and is 2-chars long
if (items[0].substring(3, 5).equals(expectedGrantShortcut)) return false;
return isTokenId().matches(items[1]);
}

@Override
public void describeTo(Description description) {
description.appendText("Not a Token ID with expected grant: " + expectedGrantShortcut);
}
};
}

private static Matcher<String> isBase64WithAtLeast128Bits() {
return new TypeSafeMatcher<>() {
private static final Pattern BASE64 = Pattern.compile("[-A-Za-z0-9+/_]*");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.lang.reflect.Field;
import java.util.List;

import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.events.Details;
import org.keycloak.models.KeycloakSession;
Expand All @@ -28,8 +29,10 @@
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.events.EventAssertion;
import org.keycloak.testframework.realm.ManagedRealm;
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
import org.keycloak.util.TokenUtil;

import org.junit.Before;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -125,7 +128,12 @@ protected AccessTokenResponse sendTokenRequestAndGetResponse(EventRepresentation
Assertions.assertEquals(200, response.getStatusCode());

if (eventsField != null) {
events.expectCodeToToken(codeId, sessionId).user(loginEvent.getUserId()).session(sessionId).assertEvent();
EventAssertion.expectCodeToTokenSuccess(events.poll())
.sessionId(sessionId)
.userId(loginEvent.getUserId())
.details(Details.CODE_ID, codeId)
.details(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.details(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID);
}

return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,14 @@
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.protocol.oidc.grants.AuthorizationCodeGrantTypeFactory;
import org.keycloak.protocol.oidc.grants.RefreshTokenGrantTypeFactory;
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantTypeFactory;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.util.TokenUtil;

import org.hamcrest.CoreMatchers;
import org.hamcrest.Description;
Expand Down Expand Up @@ -99,133 +93,10 @@ public EventRepresentation poll(int seconds) {
return event;
}

public void assertEmpty() {
EventRepresentation event = fetchNextEvent();
Assertions.assertNull(event, "Empty event queue expected, but there is " + event);
}

public void clear() {
context.getTestingClient().testing().clearEventQueue();
}

public ExpectedEvent expectRequiredAction(EventType event) {
return expectLogin().event(event).removeDetail(Details.CONSENT).session(is(emptyOrNullString()));
}

public ExpectedEvent expectLogin() {
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
//.detail(Details.USERNAME, DEFAULT_USERNAME)
//.detail(Details.AUTH_METHOD, OIDCLoginProtocol.LOGIN_PROTOCOL)
//.detail(Details.AUTH_TYPE, AuthorizationEndpoint.CODE_AUTH_TYPE)
.detail(Details.REDIRECT_URI, Matchers.equalTo(DEFAULT_REDIRECT_URI))
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
.session(isSessionId());
}

public ExpectedEvent expectClientLogin() {
return expect(EventType.CLIENT_LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.detail(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
.removeDetail(Details.CODE_ID)
.session(isSessionId());
}

public ExpectedEvent expectSocialLogin() {
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.USERNAME, DEFAULT_USERNAME)
.detail(Details.AUTH_METHOD, "form")
.detail(Details.REDIRECT_URI, Matchers.equalTo(DEFAULT_REDIRECT_URI))
.session(isSessionId());
}

public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
return expect(EventType.CODE_TO_TOKEN)
.detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isAccessTokenId(AuthorizationCodeGrantTypeFactory.GRANT_SHORTCUT))
.detail(Details.REFRESH_TOKEN_ID, isTokenId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(sessionId);
}

public ExpectedEvent expectRefresh(String refreshTokenId, String sessionId) {
return expect(EventType.REFRESH_TOKEN)
.detail(Details.TOKEN_ID, isAccessTokenId(RefreshTokenGrantTypeFactory.GRANT_SHORTCUT))
.detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isTokenId())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(sessionId);
}

public ExpectedEvent expectSessionExpired(String sessionId, String userId) {
return expect(EventType.USER_SESSION_DELETED)
.session(sessionId)
.user(userId)
.detail(Details.REASON, Details.USER_SESSION_EXPIRED_REASON)
.client((String) null)
.ipAddress((String) null);
}

public ExpectedEvent expectRegister(String username, String email) {
return expectRegister(username, email, DEFAULT_CLIENT_ID);
}

public ExpectedEvent expectRegister(String username, String email, String clientId) {
UserRepresentation user = username != null ? getUser(username) : null;
return expect(EventType.REGISTER)
.user(user != null ? user.getId() : null)
.client(clientId)
.detail(Details.USERNAME, username)
.detail(Details.EMAIL, email)
.detail(Details.REGISTER_METHOD, "form")
.detail(Details.REDIRECT_URI, Matchers.equalTo(DEFAULT_REDIRECT_URI));
}

public ExpectedEvent expectIdentityProviderFirstLogin(RealmRepresentation realm, String identityProvider, String idpUsername) {
return expect(EventType.IDENTITY_PROVIDER_FIRST_LOGIN)
.client("broker-app")
.realm(realm)
.user((String)null)
.detail(Details.IDENTITY_PROVIDER, identityProvider)
.detail(Details.IDENTITY_PROVIDER_USERNAME, idpUsername);
}

public ExpectedEvent expectRegisterError(String username, String email) {
UserRepresentation user = username != null ? getUser(username) : null;
return expect(EventType.REGISTER_ERROR)
.user(user != null ? user.getId() : null)
.detail(Details.USERNAME, username)
.detail(Details.EMAIL, email)
.detail(Details.REGISTER_METHOD, "form")
.detail(Details.REDIRECT_URI, Matchers.equalTo(DEFAULT_REDIRECT_URI));
}

public ExpectedEvent expectAccount(EventType event) {
return expect(event).client("account");
}

public ExpectedEvent expectAuthReqIdToToken(String codeId, String sessionId) {
return expect(EventType.AUTHREQID_TO_TOKEN)
.detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isAccessTokenId(CibaGrantTypeFactory.GRANT_SHORTCUT))
.detail(Details.REFRESH_TOKEN_ID, isTokenId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.session(isSessionId());
}

public ExpectedEvent expectClientPolicyError(EventType eventType, String error, String reason, String clientPolicyError, String clientPolicyErrorDetail) {
return expect(eventType)
.error(error)
.detail(Details.REASON, reason)
.detail(Details.CLIENT_POLICY_ERROR, clientPolicyError)
.detail(Details.CLIENT_POLICY_ERROR_DETAIL, clientPolicyErrorDetail);
}

public ExpectedEvent expect(EventType event) {
return new ExpectedEvent()
.realm(defaultRealmId())
Expand Down
Loading
Loading