KEYCLOAK-8495: Account REST Svc doesn't require acct roles

This commit is contained in:
Stan Silvert 2018-12-11 20:03:05 -05:00 committed by Stian Thorgersen
parent a7f57c7e23
commit 3ed77825a2
3 changed files with 49 additions and 2 deletions

View file

@ -19,7 +19,9 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
public class AccountCredentialResource { public class AccountCredentialResource {
@ -28,11 +30,13 @@ public class AccountCredentialResource {
private final EventBuilder event; private final EventBuilder event;
private final UserModel user; private final UserModel user;
private final RealmModel realm; 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.session = session;
this.event = event; this.event = event;
this.user = user; this.user = user;
this.auth = auth;
realm = session.getContext().getRealm(); realm = session.getContext().getRealm();
} }
@ -40,6 +44,8 @@ public class AccountCredentialResource {
@Path("password") @Path("password")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public PasswordDetails passwordDetails() { public PasswordDetails passwordDetails() {
auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider) session.getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID); PasswordCredentialProvider passwordProvider = (PasswordCredentialProvider) session.getProvider(CredentialProvider.class, PasswordCredentialProviderFactory.PROVIDER_ID);
CredentialModel password = passwordProvider.getPassword(realm, user); CredentialModel password = passwordProvider.getPassword(realm, user);
@ -58,6 +64,8 @@ public class AccountCredentialResource {
@Path("password") @Path("password")
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
public Response passwordUpdate(PasswordUpdate update) { public Response passwordUpdate(PasswordUpdate update) {
auth.require(AccountRoles.MANAGE_ACCOUNT);
event.event(EventType.UPDATE_PASSWORD); event.event(EventType.UPDATE_PASSWORD);
UserCredentialModel cred = UserCredentialModel.password(update.getCurrentPassword()); UserCredentialModel cred = UserCredentialModel.password(update.getCurrentPassword());

View file

@ -207,6 +207,8 @@ public class AccountRestService {
@NoCache @NoCache
public Response sessions() { public Response sessions() {
checkAccountApiEnabled(); checkAccountApiEnabled();
auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
List<SessionRepresentation> reps = new LinkedList<>(); List<SessionRepresentation> reps = new LinkedList<>();
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user); List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
@ -245,6 +247,8 @@ public class AccountRestService {
@NoCache @NoCache
public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) { public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) {
checkAccountApiEnabled(); checkAccountApiEnabled();
auth.require(AccountRoles.MANAGE_ACCOUNT);
UserSessionModel userSession = auth.getSession(); UserSessionModel userSession = auth.getSession();
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user); List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
@ -269,6 +273,8 @@ public class AccountRestService {
@NoCache @NoCache
public Response sessionLogout(@QueryParam("id") String id) { public Response sessionLogout(@QueryParam("id") String id) {
checkAccountApiEnabled(); checkAccountApiEnabled();
auth.require(AccountRoles.MANAGE_ACCOUNT);
UserSessionModel userSession = session.sessions().getUserSession(realm, id); UserSessionModel userSession = session.sessions().getUserSession(realm, id);
if (userSession != null && userSession.getUser().equals(user)) { if (userSession != null && userSession.getUser().equals(user)) {
AuthenticationManager.backchannelLogout(session, userSession, true); AuthenticationManager.backchannelLogout(session, userSession, true);
@ -279,7 +285,7 @@ public class AccountRestService {
@Path("/credentials") @Path("/credentials")
public AccountCredentialResource credentials() { public AccountCredentialResource credentials() {
checkAccountApiEnabled(); checkAccountApiEnabled();
return new AccountCredentialResource(session, event, user); return new AccountCredentialResource(session, event, user, auth);
} }
// TODO Federated identities // TODO Federated identities

View file

@ -45,6 +45,7 @@ import static org.junit.Assert.*;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import static org.keycloak.common.Profile.Feature.ACCOUNT_API; import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled; import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
/** /**
@ -220,6 +221,38 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
// Update with read only // Update with read only
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(viewToken.getToken()).json(new UserRepresentation()).asStatus()); 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 @Test
public void testUpdateProfilePermissions() throws IOException { public void testUpdateProfilePermissions() throws IOException {