mirror of
https://github.com/keycloak/keycloak.git
synced 2026-02-18 10:27:52 -05:00
Missing anti-ID phishing check for getting client (#46056)
* Missing anti-ID phishing check for getting client Closes #46010 Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Avoid any other phishing based on error message, for PATCH + improve service exceptions Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Ensure no ID phishing for DELETE Signed-off-by: Martin Bartoš <mabartos@redhat.com> --------- Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
19118a097c
commit
92881fb42b
7 changed files with 197 additions and 79 deletions
|
|
@ -2,6 +2,7 @@ package org.keycloak.services;
|
|||
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
|
@ -20,8 +21,25 @@ public class ServiceException extends RuntimeException {
|
|||
this.suggestedHttpResponseStatus = suggestedStatus;
|
||||
}
|
||||
|
||||
public ServiceException(Response.Status suggestedStatus) {
|
||||
super();
|
||||
this.suggestedHttpResponseStatus = suggestedStatus;
|
||||
}
|
||||
|
||||
public Optional<Response.Status> getSuggestedResponseStatus() {
|
||||
return Optional.ofNullable(suggestedHttpResponseStatus);
|
||||
}
|
||||
|
||||
public WebApplicationException toWebApplicationException() {
|
||||
return toWebApplicationException(Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
public WebApplicationException toWebApplicationException(Response.Status orReturnStatus) {
|
||||
if (getMessage() != null) {
|
||||
return new WebApplicationException(getMessage(), getSuggestedResponseStatus().orElse(orReturnStatus));
|
||||
} else {
|
||||
return new WebApplicationException(getSuggestedResponseStatus().orElse(orReturnStatus));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,12 +27,22 @@ public interface ClientService extends Service {
|
|||
|
||||
record CreateOrUpdateResult(BaseClientRepresentation representation, boolean created) {}
|
||||
|
||||
Optional<BaseClientRepresentation> getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions);
|
||||
default Optional<BaseClientRepresentation> getClient(RealmModel realm, String clientId) throws ServiceException {
|
||||
return getClient(realm, clientId, null);
|
||||
}
|
||||
|
||||
Optional<BaseClientRepresentation> getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions) throws ServiceException;
|
||||
|
||||
default Stream<BaseClientRepresentation> getClients(RealmModel realm) {
|
||||
return getClients(realm, null, null, null);
|
||||
}
|
||||
|
||||
Stream<BaseClientRepresentation> getClients(RealmModel realm, ClientProjectionOptions projectionOptions, ClientSearchOptions searchOptions, ClientSortAndSliceOptions sortAndSliceOptions);
|
||||
|
||||
Stream<BaseClientRepresentation> deleteClients(RealmModel realm, ClientSearchOptions searchOptions);
|
||||
|
||||
void deleteClient(RealmModel realm, String clientId) throws ServiceException;
|
||||
|
||||
CreateOrUpdateResult createOrUpdate(RealmModel realm, BaseClientRepresentation client, boolean allowUpdate) throws ServiceException;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
|
|
@ -25,33 +27,54 @@ import org.keycloak.services.ServiceException;
|
|||
import org.keycloak.services.resources.admin.ClientResource;
|
||||
import org.keycloak.services.resources.admin.ClientsResource;
|
||||
import org.keycloak.services.resources.admin.RealmAdminResource;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;
|
||||
import org.keycloak.validation.jakarta.HibernateValidatorProvider;
|
||||
import org.keycloak.validation.jakarta.JakartaValidatorProvider;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
// TODO
|
||||
public class DefaultClientService implements ClientService {
|
||||
private final KeycloakSession session;
|
||||
private final JakartaValidatorProvider validator;
|
||||
private final RealmAdminResource realmAdminResource;
|
||||
private final AdminPermissionEvaluator permissions;
|
||||
|
||||
// v1 resources
|
||||
private final RealmAdminResource realmResource;
|
||||
private final ClientsResource clientsResource;
|
||||
private ClientResource clientResource;
|
||||
|
||||
public DefaultClientService(KeycloakSession session, RealmAdminResource realmAdminResource, ClientResource clientResource) {
|
||||
public DefaultClientService(@Nonnull KeycloakSession session,
|
||||
@Nonnull AdminPermissionEvaluator permissions,
|
||||
@Nonnull RealmAdminResource realmResource,
|
||||
@Nullable ClientResource clientResource) {
|
||||
this.session = session;
|
||||
this.realmAdminResource = realmAdminResource;
|
||||
this.clientResource = clientResource;
|
||||
|
||||
this.clientsResource = realmAdminResource.getClients();
|
||||
this.permissions = permissions;
|
||||
this.validator = new HibernateValidatorProvider();
|
||||
|
||||
this.realmResource = realmResource;
|
||||
this.clientsResource = realmResource.getClients();
|
||||
this.clientResource = clientResource;
|
||||
}
|
||||
|
||||
public DefaultClientService(KeycloakSession session, RealmAdminResource realmAdminResource) {
|
||||
this(session, realmAdminResource, null);
|
||||
public DefaultClientService(@Nonnull KeycloakSession session,
|
||||
@Nonnull AdminPermissionEvaluator permissions,
|
||||
@Nonnull RealmAdminResource realmResource) {
|
||||
this(session, permissions, realmResource, null);
|
||||
}
|
||||
|
||||
protected void avoidClientIdPhishing() throws ServiceException {
|
||||
if (clientResource == null && !permissions.clients().canList()) {
|
||||
// we do this to make sure somebody can't phish client IDs
|
||||
throw new ServiceException(Response.Status.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BaseClientRepresentation> getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions) {
|
||||
public Optional<BaseClientRepresentation> getClient(RealmModel realm, String clientId, ClientProjectionOptions projectionOptions) throws ServiceException {
|
||||
// TODO: is the access map on the representation needed
|
||||
avoidClientIdPhishing();
|
||||
return Optional.ofNullable(clientResource).map(ClientResource::viewClientModel)
|
||||
.map(model -> session.getProvider(ClientModelMapper.class, model.getProtocol()).fromModel(model));
|
||||
}
|
||||
|
|
@ -82,7 +105,11 @@ public class DefaultClientService implements ClientService {
|
|||
}
|
||||
model = mapper.toModel(client, clientResource.viewClientModel());
|
||||
var rep = ModelToRepresentation.toRepresentation(model, session);
|
||||
clientResource.update(rep);
|
||||
|
||||
try (var response = clientResource.update(rep)) {
|
||||
// close response and consume payload due to performance reasons
|
||||
EntityUtils.consumeQuietly((HttpEntity) response.getEntity());
|
||||
}
|
||||
} else {
|
||||
created = true;
|
||||
validator.validate(client, CreateClientDefault.class); // TODO improve it to avoid second validation when we know it is create and not update
|
||||
|
|
@ -115,6 +142,15 @@ public class DefaultClientService implements ClientService {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteClient(RealmModel realm, String clientId) throws ServiceException {
|
||||
avoidClientIdPhishing();
|
||||
if (clientResource == null) {
|
||||
throw new ServiceException("Cannot find the specified client", Response.Status.NOT_FOUND);
|
||||
}
|
||||
clientResource.deleteClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declaratively manage client roles - ensures the client has exactly the roles specified in 'rolesFromRep'
|
||||
* <p>
|
||||
|
|
@ -133,7 +169,12 @@ public class DefaultClientService implements ClientService {
|
|||
// Add missing roles (in desiredRoleNames but not in currentRoleNames)
|
||||
desiredRoleNames.stream()
|
||||
.filter(roleName -> !currentRoleNames.contains(roleName))
|
||||
.forEach(roleName -> roleResource.createRole(new RoleRepresentation(roleName, "", false)));
|
||||
.forEach(roleName -> {
|
||||
try (var response = roleResource.createRole(new RoleRepresentation(roleName, "", false))) {
|
||||
// close response and consume payload due to performance reasons
|
||||
EntityUtils.consumeQuietly((HttpEntity) response.getEntity());
|
||||
}
|
||||
});
|
||||
|
||||
// Remove extra roles (in currentRoleNames but not in desiredRoleNames)
|
||||
currentRoleNames.stream()
|
||||
|
|
@ -156,10 +197,10 @@ public class DefaultClientService implements ClientService {
|
|||
}
|
||||
|
||||
var clientRoleResource = clientResource.getRoleContainerResource();
|
||||
var realmRoleResource = realmAdminResource.getRoleContainerResource();
|
||||
var realmRoleResource = realmResource.getRoleContainerResource();
|
||||
|
||||
var serviceAccountUser = session.users().getServiceAccount(model);
|
||||
var serviceAccountRoleResource = realmAdminResource.users().user(clientResource.getServiceAccountUser().getId()).getRoleMappings();
|
||||
var serviceAccountRoleResource = realmResource.users().user(clientResource.getServiceAccountUser().getId()).getRoleMappings();
|
||||
|
||||
Set<String> desiredRoleNames = Optional.ofNullable(rep.getServiceAccountRoles()).orElse(Collections.emptySet());
|
||||
Set<RoleModel> currentRoles = serviceAccountUser.getRoleMappingsStream().collect(Collectors.toSet());
|
||||
|
|
|
|||
|
|
@ -8,29 +8,31 @@ import org.keycloak.admin.api.client.ClientsApi;
|
|||
import org.keycloak.admin.api.client.DefaultClientsApi;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.resources.admin.AdminAuth;
|
||||
import org.keycloak.services.resources.admin.AdminRoot;
|
||||
import org.keycloak.services.resources.admin.RealmAdminResource;
|
||||
import org.keycloak.services.resources.admin.RealmsAdminResource;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissions;
|
||||
|
||||
public class DefaultAdminApi implements AdminApi {
|
||||
private final KeycloakSession session;
|
||||
private final RealmsAdminResource realmsAdminResource;
|
||||
private final AdminPermissionEvaluator permissions;
|
||||
|
||||
// v1 resources
|
||||
private final RealmAdminResource realmAdminResource;
|
||||
private final AdminAuth auth;
|
||||
|
||||
public DefaultAdminApi(KeycloakSession session, String realmName) {
|
||||
this.session = session;
|
||||
this.auth = AdminRoot.authenticateRealmAdminRequest(session);
|
||||
this.realmsAdminResource = new RealmsAdminResource(session, auth, new TokenManager());
|
||||
this.realmAdminResource = realmsAdminResource.getRealmAdmin(realmName);
|
||||
var authInfo = AdminRoot.authenticateRealmAdminRequest(session);
|
||||
this.permissions = AdminPermissions.evaluator(session, authInfo.getRealm(), authInfo);
|
||||
this.realmAdminResource = new RealmsAdminResource(session, authInfo, new TokenManager()).getRealmAdmin(realmName);
|
||||
}
|
||||
|
||||
@Path("clients/{version:v\\d+}")
|
||||
@Override
|
||||
public ClientsApi clients(@PathParam("version") String version) {
|
||||
return switch (version) {
|
||||
case "v2" -> new DefaultClientsApi(session, realmAdminResource);
|
||||
case "v2" -> new DefaultClientsApi(session, permissions, realmAdminResource);
|
||||
default -> throw new NotFoundException();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package org.keycloak.admin.api.client;
|
|||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
|
|
@ -23,6 +25,7 @@ import org.keycloak.services.client.ClientService;
|
|||
import org.keycloak.services.client.DefaultClientService;
|
||||
import org.keycloak.services.resources.admin.ClientResource;
|
||||
import org.keycloak.services.resources.admin.RealmAdminResource;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.util.ObjectMapperResolver;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
|
@ -31,33 +34,35 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
|
||||
public class DefaultClientApi implements ClientApi {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
private final ClientService clientService;
|
||||
|
||||
private final ClientResource clientResource;
|
||||
private final String clientId;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapperResolver().getContext(null);
|
||||
|
||||
public DefaultClientApi(KeycloakSession session, RealmAdminResource realmAdminResource, ClientResource clientResource, String clientId) {
|
||||
private final KeycloakSession session;
|
||||
private final String clientId;
|
||||
private final RealmModel realm;
|
||||
private final ClientService clientService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public DefaultClientApi(@Nonnull KeycloakSession session,
|
||||
@Nonnull String clientId,
|
||||
@Nonnull AdminPermissionEvaluator permissions,
|
||||
@Nonnull RealmAdminResource realmAdminResource,
|
||||
@Nullable ClientResource clientResource) {
|
||||
this.session = session;
|
||||
this.clientResource = clientResource;
|
||||
this.clientId = clientId;
|
||||
|
||||
this.clientService = new DefaultClientService(session, permissions, realmAdminResource, clientResource);
|
||||
this.realm = Objects.requireNonNull(session.getContext().getRealm());
|
||||
this.clientService = new DefaultClientService(session, realmAdminResource, clientResource);
|
||||
|
||||
this.objectMapper = MAPPER;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Override
|
||||
public BaseClientRepresentation getClient() {
|
||||
return clientService.getClient(realm, clientId, null)
|
||||
.orElseThrow(() -> new NotFoundException("Cannot find the specified client"));
|
||||
try {
|
||||
return clientService.getClient(realm, clientId)
|
||||
.orElseThrow(() -> new NotFoundException("Cannot find the specified client"));
|
||||
} catch (ServiceException e) {
|
||||
throw e.toWebApplicationException(Response.Status.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
|
@ -71,7 +76,7 @@ public class DefaultClientApi implements ClientApi {
|
|||
var result = clientService.createOrUpdate(realm, client, true);
|
||||
return Response.status(result.created() ? Response.Status.CREATED : Response.Status.OK).entity(result.representation()).build();
|
||||
} catch (ServiceException e) {
|
||||
throw new WebApplicationException(e.getMessage(), e.getSuggestedResponseStatus().orElse(Response.Status.BAD_REQUEST));
|
||||
throw e.toWebApplicationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,10 +109,11 @@ public class DefaultClientApi implements ClientApi {
|
|||
@DELETE
|
||||
@Override
|
||||
public void deleteClient() {
|
||||
if (clientResource == null) {
|
||||
throw new NotFoundException("Cannot find the specified client");
|
||||
try {
|
||||
clientService.deleteClient(realm, clientId);
|
||||
} catch (ServiceException e) {
|
||||
throw e.toWebApplicationException();
|
||||
}
|
||||
clientResource.deleteClient();
|
||||
}
|
||||
|
||||
static void validateUnknownFields(BaseClientRepresentation rep) {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import java.util.Objects;
|
|||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
|
@ -21,23 +21,30 @@ import org.keycloak.services.client.ClientService;
|
|||
import org.keycloak.services.client.DefaultClientService;
|
||||
import org.keycloak.services.resources.admin.ClientsResource;
|
||||
import org.keycloak.services.resources.admin.RealmAdminResource;
|
||||
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;
|
||||
import org.keycloak.validation.jakarta.HibernateValidatorProvider;
|
||||
import org.keycloak.validation.jakarta.JakartaValidatorProvider;
|
||||
|
||||
public class DefaultClientsApi implements ClientsApi {
|
||||
private final KeycloakSession session;
|
||||
private final AdminPermissionEvaluator permissions;
|
||||
private final RealmModel realm;
|
||||
private final ClientService clientService;
|
||||
private final JakartaValidatorProvider validator;
|
||||
|
||||
// v1 resources
|
||||
private final RealmAdminResource realmAdminResource;
|
||||
private final ClientsResource clientsResource;
|
||||
|
||||
public DefaultClientsApi(KeycloakSession session, RealmAdminResource realmAdminResource) {
|
||||
public DefaultClientsApi(@Nonnull KeycloakSession session,
|
||||
@Nonnull AdminPermissionEvaluator permissions,
|
||||
@Nonnull RealmAdminResource realmAdminResource) {
|
||||
this.session = session;
|
||||
this.permissions = permissions;
|
||||
this.realmAdminResource = realmAdminResource;
|
||||
|
||||
this.realm = Objects.requireNonNull(session.getContext().getRealm());
|
||||
this.clientService = new DefaultClientService(session, realmAdminResource);
|
||||
this.clientService = new DefaultClientService(session, permissions, realmAdminResource);
|
||||
this.validator = new HibernateValidatorProvider();
|
||||
this.clientsResource = realmAdminResource.getClients();
|
||||
}
|
||||
|
|
@ -45,7 +52,7 @@ public class DefaultClientsApi implements ClientsApi {
|
|||
@GET
|
||||
@Override
|
||||
public Stream<BaseClientRepresentation> getClients() {
|
||||
return clientService.getClients(realm, null, null, null);
|
||||
return clientService.getClients(realm);
|
||||
}
|
||||
|
||||
@POST
|
||||
|
|
@ -58,7 +65,7 @@ public class DefaultClientsApi implements ClientsApi {
|
|||
.entity(clientService.createOrUpdate(realm, client, false).representation())
|
||||
.build();
|
||||
} catch (ServiceException e) {
|
||||
throw new WebApplicationException(e.getMessage(), e.getSuggestedResponseStatus().orElse(Response.Status.BAD_REQUEST));
|
||||
throw e.toWebApplicationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +73,7 @@ public class DefaultClientsApi implements ClientsApi {
|
|||
@Override
|
||||
public ClientApi client(@PathParam("id") String clientId) {
|
||||
var client = Optional.ofNullable(session.clients().getClientByClientId(realm, clientId));
|
||||
return new DefaultClientApi(session, realmAdminResource, client.map(c -> clientsResource.getClient(c.getId())).orElse(null), clientId);
|
||||
return new DefaultClientApi(session, clientId, permissions, realmAdminResource, client.map(c -> clientsResource.getClient(c.getId())).orElse(null));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
|
@ -52,7 +53,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
* Based on this spike <a href="https://github.com/keycloak/keycloak/issues/45940#issuecomment-3840875460">GitHub Issue #45940</a>
|
||||
*/
|
||||
@KeycloakIntegrationTest(config = ClientApiV2AuthorizationTest.AdminV2WithAuthzConfig.class)
|
||||
public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
||||
public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test {
|
||||
|
||||
@InjectHttpClient
|
||||
CloseableHttpClient client;
|
||||
|
|
@ -137,7 +138,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep("test-create-realm-admin", "new-role1", "new-role2"))));
|
||||
try (var response = client.execute(request)) {
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(201));
|
||||
}
|
||||
|
||||
// manage-clients: should be able to create clients (has requireManage)
|
||||
|
|
@ -147,7 +148,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep("test-create-manage", "new-role1", "new-role2"))));
|
||||
try (var response = client.execute(request)) {
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(201));
|
||||
}
|
||||
|
||||
// view-clients: should get 403 (lacks requireManage)
|
||||
|
|
@ -156,19 +157,19 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
|
||||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep("test-create-view", "new-role1", "new-role2"))));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// query-clients: should get 403 (lacks requireManage)
|
||||
setAuthHeader(request, adminClients.get("query-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// no-access: should get 403 (lacks requireManage)
|
||||
setAuthHeader(request, adminClients.get("no-access"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,13 +202,13 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
// query-clients: should get 403 (can list but not view individual clients)
|
||||
setAuthHeader(request, adminClients.get("query-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// no-access: should get 403 (lacks requireView)
|
||||
setAuthHeader(request, adminClients.get("no-access"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,28 +223,27 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
// view-clients: should get 404 (has canList, client doesn't exist)
|
||||
setAuthHeader(request, adminClients.get("view-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(404, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(404));
|
||||
}
|
||||
|
||||
// manage-clients: should get 404 (has canList, client doesn't exist)
|
||||
setAuthHeader(request, adminClients.get("manage-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(404, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(404));
|
||||
}
|
||||
|
||||
// query-clients: should get 404 (has canList, client doesn't exist)
|
||||
setAuthHeader(request, adminClients.get("query-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(404, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(404));
|
||||
}
|
||||
|
||||
// TODO - our V2 logic does not handle the anti-ID phishing yet
|
||||
// TODO - issue https://github.com/keycloak/keycloak/issues/46010
|
||||
// no-access: should get 403 (lacks canList, prevents ID phishing)
|
||||
// setAuthHeader(request, clients.get("no-access"));
|
||||
// try (var response = client.execute(request)) {
|
||||
// assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
// }
|
||||
setAuthHeader(request, adminClients.get("no-access"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
assertThat(EntityUtils.toString(response.getEntity()), containsString("HTTP 403 Forbidden"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -259,7 +259,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
|
||||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep("test-update-admin", "role123", "my-role"))));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(200));
|
||||
OIDCClientRepresentation client = mapper.createParser(response.getEntity().getContent())
|
||||
.readValueAs(OIDCClientRepresentation.class);
|
||||
assertThat(client.getClientId(), is("test-update-admin"));
|
||||
|
|
@ -273,7 +273,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
|
||||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep("test-update-manage", "role123", "my-role"))));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(200));
|
||||
OIDCClientRepresentation client = mapper.createParser(response.getEntity().getContent())
|
||||
.readValueAs(OIDCClientRepresentation.class);
|
||||
assertThat(client.getClientId(), is("test-update-manage"));
|
||||
|
|
@ -287,13 +287,13 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
|
||||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep("test-update-view", "role123", "my-role"))));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// query-clients: should get 403 (lacks requireConfigure)
|
||||
setAuthHeader(request, adminClients.get("query-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setEntity(new StringEntity(mapper.writeValueAsString(patch)));
|
||||
try (var response = client.execute(request)) {
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(200));
|
||||
}
|
||||
|
||||
// view-clients: should get 403 (lacks requireConfigure)
|
||||
|
|
@ -323,19 +323,39 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/merge-patch+json");
|
||||
request.setEntity(new StringEntity(mapper.writeValueAsString(patch)));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// query-clients: should get 403 (lacks requireConfigure)
|
||||
setAuthHeader(request, adminClients.get("query-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// manage-clients: should be able to patch clients (has manage-clients)
|
||||
setAuthHeader(request, adminClients.get("manage-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(200));
|
||||
}
|
||||
|
||||
// does not exist
|
||||
request = new HttpPatch(getClientsApiUrl() + "/does-not-exist");
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/merge-patch+json");
|
||||
patch = new OIDCClientRepresentation();
|
||||
patch.setDescription("Patched-non-existing");
|
||||
request.setEntity(new StringEntity(mapper.writeValueAsString(patch)));
|
||||
|
||||
// no-access: not existing - should get 403 (lacks canList, prevents ID phishing)
|
||||
setAuthHeader(request, adminClients.get("no-access"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
assertThat(EntityUtils.toString(response.getEntity()), containsString("HTTP 403 Forbidden"));
|
||||
}
|
||||
|
||||
// view-clients: not existing - should get 404
|
||||
setAuthHeader(request, adminClients.get("view-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(404));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,19 +386,33 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request = new HttpDelete(getClientsApiUrl() + "/test-delete-view");
|
||||
setAuthHeader(request, adminClients.get("view-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// query-clients: should get 403 (lacks requireManage)
|
||||
setAuthHeader(request, adminClients.get("query-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// no-access: should get 403 (lacks requireManage)
|
||||
setAuthHeader(request, adminClients.get("no-access"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
|
||||
// no-access: not existing - should get 403 (lacks canList, prevents ID phishing)
|
||||
request = new HttpDelete(getClientsApiUrl() + "/does-not-exist");
|
||||
setAuthHeader(request, adminClients.get("no-access"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
assertThat(EntityUtils.toString(response.getEntity()), containsString("HTTP 403 Forbidden"));
|
||||
}
|
||||
|
||||
// view-clients: not existing - should get 404
|
||||
setAuthHeader(request, adminClients.get("view-clients"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(404));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +430,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
setAuthHeader(request, adminClients.get("realm-admin"));
|
||||
try (var response = client.execute(request)) {
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(200));
|
||||
}
|
||||
|
||||
// Test accessing non-existent realm - should return 404
|
||||
|
|
@ -404,7 +438,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request = new HttpGet(nonExistentRealmUrl);
|
||||
setAuthHeader(request, adminClients.get("realm-admin"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(404, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(404));
|
||||
}
|
||||
|
||||
// When accessing a different realm from a non-administration realm, should return 403
|
||||
|
|
@ -414,7 +448,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request = new HttpGet(differentRealmUrl);
|
||||
setAuthHeader(request, adminClients.get("realm-admin"));
|
||||
try (var response = client.execute(request)) {
|
||||
assertEquals(403, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(403));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -433,7 +467,7 @@ public class ClientApiV2AuthorizationTest extends AbstractClientApiV2Test{
|
|||
request.setEntity(new StringEntity(mapper.writeValueAsString(createClientRep(clientId, "test-role1", "test-role2"))));
|
||||
try (var response = client.execute(request)) {
|
||||
EntityUtils.consumeQuietly(response.getEntity());
|
||||
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||
assertThat(response.getStatusLine().getStatusCode(), is(201));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue