Prevent user from removing built-in client scopes (#27134)
Closes #26937 Signed-off-by: Kaustubh B <kbawanka@redhat.com>
This commit is contained in:
parent
83af01c4c0
commit
03f6cda85a
5 changed files with 57 additions and 27 deletions
|
@ -282,6 +282,7 @@ samlKeysExportError=无法导出密钥,因为:{{error}}
|
||||||
webAuthnPolicyCreateTimeout=超时
|
webAuthnPolicyCreateTimeout=超时
|
||||||
comparison=对比
|
comparison=对比
|
||||||
deletedSuccessClientScope=客户端作用域已删除
|
deletedSuccessClientScope=客户端作用域已删除
|
||||||
|
notAllowedToDeleteAllClientScopes=您不能删除所有客户端作用域
|
||||||
notBeforeError=清除领域的“不早于”时出错\: {{error}}
|
notBeforeError=清除领域的“不早于”时出错\: {{error}}
|
||||||
columnDisplayName=展示名称
|
columnDisplayName=展示名称
|
||||||
noUsersFoundErrorStorage=找不到用户,可能是由于错误配置了联合提供程序{{error}}
|
noUsersFoundErrorStorage=找不到用户,可能是由于错误配置了联合提供程序{{error}}
|
||||||
|
|
|
@ -305,6 +305,7 @@ webAuthnPolicyCreateTimeout=Timeout
|
||||||
comparison=Comparison
|
comparison=Comparison
|
||||||
passwordPoliciesHelp.digits=The number of numerical digits required in the password string.
|
passwordPoliciesHelp.digits=The number of numerical digits required in the password string.
|
||||||
deletedSuccessClientScope=The client scope has been deleted
|
deletedSuccessClientScope=The client scope has been deleted
|
||||||
|
notAllowedToDeleteAllClientScopes=You are not allowed to delete all the client scopes.
|
||||||
columnDisplayName=Display name
|
columnDisplayName=Display name
|
||||||
noUsersFoundErrorStorage=No users found, could be due to wrongly configured federated provider {{error}}
|
noUsersFoundErrorStorage=No users found, could be due to wrongly configured federated provider {{error}}
|
||||||
lookAround=Look around window
|
lookAround=Look around window
|
||||||
|
|
|
@ -157,6 +157,9 @@ export default function ClientScopesSection() {
|
||||||
continueButtonLabel: "delete",
|
continueButtonLabel: "delete",
|
||||||
continueButtonVariant: ButtonVariant.danger,
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
|
const clientScopes = await adminClient.clientScopes.find();
|
||||||
|
const clientScopeLength = Object.keys(clientScopes).length;
|
||||||
|
if (clientScopeLength - selectedScopes.length > 0) {
|
||||||
try {
|
try {
|
||||||
for (const scope of selectedScopes) {
|
for (const scope of selectedScopes) {
|
||||||
try {
|
try {
|
||||||
|
@ -174,6 +177,9 @@ export default function ClientScopesSection() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("deleteErrorClientScope", error);
|
addError("deleteErrorClientScope", error);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
addError(t("notAllowedToDeleteAllClientScopes"), "error");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -148,8 +149,10 @@ public class ClientScopeResource {
|
||||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENT_SCOPES)
|
@Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENT_SCOPES)
|
||||||
@Operation(summary = "Delete the client scope")
|
@Operation(summary = "Delete the client scope")
|
||||||
public Response deleteClientScope() {
|
public Response deleteClientScope() {
|
||||||
auth.clients().requireManage(clientScope);
|
|
||||||
|
|
||||||
|
auth.clients().requireManage(clientScope);
|
||||||
|
long clientScopesCount = Arrays.stream(realm.getClientScopesStream().toArray()).count();
|
||||||
|
if (clientScopesCount > 1) {
|
||||||
try {
|
try {
|
||||||
realm.removeClientScope(clientScope.getId());
|
realm.removeClientScope(clientScope.getId());
|
||||||
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
|
adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).success();
|
||||||
|
@ -157,6 +160,9 @@ public class ClientScopeResource {
|
||||||
} catch (ModelException me) {
|
} catch (ModelException me) {
|
||||||
throw ErrorResponse.error(me.getMessage(), Response.Status.BAD_REQUEST);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -33,12 +33,7 @@ import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.*;
|
||||||
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.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
import org.keycloak.testsuite.arquillian.annotation.DisableFeature;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
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) {
|
private void handleExpectedCreateFailure(ClientScopeRepresentation scopeRep, int expectedErrorCode, String expectedErrorMessage) {
|
||||||
try(Response resp = clientScopes().create(scopeRep)) {
|
try(Response resp = clientScopes().create(scopeRep)) {
|
||||||
Assert.assertEquals(expectedErrorCode, resp.getStatus());
|
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);
|
assertAdminEvents.assertEvent(getRealmId(), OperationType.DELETE, AdminEventPaths.clientScopeResourcePath(clientScopeId), ResourceType.CLIENT_SCOPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeClientScopeMustFail(String clientScopeId) {
|
||||||
|
try {
|
||||||
|
clientScopes().get(clientScopeId).remove();
|
||||||
|
} catch (Exception expected) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue