Merge pull request #1179 from mposolda/master
KEYCLOAK-1070 Improve Applications page and add available roles. Add tes...
This commit is contained in:
commit
b8d23829aa
11 changed files with 438 additions and 170 deletions
|
@ -20,7 +20,7 @@ import javax.ws.rs.core.UriInfo;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.account.AccountPages;
|
||||
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.AccountFederatedIdentityBean;
|
||||
import org.keycloak.account.freemarker.model.FeaturesBean;
|
||||
|
@ -186,7 +186,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
attributes.put("sessions", new SessionsBean(realm, sessions));
|
||||
break;
|
||||
case APPLICATIONS:
|
||||
attributes.put("consent", new ConsentBean(user));
|
||||
attributes.put("applications", new ApplicationsBean(realm, user));
|
||||
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
|
||||
break;
|
||||
case PASSWORD:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,42 +13,71 @@
|
|||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>${msg("client")}</td>
|
||||
<td>${msg("grantedPersonalInfo")}</td>
|
||||
<td>${msg("application")}</td>
|
||||
<td>${msg("availablePermissions")}</td>
|
||||
<td>${msg("grantedPermissions")}</td>
|
||||
<td>${msg("grantedPersonalInfo")}</td>
|
||||
<td>${msg("action")}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<#list consent.clientGrants as clientGrant>
|
||||
<#list applications.applications as application>
|
||||
<tr>
|
||||
<td>
|
||||
<#if clientGrant.client.baseUrl??><a href="${clientGrant.client.baseUrl}"></#if>
|
||||
<#if clientGrant.client.name??>${advancedMsg(clientGrant.client.name)}<#else>${clientGrant.client.clientId}</#if>
|
||||
<#if clientGrant.client.baseUrl??></a></#if>
|
||||
<#if application.client.baseUrl??><a href="${application.client.baseUrl}"></#if>
|
||||
<#if application.client.name??>${advancedMsg(application.client.name)}<#else>${application.client.clientId}</#if>
|
||||
<#if application.client.baseUrl??></a></#if>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<#list clientGrant.claimsGranted as claim>
|
||||
${advancedMsg(claim)}<#if claim_has_next>, </#if>
|
||||
</#list>
|
||||
</td>
|
||||
<td>
|
||||
<#list clientGrant.realmRolesGranted as role>
|
||||
<#list application.realmRolesAvailable as role>
|
||||
<#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if>
|
||||
<#if role_has_next>, </#if>
|
||||
</#list>
|
||||
<#list clientGrant.resourceRolesGranted?keys as resource>
|
||||
<#if clientGrant.realmRolesGranted?has_content>, </#if>
|
||||
<#list clientGrant.resourceRolesGranted[resource] as clientRole>
|
||||
<#list application.resourceRolesAvailable?keys as resource>
|
||||
<#if application.realmRolesAvailable?has_content>, </#if>
|
||||
<#list application.resourceRolesAvailable[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>
|
||||
</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>
|
||||
</tr>
|
||||
</#list>
|
||||
|
|
|
@ -12,7 +12,7 @@ changePasswordHtmlTitle=Change Password
|
|||
sessionsHtmlTitle=Sessions
|
||||
accountManagementTitle=Keycloak Account Management
|
||||
authenticatorTitle=Authenticator
|
||||
applicationsHtmlTitle=Manage Granted Permissions
|
||||
applicationsHtmlTitle=Applications
|
||||
|
||||
authenticatorCode=One-time code
|
||||
email=Email
|
||||
|
@ -32,7 +32,7 @@ region=State, Province, or Region
|
|||
postal_code=Zip or Postal code
|
||||
country=Country
|
||||
emailVerified=Email verified
|
||||
gssDelegationCredential=gss delegation credential
|
||||
gssDelegationCredential=GSS Delegation Credential
|
||||
|
||||
role_admin=Admin
|
||||
role_realm-admin=Realm Admin
|
||||
|
@ -50,6 +50,7 @@ role_manage-identity-providers=Manage identity providers
|
|||
role_manage-clients=Manage clients
|
||||
role_manage-events=Manage events
|
||||
role_view-profile=View profile
|
||||
role_manage-account=Manage account
|
||||
client_account=Account
|
||||
client_security-admin-console=Security Admin Console
|
||||
client_realm-management=Realm Management
|
||||
|
@ -78,10 +79,13 @@ authenticator=Authenticator
|
|||
sessions=Sessions
|
||||
log=Log
|
||||
|
||||
grantedPersonalInfo=Granted Personal Info
|
||||
application=Application
|
||||
availablePermissions=Available Permissions
|
||||
grantedPermissions=Granted Permissions
|
||||
grantedPersonalInfo=Granted Personal Info
|
||||
action=Action
|
||||
inResource=in
|
||||
fullAccess=Full Access
|
||||
revoke=Revoke Grant
|
||||
|
||||
configureAuthenticators=Configured Authenticators
|
||||
|
|
|
@ -76,10 +76,10 @@ public interface UserModel {
|
|||
void setFederationLink(String link);
|
||||
|
||||
void addConsent(UserConsentModel consent);
|
||||
UserConsentModel getConsentByClient(String clientId);
|
||||
UserConsentModel getConsentByClient(String clientInternalId);
|
||||
List<UserConsentModel> getConsents();
|
||||
void updateConsent(UserConsentModel consent);
|
||||
boolean revokeConsentForClient(String clientId);
|
||||
boolean revokeConsentForClient(String clientInternalId);
|
||||
|
||||
public static enum RequiredAction {
|
||||
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
|
||||
|
|
|
@ -370,7 +370,7 @@ public class MongoUserProvider implements UserProvider {
|
|||
.and("grantedProtocolMappers").is(protocolMapper.getId())
|
||||
.get();
|
||||
DBObject pull = new BasicDBObject("$pull", query);
|
||||
getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext);
|
||||
getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -52,7 +52,7 @@ public class ApplicationServlet extends HttpServlet {
|
|||
PrintWriter pw = resp.getWriter();
|
||||
pw.printf("<html><head><title>%s</title></head><body>", title);
|
||||
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.flush();
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.BeforeClass;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.account.freemarker.model.ApplicationsBean;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventType;
|
||||
|
@ -43,6 +44,7 @@ import org.keycloak.services.resources.AccountService;
|
|||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
||||
import org.keycloak.testsuite.pages.AccountLogPage;
|
||||
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||
import org.keycloak.testsuite.pages.AccountSessionsPage;
|
||||
|
@ -63,6 +65,7 @@ import org.openqa.selenium.WebDriver;
|
|||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -129,6 +132,9 @@ public class AccountTest {
|
|||
@WebResource
|
||||
protected AccountSessionsPage sessionsPage;
|
||||
|
||||
@WebResource
|
||||
protected AccountApplicationsPage applicationsPage;
|
||||
|
||||
@WebResource
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,11 +26,24 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.constants.KerberosConstants;
|
||||
import org.keycloak.events.Details;
|
||||
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.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
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.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
|
@ -69,11 +82,17 @@ public class OAuthGrantTest {
|
|||
@WebResource
|
||||
protected OAuthGrantPage grantPage;
|
||||
|
||||
@WebResource
|
||||
protected AccountApplicationsPage accountAppsPage;
|
||||
|
||||
@WebResource
|
||||
protected AppPage appPage;
|
||||
|
||||
private static String ROLE_USER = "Have User privileges";
|
||||
private static String ROLE_CUSTOMER = "Have Customer User privileges";
|
||||
|
||||
@Test
|
||||
public void oauthGrantAcceptTest() throws IOException {
|
||||
public void oauthGrantAcceptTest() {
|
||||
oauth.clientId("third-party");
|
||||
oauth.doLoginGrant("test-user@localhost", "password");
|
||||
|
||||
|
@ -106,10 +125,16 @@ public class OAuthGrantTest {
|
|||
Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
|
||||
|
||||
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
|
||||
public void oauthGrantCancelTest() throws IOException {
|
||||
public void oauthGrantCancelTest() {
|
||||
oauth.clientId("third-party");
|
||||
oauth.doLoginGrant("test-user@localhost", "password");
|
||||
|
||||
|
@ -125,4 +150,118 @@ public class OAuthGrantTest {
|
|||
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);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,62 +37,81 @@ public class AccountApplicationsPage extends AbstractAccountPage {
|
|||
driver.findElement(By.id("revoke-" + clientId)).click();
|
||||
}
|
||||
|
||||
public Map<String, ClientGrant> getClientGrants() {
|
||||
Map<String, ClientGrant> table = new HashMap<String, ClientGrant>();
|
||||
public Map<String, AppEntry> getApplications() {
|
||||
Map<String, AppEntry> table = new HashMap<String, AppEntry>();
|
||||
for (WebElement r : driver.findElements(By.tagName("tr"))) {
|
||||
int count = 0;
|
||||
ClientGrant currentGrant = null;
|
||||
AppEntry currentEntry = null;
|
||||
|
||||
for (WebElement col : r.findElements(By.tagName("td"))) {
|
||||
count++;
|
||||
switch (count) {
|
||||
case 1:
|
||||
currentGrant = new ClientGrant();
|
||||
String clientId = col.getText();
|
||||
table.put(clientId, currentGrant);
|
||||
currentEntry = new AppEntry();
|
||||
String client = col.getText();
|
||||
table.put(client, currentEntry);
|
||||
break;
|
||||
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[] roles = rolesStr.split(",");
|
||||
for (String role : roles) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
table.remove("Client");
|
||||
table.remove("Application");
|
||||
return table;
|
||||
}
|
||||
|
||||
public static class ClientGrant {
|
||||
public static class AppEntry {
|
||||
|
||||
private final List<String> protocolMapperDescriptions = new ArrayList<String>();
|
||||
private final List<String> roleDescriptions = new ArrayList<String>();
|
||||
private final List<String> rolesAvailable = 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) {
|
||||
protocolMapperDescriptions.add(protocolMapper);
|
||||
protocolMappersGranted.add(protocolMapper);
|
||||
}
|
||||
|
||||
private void addRole(String role) {
|
||||
roleDescriptions.add(role);
|
||||
public List<String> getRolesGranted() {
|
||||
return rolesGranted;
|
||||
}
|
||||
|
||||
public List<String> getProtocolMapperDescriptions() {
|
||||
return protocolMapperDescriptions;
|
||||
public List<String> getRolesAvailable() {
|
||||
return rolesAvailable;
|
||||
}
|
||||
|
||||
public List<String> getRoleDescriptions() {
|
||||
return roleDescriptions;
|
||||
public List<String> getProtocolMappersGranted() {
|
||||
return protocolMappersGranted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue