Merge pull request #1179 from mposolda/master

KEYCLOAK-1070 Improve Applications page and add available roles. Add tes...
This commit is contained in:
Marek Posolda 2015-04-24 17:15:21 +02:00
commit b8d23829aa
11 changed files with 438 additions and 170 deletions

View file

@ -20,7 +20,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.account.AccountPages; import org.keycloak.account.AccountPages;
import org.keycloak.account.AccountProvider; import org.keycloak.account.AccountProvider;
import org.keycloak.account.freemarker.model.ConsentBean; import org.keycloak.account.freemarker.model.ApplicationsBean;
import org.keycloak.account.freemarker.model.AccountBean; import org.keycloak.account.freemarker.model.AccountBean;
import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean; import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
import org.keycloak.account.freemarker.model.FeaturesBean; import org.keycloak.account.freemarker.model.FeaturesBean;
@ -186,7 +186,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
attributes.put("sessions", new SessionsBean(realm, sessions)); attributes.put("sessions", new SessionsBean(realm, sessions));
break; break;
case APPLICATIONS: case APPLICATIONS:
attributes.put("consent", new ConsentBean(user)); attributes.put("applications", new ApplicationsBean(realm, user));
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle)); attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
break; break;
case PASSWORD: case PASSWORD:

View file

@ -0,0 +1,154 @@
package org.keycloak.account.freemarker.model;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.util.MultivaluedHashMap;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ApplicationsBean {
private List<ApplicationEntry> applications = new LinkedList<ApplicationEntry>();
public ApplicationsBean(RealmModel realm, UserModel user) {
List<ClientModel> realmClients = realm.getClients();
for (ClientModel client : realmClients) {
// Don't show bearerOnly clients
if (client.isBearerOnly()) {
continue;
}
Set<RoleModel> availableRoles = TokenManager.getAccess(null, client, user);
// Don't show applications, which user doesn't have access into (any available roles)
if (availableRoles.isEmpty()) {
continue;
}
List<RoleModel> realmRolesAvailable = new LinkedList<RoleModel>();
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable = new MultivaluedHashMap<String, ClientRoleEntry>();
processRoles(availableRoles, realmRolesAvailable, resourceRolesAvailable);
List<RoleModel> realmRolesGranted = new LinkedList<RoleModel>();
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted = new MultivaluedHashMap<String, ClientRoleEntry>();
List<String> claimsGranted = new LinkedList<String>();
if (client.isConsentRequired()) {
UserConsentModel consent = user.getConsentByClient(client.getId());
if (consent != null) {
processRoles(consent.getGrantedRoles(), realmRolesGranted, resourceRolesGranted);
for (ProtocolMapperModel protocolMapper : consent.getGrantedProtocolMappers()) {
claimsGranted.add(protocolMapper.getConsentText());
}
}
}
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, realmRolesGranted, resourceRolesGranted, client, claimsGranted);
applications.add(appEntry);
}
}
private void processRoles(Set<RoleModel> inputRoles, List<RoleModel> realmRoles, MultivaluedHashMap<String, ClientRoleEntry> clientRoles) {
for (RoleModel role : inputRoles) {
if (role.getContainer() instanceof RealmModel) {
realmRoles.add(role);
} else {
ClientModel currentClient = (ClientModel) role.getContainer();
ClientRoleEntry clientRole = new ClientRoleEntry(currentClient.getClientId(), currentClient.getName(),
role.getName(), role.getDescription());
clientRoles.add(currentClient.getClientId(), clientRole);
}
}
}
public List<ApplicationEntry> getApplications() {
return applications;
}
public static class ApplicationEntry {
private final List<RoleModel> realmRolesAvailable;
private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable;
private final List<RoleModel> realmRolesGranted;
private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted;
private final ClientModel client;
private final List<String> claimsGranted;
public ApplicationEntry(List<RoleModel> realmRolesAvailable, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesAvailable,
List<RoleModel> realmRolesGranted, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted,
ClientModel client, List<String> claimsGranted) {
this.realmRolesAvailable = realmRolesAvailable;
this.resourceRolesAvailable = resourceRolesAvailable;
this.realmRolesGranted = realmRolesGranted;
this.resourceRolesGranted = resourceRolesGranted;
this.client = client;
this.claimsGranted = claimsGranted;
}
public List<RoleModel> getRealmRolesAvailable() {
return realmRolesAvailable;
}
public MultivaluedHashMap<String, ClientRoleEntry> getResourceRolesAvailable() {
return resourceRolesAvailable;
}
public List<RoleModel> getRealmRolesGranted() {
return realmRolesGranted;
}
public MultivaluedHashMap<String, ClientRoleEntry> getResourceRolesGranted() {
return resourceRolesGranted;
}
public ClientModel getClient() {
return client;
}
public List<String> getClaimsGranted() {
return claimsGranted;
}
}
// Same class used in OAuthGrantBean as well. Maybe should be merged into common-freemarker...
public static class ClientRoleEntry {
private final String clientId;
private final String clientName;
private final String roleName;
private final String roleDescription;
public ClientRoleEntry(String clientId, String clientName, String roleName, String roleDescription) {
this.clientId = clientId;
this.clientName = clientName;
this.roleName = roleName;
this.roleDescription = roleDescription;
}
public String getClientId() {
return clientId;
}
public String getClientName() {
return clientName;
}
public String getRoleName() {
return roleName;
}
public String getRoleDescription() {
return roleDescription;
}
}
}

View file

@ -1,117 +0,0 @@
package org.keycloak.account.freemarker.model;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.util.MultivaluedHashMap;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ConsentBean {
private List<ClientGrantBean> clientGrants = new LinkedList<ClientGrantBean>();
public ConsentBean(UserModel user) {
List<UserConsentModel> grantedConsents = user.getConsents();
for (UserConsentModel consent : grantedConsents) {
ClientModel client = consent.getClient();
List<RoleModel> realmRolesGranted = new LinkedList<RoleModel>();
MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted = new MultivaluedHashMap<String, ClientRoleEntry>();
for (RoleModel role : consent.getGrantedRoles()) {
if (role.getContainer() instanceof RealmModel) {
realmRolesGranted.add(role);
} else {
ClientModel currentClient = (ClientModel) role.getContainer();
ClientRoleEntry clientRole = new ClientRoleEntry(currentClient.getClientId(), currentClient.getName(),
role.getName(), role.getDescription());
resourceRolesGranted.add(currentClient.getClientId(), clientRole);
}
}
List<String> claimsGranted = new LinkedList<String>();
for (ProtocolMapperModel protocolMapper : consent.getGrantedProtocolMappers()) {
claimsGranted.add(protocolMapper.getConsentText());
}
ClientGrantBean clientGrant = new ClientGrantBean(realmRolesGranted, resourceRolesGranted, client, claimsGranted);
clientGrants.add(clientGrant);
}
}
public List<ClientGrantBean> getClientGrants() {
return clientGrants;
}
public static class ClientGrantBean {
private final List<RoleModel> realmRolesGranted;
private final MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted;
private final ClientModel client;
private final List<String> claimsGranted;
public ClientGrantBean(List<RoleModel> realmRolesGranted, MultivaluedHashMap<String, ClientRoleEntry> resourceRolesGranted,
ClientModel client, List<String> claimsGranted) {
this.realmRolesGranted = realmRolesGranted;
this.resourceRolesGranted = resourceRolesGranted;
this.client = client;
this.claimsGranted = claimsGranted;
}
public List<RoleModel> getRealmRolesGranted() {
return realmRolesGranted;
}
public MultivaluedHashMap<String, ClientRoleEntry> getResourceRolesGranted() {
return resourceRolesGranted;
}
public ClientModel getClient() {
return client;
}
public List<String> getClaimsGranted() {
return claimsGranted;
}
}
// Same class used in OAuthGrantBean as well. Maybe should be merged into common-freemarker...
public static class ClientRoleEntry {
private final String clientId;
private final String clientName;
private final String roleName;
private final String roleDescription;
public ClientRoleEntry(String clientId, String clientName, String roleName, String roleDescription) {
this.clientId = clientId;
this.clientName = clientName;
this.roleName = roleName;
this.roleDescription = roleDescription;
}
public String getClientId() {
return clientId;
}
public String getClientName() {
return clientName;
}
public String getRoleName() {
return roleName;
}
public String getRoleDescription() {
return roleDescription;
}
}
}

View file

@ -13,42 +13,71 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<td>${msg("client")}</td> <td>${msg("application")}</td>
<td>${msg("grantedPersonalInfo")}</td> <td>${msg("availablePermissions")}</td>
<td>${msg("grantedPermissions")}</td> <td>${msg("grantedPermissions")}</td>
<td>${msg("grantedPersonalInfo")}</td>
<td>${msg("action")}</td> <td>${msg("action")}</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<#list consent.clientGrants as clientGrant> <#list applications.applications as application>
<tr> <tr>
<td> <td>
<#if clientGrant.client.baseUrl??><a href="${clientGrant.client.baseUrl}"></#if> <#if application.client.baseUrl??><a href="${application.client.baseUrl}"></#if>
<#if clientGrant.client.name??>${advancedMsg(clientGrant.client.name)}<#else>${clientGrant.client.clientId}</#if> <#if application.client.name??>${advancedMsg(application.client.name)}<#else>${application.client.clientId}</#if>
<#if clientGrant.client.baseUrl??></a></#if> <#if application.client.baseUrl??></a></#if>
</td> </td>
<td> <td>
<#list clientGrant.claimsGranted as claim> <#list application.realmRolesAvailable as role>
${advancedMsg(claim)}<#if claim_has_next>, </#if>
</#list>
</td>
<td>
<#list clientGrant.realmRolesGranted as role>
<#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if> <#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if>
<#if role_has_next>, </#if> <#if role_has_next>, </#if>
</#list> </#list>
<#list clientGrant.resourceRolesGranted?keys as resource> <#list application.resourceRolesAvailable?keys as resource>
<#if clientGrant.realmRolesGranted?has_content>, </#if> <#if application.realmRolesAvailable?has_content>, </#if>
<#list clientGrant.resourceRolesGranted[resource] as clientRole> <#list application.resourceRolesAvailable[resource] as clientRole>
<#if clientRole.roleDescription??>${advancedMsg(clientRole.roleDescription)}<#else>${advancedMsg(clientRole.roleName)}</#if> <#if clientRole.roleDescription??>${advancedMsg(clientRole.roleDescription)}<#else>${advancedMsg(clientRole.roleName)}</#if>
${msg("inResource")} <strong><#if clientRole.clientName??>${advancedMsg(clientRole.clientName)}<#else>${clientRole.clientId}</#if></strong> ${msg("inResource")} <strong><#if clientRole.clientName??>${advancedMsg(clientRole.clientName)}<#else>${clientRole.clientId}</#if></strong>
<#if clientRole_has_next>, </#if> <#if clientRole_has_next>, </#if>
</#list> </#list>
</#list> </#list>
</td> </td>
<td> <td>
<button type='submit' class='btn btn-primary' id='revoke-${clientGrant.client.clientId}' name='clientId' value="${clientGrant.client.id}">${msg("revoke")}</button> <#if application.client.consentRequired>
<#list application.realmRolesGranted as role>
<#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if>
<#if role_has_next>, </#if>
</#list>
<#list application.resourceRolesGranted?keys as resource>
<#if application.realmRolesGranted?has_content>, </#if>
<#list application.resourceRolesGranted[resource] as clientRole>
<#if clientRole.roleDescription??>${advancedMsg(clientRole.roleDescription)}<#else>${advancedMsg(clientRole.roleName)}</#if>
${msg("inResource")} <strong><#if clientRole.clientName??>${advancedMsg(clientRole.clientName)}<#else>${clientRole.clientId}</#if></strong>
<#if clientRole_has_next>, </#if>
</#list>
</#list>
<#else>
<strong>${msg("fullAccess")}</strong>
</#if>
</td>
<td>
<#if application.client.consentRequired>
<#list application.claimsGranted as claim>
${advancedMsg(claim)}<#if claim_has_next>, </#if>
</#list>
<#else>
<strong>${msg("fullAccess")}</strong>
</#if>
</td>
<td>
<#if application.client.consentRequired>
<button type='submit' class='btn btn-primary' id='revoke-${application.client.clientId}' name='clientId' value="${application.client.id}">${msg("revoke")}</button>
</#if>
</td> </td>
</tr> </tr>
</#list> </#list>

View file

@ -12,7 +12,7 @@ changePasswordHtmlTitle=Change Password
sessionsHtmlTitle=Sessions sessionsHtmlTitle=Sessions
accountManagementTitle=Keycloak Account Management accountManagementTitle=Keycloak Account Management
authenticatorTitle=Authenticator authenticatorTitle=Authenticator
applicationsHtmlTitle=Manage Granted Permissions applicationsHtmlTitle=Applications
authenticatorCode=One-time code authenticatorCode=One-time code
email=Email email=Email
@ -32,7 +32,7 @@ region=State, Province, or Region
postal_code=Zip or Postal code postal_code=Zip or Postal code
country=Country country=Country
emailVerified=Email verified emailVerified=Email verified
gssDelegationCredential=gss delegation credential gssDelegationCredential=GSS Delegation Credential
role_admin=Admin role_admin=Admin
role_realm-admin=Realm Admin role_realm-admin=Realm Admin
@ -50,6 +50,7 @@ role_manage-identity-providers=Manage identity providers
role_manage-clients=Manage clients role_manage-clients=Manage clients
role_manage-events=Manage events role_manage-events=Manage events
role_view-profile=View profile role_view-profile=View profile
role_manage-account=Manage account
client_account=Account client_account=Account
client_security-admin-console=Security Admin Console client_security-admin-console=Security Admin Console
client_realm-management=Realm Management client_realm-management=Realm Management
@ -78,10 +79,13 @@ authenticator=Authenticator
sessions=Sessions sessions=Sessions
log=Log log=Log
grantedPersonalInfo=Granted Personal Info application=Application
availablePermissions=Available Permissions
grantedPermissions=Granted Permissions grantedPermissions=Granted Permissions
grantedPersonalInfo=Granted Personal Info
action=Action action=Action
inResource=in inResource=in
fullAccess=Full Access
revoke=Revoke Grant revoke=Revoke Grant
configureAuthenticators=Configured Authenticators configureAuthenticators=Configured Authenticators

View file

@ -76,10 +76,10 @@ public interface UserModel {
void setFederationLink(String link); void setFederationLink(String link);
void addConsent(UserConsentModel consent); void addConsent(UserConsentModel consent);
UserConsentModel getConsentByClient(String clientId); UserConsentModel getConsentByClient(String clientInternalId);
List<UserConsentModel> getConsents(); List<UserConsentModel> getConsents();
void updateConsent(UserConsentModel consent); void updateConsent(UserConsentModel consent);
boolean revokeConsentForClient(String clientId); boolean revokeConsentForClient(String clientInternalId);
public static enum RequiredAction { public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD

View file

@ -370,7 +370,7 @@ public class MongoUserProvider implements UserProvider {
.and("grantedProtocolMappers").is(protocolMapper.getId()) .and("grantedProtocolMappers").is(protocolMapper.getId())
.get(); .get();
DBObject pull = new BasicDBObject("$pull", query); DBObject pull = new BasicDBObject("$pull", query);
getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext); getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
} }
@Override @Override

View file

@ -52,7 +52,7 @@ public class ApplicationServlet extends HttpServlet {
PrintWriter pw = resp.getWriter(); PrintWriter pw = resp.getWriter();
pw.printf("<html><head><title>%s</title></head><body>", title); pw.printf("<html><head><title>%s</title></head><body>", title);
UriBuilder base = UriBuilder.fromUri("http://localhost:8081/auth"); UriBuilder base = UriBuilder.fromUri("http://localhost:8081/auth");
pw.printf(LINK, RealmsResource.accountUrl(base), "account", "account"); pw.printf(LINK, RealmsResource.accountUrl(base).build("test"), "account", "account");
pw.print("</body></html>"); pw.print("</body></html>");
pw.flush(); pw.flush();

View file

@ -28,6 +28,7 @@ import org.junit.BeforeClass;
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.account.freemarker.model.ApplicationsBean;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
@ -43,6 +44,7 @@ import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.AccountLogPage; import org.keycloak.testsuite.pages.AccountLogPage;
import org.keycloak.testsuite.pages.AccountPasswordPage; import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountSessionsPage; import org.keycloak.testsuite.pages.AccountSessionsPage;
@ -63,6 +65,7 @@ import org.openqa.selenium.WebDriver;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -129,6 +132,9 @@ public class AccountTest {
@WebResource @WebResource
protected AccountSessionsPage sessionsPage; protected AccountSessionsPage sessionsPage;
@WebResource
protected AccountApplicationsPage applicationsPage;
@WebResource @WebResource
protected ErrorPage errorPage; protected ErrorPage errorPage;
@ -517,4 +523,38 @@ public class AccountTest {
} }
} }
// More tests (including revoke) are in OAuthGrantTest
@Test
public void applications() {
applicationsPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=applications").assertEvent();
Assert.assertTrue(applicationsPage.isCurrent());
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
Assert.assertEquals(3, apps.size());
AccountApplicationsPage.AppEntry accountEntry = apps.get("Account");
Assert.assertEquals(2, accountEntry.getRolesAvailable().size());
Assert.assertTrue(accountEntry.getRolesAvailable().contains("Manage account in Account"));
Assert.assertTrue(accountEntry.getRolesAvailable().contains("View profile in Account"));
Assert.assertEquals(1, accountEntry.getRolesGranted().size());
Assert.assertTrue(accountEntry.getRolesGranted().contains("Full Access"));
Assert.assertEquals(1, accountEntry.getProtocolMappersGranted().size());
Assert.assertTrue(accountEntry.getProtocolMappersGranted().contains("Full Access"));
AccountApplicationsPage.AppEntry testAppEntry = apps.get("test-app");
Assert.assertEquals(4, testAppEntry.getRolesAvailable().size());
Assert.assertTrue(testAppEntry.getRolesGranted().contains("Full Access"));
Assert.assertTrue(testAppEntry.getProtocolMappersGranted().contains("Full Access"));
AccountApplicationsPage.AppEntry thirdPartyEntry = apps.get("third-party");
Assert.assertEquals(2, thirdPartyEntry.getRolesAvailable().size());
Assert.assertTrue(thirdPartyEntry.getRolesAvailable().contains("Have User privileges"));
Assert.assertTrue(thirdPartyEntry.getRolesAvailable().contains("Have Customer User privileges in test-app"));
Assert.assertEquals(0, thirdPartyEntry.getRolesGranted().size());
Assert.assertEquals(0, thirdPartyEntry.getProtocolMappersGranted().size());
}
} }

View file

