KEYCLOAK-1088 Audit of user grants during login
This commit is contained in:
parent
7cf1b002d2
commit
d3e9b29d78
10 changed files with 83 additions and 22 deletions
|
@ -27,4 +27,9 @@ public interface Details {
|
|||
String CLIENT_SESSION_STATE = "client_session_state";
|
||||
String CLIENT_SESSION_HOST = "client_session_host";
|
||||
|
||||
String CONSENT = "consent";
|
||||
String CONSENT_VALUE_NO_CONSENT_REQUIRED = "no_consent_required"; // No consent is required by client
|
||||
String CONSENT_VALUE_CONSENT_GRANTED = "consent_granted"; // Consent granted by user
|
||||
String CONSENT_VALUE_PERSISTED_CONSENT = "persistent_consent"; // Persistent consent used (was already granted by user before)
|
||||
|
||||
}
|
||||
|
|
|
@ -465,7 +465,6 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
if (client.isConsentRequired()) {
|
||||
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
|
||||
|
||||
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
|
||||
|
||||
|
@ -496,11 +495,18 @@ public class AuthenticationManager {
|
|||
|
||||
// Skip grant screen if everything was already approved by this user
|
||||
if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) {
|
||||
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
|
||||
|
||||
return session.getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(accessCode.getCode())
|
||||
.setAccessRequest(realmRoles, resourceRoles, protocolMappers)
|
||||
.createOAuthGrant(clientSession);
|
||||
} else {
|
||||
String consentDetail = (grantedConsent != null) ? Details.CONSENT_VALUE_PERSISTED_CONSENT : Details.CONSENT_VALUE_NO_CONSENT_REQUIRED;
|
||||
event.detail(Details.CONSENT, consentDetail);
|
||||
}
|
||||
} else {
|
||||
event.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED);
|
||||
}
|
||||
|
||||
event.success();
|
||||
|
|
|
@ -316,7 +316,7 @@ public class LoginActionsService {
|
|||
return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
|
||||
}
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
||||
}
|
||||
|
||||
|
@ -443,7 +443,7 @@ public class LoginActionsService {
|
|||
return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
|
||||
}
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
|
||||
}
|
||||
|
||||
|
@ -741,6 +741,7 @@ public class LoginActionsService {
|
|||
}
|
||||
user.updateConsent(grantedConsent);
|
||||
|
||||
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
|
||||
event.success();
|
||||
|
||||
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
|
||||
|
|
|
@ -113,7 +113,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
|||
}
|
||||
|
||||
public ExpectedEvent expectRequiredAction(EventType event) {
|
||||
return expectLogin().event(event).session(isUUID());
|
||||
return expectLogin().event(event).removeDetail(Details.CONSENT).session(isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectLogin() {
|
||||
|
@ -123,6 +123,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
|||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI)
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED)
|
||||
.session(isUUID());
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,9 @@ public class AccountTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().session((String) null).error("invalid_user_credentials").assertEvent();
|
||||
events.expectLogin().session((String) null).error("invalid_user_credentials")
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "new-password");
|
||||
|
|
|
@ -127,7 +127,10 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent();
|
||||
events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials")
|
||||
.detail(Details.USERNAME, "login-test")
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -147,7 +150,10 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent();
|
||||
events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials")
|
||||
.detail(Details.USERNAME, "login-test")
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
} finally {
|
||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
|
@ -175,7 +181,10 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
|
||||
events.expectLogin().user(userId).session((String) null).error("user_disabled")
|
||||
.detail(Details.USERNAME, "login-test")
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
} finally {
|
||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
|
@ -195,7 +204,10 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").assertEvent();
|
||||
events.expectLogin().user((String) null).session((String) null).error("user_not_found")
|
||||
.detail(Details.USERNAME, "invalid")
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -413,7 +425,10 @@ public class LoginTest {
|
|||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).assertEvent();
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null)
|
||||
.removeDetail(Details.USERNAME)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
// KEYCLOAK-1037
|
||||
|
@ -427,7 +442,10 @@ public class LoginTest {
|
|||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent();
|
||||
events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails()
|
||||
.detail(Details.CODE_ID, AssertEvents.isCodeId())
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
|
||||
} finally {
|
||||
Time.setOffset(0);
|
||||
|
|
|
@ -114,7 +114,9 @@ public class LoginTotpTest {
|
|||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent();
|
||||
events.expectLogin().error("invalid_user_credentials").session((String) null)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -140,7 +142,9 @@ public class LoginTotpTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent();
|
||||
events.expectLogin().error("invalid_user_credentials").session((String) null)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -159,7 +163,8 @@ public class LoginTotpTest {
|
|||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("invalid_user_credentials")
|
||||
.session((String) null);
|
||||
.session((String) null)
|
||||
.removeDetail(Details.CONSENT);
|
||||
expectedEvent.assertEvent();
|
||||
} finally {
|
||||
Time.setOffset(0);
|
||||
|
|
|
@ -141,6 +141,7 @@ public class AuthorizationCodeTest {
|
|||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null)
|
||||
.removeDetail(Details.USERNAME)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.detail(Details.REDIRECT_URI, "http://localhost:8081/auth/realms/test/protocol/openid-connect/oauth/oob")
|
||||
.assertEvent().getDetails().get(Details.CODE_ID);
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
@ -51,7 +50,6 @@ import org.keycloak.testsuite.rule.WebResource;
|
|||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -104,7 +102,10 @@ public class OAuthGrantTest {
|
|||
|
||||
Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
|
||||
|
||||
Event loginEvent = events.expectLogin().client("third-party").assertEvent();
|
||||
Event loginEvent = events.expectLogin()
|
||||
.client("third-party")
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
|
||||
.assertEvent();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
|
@ -147,7 +148,11 @@ public class OAuthGrantTest {
|
|||
Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.ERROR));
|
||||
assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
|
||||
events.expectLogin().client("third-party").error("rejected_by_user").assertEvent();
|
||||
events.expectLogin()
|
||||
.client("third-party")
|
||||
.error("rejected_by_user")
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -159,7 +164,10 @@ public class OAuthGrantTest {
|
|||
grantPage.assertCurrent();
|
||||
grantPage.accept();
|
||||
|
||||
events.expectLogin().client("third-party").assertEvent();
|
||||
events.expectLogin()
|
||||
.client("third-party")
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
|
||||
.assertEvent();
|
||||
|
||||
// Assert permissions granted on Account mgmt. applications page
|
||||
accountAppsPage.open();
|
||||
|
@ -172,7 +180,11 @@ public class OAuthGrantTest {
|
|||
// Open login form and assert grantPage not shown
|
||||
oauth.openLoginForm();
|
||||
appPage.assertCurrent();
|
||||
events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("third-party").assertEvent();
|
||||
events.expectLogin()
|
||||
.detail(Details.AUTH_METHOD, "sso")
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_PERSISTED_CONSENT)
|
||||
.removeDetail(Details.USERNAME)
|
||||
.client("third-party").assertEvent();
|
||||
|
||||
// Revoke grant in account mgmt.
|
||||
accountAppsPage.open();
|
||||
|
@ -219,7 +231,10 @@ public class OAuthGrantTest {
|
|||
// Confirm grant page
|
||||
grantPage.assertCurrent();
|
||||
grantPage.accept();
|
||||
events.expectLogin().client("third-party").assertEvent();
|
||||
events.expectLogin()
|
||||
.client("third-party")
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
|
||||
.assertEvent();
|
||||
|
||||
// Assert new role and protocol mapper not in account mgmt.
|
||||
accountAppsPage.open();
|
||||
|
@ -235,7 +250,10 @@ public class OAuthGrantTest {
|
|||
Assert.assertTrue(driver.getPageSource().contains("new-role"));
|
||||
Assert.assertTrue(driver.getPageSource().contains(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME));
|
||||
grantPage.accept();
|
||||
events.expectLogin().client("third-party").assertEvent();
|
||||
events.expectLogin()
|
||||
.client("third-party")
|
||||
.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
|
||||
.assertEvent();
|
||||
|
||||
// Go to account mgmt. Everything is granted now
|
||||
accountAppsPage.open();
|
||||
|
|
|
@ -93,6 +93,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
.detail(Details.USERNAME, login)
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshToken.getSessionState());
|
||||
|
@ -128,6 +129,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
|
||||
HttpResponse logoutResponse = oauth.doLogout(response.getRefreshToken(), "secret");
|
||||
|
@ -180,6 +182,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
.detail(Details.RESPONSE_TYPE, "token")
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.error(Errors.INVALID_USER_CREDENTIALS)
|
||||
.assertEvent();
|
||||
}
|
||||
|
@ -203,6 +206,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
.detail(Details.USERNAME, "invalid")
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.error(Errors.INVALID_USER_CREDENTIALS)
|
||||
.assertEvent();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue