Details
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java b/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java
index 9078987..cbd5054 100644
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java
@@ -19,7 +19,9 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
+import org.keycloak.models.AccountRoles;
import org.keycloak.models.ModelException;
+import org.keycloak.services.managers.Auth;
import org.keycloak.services.messages.Messages;
public class AccountCredentialResource {
@@ -28,11 +30,13 @@ public class AccountCredentialResource {
private final EventBuilder event;
private final UserModel user;
private final RealmModel realm;
+ private Auth auth;
- public AccountCredentialResource(KeycloakSession session, EventBuilder event, UserModel user) {
+ public AccountCredentialResource(KeycloakSession session, EventBuilder event, UserModel user, Auth auth) {
this.session = session;
this.event = event;
this.user = user;
+ this.auth = auth;
realm = session.getContext().getRealm();
}
@@ -40,6 +44,8 @@ public class AccountCredentialResource {
@Path("password")
@Produces(MediaType.APPLICATION_JSON)
public PasswordDetails passwordDetails() {
+ auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
+
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider) session.getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
CredentialModel password = passwordProvider.getPassword(realm, user);
@@ -58,6 +64,8 @@ public class AccountCredentialResource {
@Path("password")
@Consumes(MediaType.APPLICATION_JSON)
public Response passwordUpdate(PasswordUpdate update) {
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
+
event.event(EventType.UPDATE_PASSWORD);
UserCredentialModel cred = UserCredentialModel.password(update.getCurrentPassword());
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
index 72fe538..ae298b3 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
@@ -207,6 +207,8 @@ public class AccountRestService {
@NoCache
public Response sessions() {
checkAccountApiEnabled();
+ auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
+
List<SessionRepresentation> reps = new LinkedList<>();
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
@@ -245,6 +247,8 @@ public class AccountRestService {
@NoCache
public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) {
checkAccountApiEnabled();
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
+
UserSessionModel userSession = auth.getSession();
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
@@ -269,6 +273,8 @@ public class AccountRestService {
@NoCache
public Response sessionLogout(@QueryParam("id") String id) {
checkAccountApiEnabled();
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
+
UserSessionModel userSession = session.sessions().getUserSession(realm, id);
if (userSession != null && userSession.getUser().equals(user)) {
AuthenticationManager.backchannelLogout(session, userSession, true);
@@ -279,7 +285,7 @@ public class AccountRestService {
@Path("/credentials")
public AccountCredentialResource credentials() {
checkAccountApiEnabled();
- return new AccountCredentialResource(session, event, user);
+ return new AccountCredentialResource(session, event, user, auth);
}
// TODO Federated identities
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
index 0cbda1b..7daccb0 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
@@ -45,6 +45,7 @@ import static org.junit.Assert.*;
import org.keycloak.services.messages.Messages;
import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
+import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
/**
@@ -220,6 +221,38 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
// Update with read only
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(viewToken.getToken()).json(new UserRepresentation()).asStatus());
}
+
+ @Test
+ public void testProfilePreviewPermissions() throws IOException {
+ assumeFeatureEnabled(ACCOUNT_API);
+
+ TokenUtil noaccessToken = new TokenUtil("no-account-access", "password");
+ TokenUtil viewToken = new TokenUtil("view-account-access", "password");
+
+ // Read sessions with no access
+ assertEquals(403, SimpleHttp.doGet(getAccountUrl("sessions"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
+
+ // Delete all sessions with no access
+ assertEquals(403, SimpleHttp.doDelete(getAccountUrl("sessions"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
+
+ // Delete all sessions with read only
+ assertEquals(403, SimpleHttp.doDelete(getAccountUrl("sessions"), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus());
+
+ // Delete single session with no access
+ assertEquals(403, SimpleHttp.doDelete(getAccountUrl("session?id=bogusId"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
+
+ // Delete single session with read only
+ assertEquals(403, SimpleHttp.doDelete(getAccountUrl("session?id=bogusId"), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus());
+
+ // Read password details with no access
+ assertEquals(403, SimpleHttp.doGet(getAccountUrl("credentials/password"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
+
+ // Update password with no access
+ assertEquals(403, SimpleHttp.doPost(getAccountUrl("credentials/password"), client).auth(noaccessToken.getToken()).json(new PasswordUpdate()).asStatus());
+
+ // Update password with read only
+ assertEquals(403, SimpleHttp.doPost(getAccountUrl("credentials/password"), client).auth(viewToken.getToken()).json(new PasswordUpdate()).asStatus());
+ }
@Test
public void testUpdateProfilePermissions() throws IOException {