@ -26,11 +26,24 @@ import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.constants.KerberosConstants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ProtocolMapperModel;
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.representations.AccessToken;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage; import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
@ -69,11 +82,17 @@ public class OAuthGrantTest {
@WebResource @WebResource
protected OAuthGrantPage grantPage; protected OAuthGrantPage grantPage;
@WebResource
protected AccountApplicationsPage accountAppsPage;
@WebResource
protected AppPage appPage;
private static String ROLE_USER = "Have User privileges"; private static String ROLE_USER = "Have User privileges";
private static String ROLE_CUSTOMER = "Have Customer User privileges"; private static String ROLE_CUSTOMER = "Have Customer User privileges";
@Test @Test
public void oauthGrantAcceptTest() throws IOException { public void oauthGrantAcceptTest() {
oauth.clientId("third-party"); oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password"); oauth.doLoginGrant("test-user@localhost", "password");
@ -106,10 +125,16 @@ public class OAuthGrantTest {
Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user")); Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
events.expectCodeToToken(codeId, loginEvent.getSessionId()).client("third-party").assertEvent(); events.expectCodeToToken(codeId, loginEvent.getSessionId()).client("third-party").assertEvent();
accountAppsPage.open();
accountAppsPage.revokeGrant("third-party");
events.expect(EventType.REVOKE_GRANT)
.client("account").detail(Details.REVOKED_CLIENT, "third-party").assertEvent();
} }
@Test @Test
public void oauthGrantCancelTest() throws IOException { public void oauthGrantCancelTest() {
oauth.clientId("third-party"); oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password"); oauth.doLoginGrant("test-user@localhost", "password");
@ -125,4 +150,118 @@ public class OAuthGrantTest {
events.expectLogin().client("third-party").error("rejected_by_user").assertEvent(); events.expectLogin().client("third-party").error("rejected_by_user").assertEvent();
} }
@Test
public void oauthGrantNotShownWhenAlreadyGranted() {
// Grant permissions on grant screen
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
grantPage.assertCurrent();
grantPage.accept();
events.expectLogin().client("third-party").assertEvent();
// Assert permissions granted on Account mgmt. applications page
accountAppsPage.open();
AccountApplicationsPage.AppEntry thirdPartyEntry = accountAppsPage.getApplications().get("third-party");
Assert.assertTrue(thirdPartyEntry.getRolesGranted().contains(ROLE_USER));
Assert.assertTrue(thirdPartyEntry.getRolesGranted().contains("Have Customer User privileges in test-app"));
Assert.assertTrue(thirdPartyEntry.getProtocolMappersGranted().contains("Full name"));
Assert.assertTrue(thirdPartyEntry.getProtocolMappersGranted().contains("Email"));
// 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();
// Revoke grant in account mgmt.
accountAppsPage.open();
accountAppsPage.revokeGrant("third-party");
events.expect(EventType.REVOKE_GRANT)
.client("account").detail(Details.REVOKED_CLIENT, "third-party").assertEvent();
// Open login form again and assert grant Page is shown
oauth.openLoginForm();
grantPage.assertCurrent();
Assert.assertTrue(driver.getPageSource().contains(ROLE_USER));
Assert.assertTrue(driver.getPageSource().contains(ROLE_CUSTOMER));
}
@Test
public void oauthGrantAddAnotherRoleAndMapper() {
// Grant permissions on grant screen
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
// Add new protocolMapper and role before showing grant page
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ProtocolMapperModel protocolMapper = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
KerberosConstants.GSS_DELEGATION_CREDENTIAL,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
true, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
true, false);
ClientModel thirdPartyApp = appRealm.getClientByClientId("third-party");
thirdPartyApp.addProtocolMapper(protocolMapper);
RoleModel newRole = appRealm.addRole("new-role");
thirdPartyApp.addScopeMapping(newRole);
UserModel testUser = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
testUser.grantRole(newRole);
}
});
// Confirm grant page
grantPage.assertCurrent();
grantPage.accept();
events.expectLogin().client("third-party").assertEvent();
// Assert new role and protocol mapper not in account mgmt.
accountAppsPage.open();
AccountApplicationsPage.AppEntry appEntry = accountAppsPage.getApplications().get("third-party");
Assert.assertFalse(appEntry.getRolesGranted().contains("new-role"));
Assert.assertFalse(appEntry.getProtocolMappersGranted().contains(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME));
// Show grant page another time. Just new role and protocol mapper are on the page
oauth.openLoginForm();
grantPage.assertCurrent();
Assert.assertFalse(driver.getPageSource().contains(ROLE_USER));
Assert.assertFalse(driver.getPageSource().contains("Full name"));
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();
// Go to account mgmt. Everything is granted now
accountAppsPage.open();
appEntry = accountAppsPage.getApplications().get("third-party");
Assert.assertTrue(appEntry.getRolesGranted().contains("new-role"));
Assert.assertTrue(appEntry.getProtocolMappersGranted().contains(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME));
// Revoke
accountAppsPage.revokeGrant("third-party");
events.expect(EventType.REVOKE_GRANT)
.client("account").detail(Details.REVOKED_CLIENT, "third-party").assertEvent();
// Cleanup
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel thirdPartyApp = appRealm.getClientByClientId("third-party");
ProtocolMapperModel gssMapper = thirdPartyApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME);
thirdPartyApp.removeProtocolMapper(gssMapper);
RoleModel newRole = appRealm.getRole("new-role");
appRealm.removeRole(newRole);
}
});
}
} }

