Prevent user from removing built-in client scopes (#27134)

Closes #26937

Signed-off-by: Kaustubh B <kbawanka@redhat.com>
This commit is contained in:
kaustubh-rh 2024-02-26 15:46:23 +05:30 committed by GitHub
parent 83af01c4c0
commit 03f6cda85a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 57 additions and 27 deletions

View file

@ -282,6 +282,7 @@ samlKeysExportError=无法导出密钥,因为:{{error}}
webAuthnPolicyCreateTimeout=超时
comparison=对比
deletedSuccessClientScope=客户端作用域已删除
notAllowedToDeleteAllClientScopes=您不能删除所有客户端作用域
notBeforeError=清除领域的“不早于”时出错\: {{error}}
columnDisplayName=展示名称
noUsersFoundErrorStorage=找不到用户,可能是由于错误配置了联合提供程序{{error}}

View file

@ -305,6 +305,7 @@ webAuthnPolicyCreateTimeout=Timeout
comparison=Comparison
passwordPoliciesHelp.digits=The number of numerical digits required in the password string.
deletedSuccessClientScope=The client scope has been deleted
notAllowedToDeleteAllClientScopes=You are not allowed to delete all the client scopes.
columnDisplayName=Display name
noUsersFoundErrorStorage=No users found, could be due to wrongly configured federated provider {{error}}
lookAround=Look around window

View file

@ -157,22 +157,28 @@ export default function ClientScopesSection() {
continueButtonLabel: "delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
for (const scope of selectedScopes) {
try {
await removeScope(scope);
} catch (error: any) {
console.warn(
"could not remove scope",
error.response?.data?.errorMessage || error,
);
const clientScopes = await adminClient.clientScopes.find();
const clientScopeLength = Object.keys(clientScopes).length;
if (clientScopeLength - selectedScopes.length > 0) {
try {
for (const scope of selectedScopes) {
try {
await removeScope(scope);
} catch (error: any) {
console.warn(
"could not remove scope",
error.response?.data?.errorMessage || error,
);
}
await adminClient.clientScopes.del({ id: scope.id! });
}
await adminClient.clientScopes.del({ id: scope.id! });
addAlert(t("deletedSuccessClientScope"), AlertVariant.success);
refresh();
} catch (error) {
addError("deleteErrorClientScope", error);
}
addAlert(t("deletedSuccessClientScope"), AlertVariant.success);
refresh();
} catch (error) {
addError("deleteErrorClientScope", error);
} else {
addError(t("notAllowedToDeleteAllClientScopes"), "error");
}
},
});

View file

@ -47,6 +47,7 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Pattern;
@ -148,14 +149,19 @@ public class ClientScopeResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENT_SCOPES)
@Operation(summary = "Delete the client scope")
public Response deleteClientScope() {
auth.clients().requireManage(clientScope);
try {
realm.removeClientScope(clientScope.getId());
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
return Response.noContent().build();
} catch (ModelException me) {
throw ErrorResponse.error(me.getMessage(), Response.Status.BAD_REQUEST);
auth.clients().requireManage(clientScope);
long clientScopesCount = Arrays.stream(realm.getClientScopesStream().toArray()).count();
if (clientScopesCount > 1) {
try {
realm.removeClientScope(clientScope.getId());
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
return Response.noContent().build();
} catch (ModelException me) {
throw ErrorResponse.error(me.getMessage(), Response.Status.BAD_REQUEST);
}
} else {
throw ErrorResponse.error("You are not allowed to delete all the client scopes.", Response.Status.FORBIDDEN);
}
}

View file

@ -33,12 +33,7 @@ import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.*;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
@ -841,6 +836,19 @@ public class ClientScopeTest extends AbstractClientTest {
}
@Test
public void deleteAllClientScopesMustFail() {
List<ClientScopeRepresentation> clientScopes = clientScopes().findAll();
for (int i = 0; i < clientScopes.size(); i++) {
ClientScopeRepresentation clientScope = clientScopes.get(i);
if (i != clientScopes.size() - 1) {
removeClientScope(clientScope.getId());
} else {
removeClientScopeMustFail(clientScope.getId());
}
}
}
private void handleExpectedCreateFailure(ClientScopeRepresentation scopeRep, int expectedErrorCode, String expectedErrorMessage) {
try(Response resp = clientScopes().create(scopeRep)) {
Assert.assertEquals(expectedErrorCode, resp.getStatus());
@ -876,4 +884,12 @@ public class ClientScopeTest extends AbstractClientTest {
assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE, AdminEventPaths.clientScopeResourcePath(clientScopeId), ResourceType.CLIENT_SCOPE);
}
private void removeClientScopeMustFail(String clientScopeId) {
try {
clientScopes().get(clientScopeId).remove();
} catch (Exception expected) {
}
}
}