KEYCLOAK-1088 Audit of user grants during login

This commit is contained in:
mposolda 2015-06-09 20:37:01 +02:00
parent 7cf1b002d2
commit d3e9b29d78
10 changed files with 83 additions and 22 deletions

View file

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

View file

@ -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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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();

View file

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