View file

@ -37,62 +37,81 @@ public class AccountApplicationsPage extends AbstractAccountPage {
driver.findElement(By.id("revoke-" + clientId)).click(); driver.findElement(By.id("revoke-" + clientId)).click();
} }
public Map<String, ClientGrant> getClientGrants() { public Map<String, AppEntry> getApplications() {
Map<String, ClientGrant> table = new HashMap<String, ClientGrant>(); Map<String, AppEntry> table = new HashMap<String, AppEntry>();
for (WebElement r : driver.findElements(By.tagName("tr"))) { for (WebElement r : driver.findElements(By.tagName("tr"))) {
int count = 0; int count = 0;
ClientGrant currentGrant = null; AppEntry currentEntry = null;
for (WebElement col : r.findElements(By.tagName("td"))) { for (WebElement col : r.findElements(By.tagName("td"))) {
count++; count++;
switch (count) { switch (count) {
case 1: case 1:
currentGrant = new ClientGrant(); currentEntry = new AppEntry();
String clientId = col.getText(); String client = col.getText();
table.put(clientId, currentGrant); table.put(client, currentEntry);
break; break;
case 2: case 2:
String protMappersStr = col.getText();
String[] protMappers = protMappersStr.split(",");
for (String protMapper : protMappers) {
protMapper = protMapper.trim();
currentGrant.addMapper(protMapper);
}
break;
case 3:
String rolesStr = col.getText(); String rolesStr = col.getText();
String[] roles = rolesStr.split(","); String[] roles = rolesStr.split(",");
for (String role : roles) { for (String role : roles) {
role = role.trim(); role = role.trim();
currentGrant.addRole(role); currentEntry.addAvailableRole(role);
}
break;
case 3:
rolesStr = col.getText();
if (rolesStr.isEmpty()) break;
roles = rolesStr.split(",");
for (String role : roles) {
role = role.trim();
currentEntry.addGrantedRole(role);
}
break;
case 4:
String protMappersStr = col.getText();
if (protMappersStr.isEmpty()) break;
String[] protMappers = protMappersStr.split(",");
for (String protMapper : protMappers) {
protMapper = protMapper.trim();
currentEntry.addMapper(protMapper);
} }
break; break;
} }
} }
} }
table.remove("Client"); table.remove("Application");
return table; return table;
} }
public static class ClientGrant { public static class AppEntry {
private final List<String> protocolMapperDescriptions = new ArrayList<String>(); private final List<String> rolesAvailable = new ArrayList<String>();
private final List<String> roleDescriptions = new ArrayList<String>(); private final List<String> rolesGranted = new ArrayList<String>();
private final List<String> protocolMappersGranted = new ArrayList<String>();
private void addAvailableRole(String role) {
rolesAvailable.add(role);
}
private void addGrantedRole(String role) {
rolesGranted.add(role);
}
private void addMapper(String protocolMapper) { private void addMapper(String protocolMapper) {
protocolMapperDescriptions.add(protocolMapper); protocolMappersGranted.add(protocolMapper);
} }
private void addRole(String role) { public List<String> getRolesGranted() {
roleDescriptions.add(role); return rolesGranted;
} }
public List<String> getProtocolMapperDescriptions() { public List<String> getRolesAvailable() {
return protocolMapperDescriptions; return rolesAvailable;
} }
public List<String> getRoleDescriptions() { public List<String> getProtocolMappersGranted() {
return roleDescriptions; return protocolMappersGranted;
} }
} }
} }