From 2aa9b2b2a0d4e7d7e57026d9e149b77b648da57c Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Mon, 15 Jul 2024 17:13:42 +0200 Subject: [PATCH] Temp admin labels Signed-off-by: Peter Zaoral --- .../admin/messages/messages_en.properties | 3 ++- .../admin-ui/src/clients/ClientsSection.tsx | 10 ++++++++++ .../src/components/users/UserDataTable.tsx | 18 +++++++++++++++--- .../admin/ui/rest/BruteForceUsersResource.java | 8 ++++++++ .../services/managers/ApplianceBootstrap.java | 4 +--- .../resources/admin/ClientsResource.java | 15 +++++++++++++++ 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index 318b28b7f4f7..cb818e8541c7 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -3217,4 +3217,5 @@ forgotPasswordHelp=Specifies independent timeout for forgot password. executeActionsHelp=Specifies independent timeout for execute actions. validatingX509CertsHelp=The public certificates Keycloak uses to validate the signatures of SAML requests and responses from the external IDP when Use metadata descriptor URL is OFF. Multiple certificates can be entered separated by comma (,). The certificates can be re-imported from the Metadata descriptor URL clicking the Import Keys action in the identity provider page. The action downloads the current certificates in the metadata endpoint and assigns them to the config in this same option. You need to click Save to definitely store the re-imported certificates. loggedInAsTempAdminUser=You are logged in as a temporary admin user. To harden security, create a permanent admin account and delete the temporary one. -temporaryAdmin=temporary admin \ No newline at end of file +temporaryAdmin=Temporary admin user account. Ensure it is replaced with a permanent admin user account as soon as possible. +temporaryService=Temporary admin service account. Ensure it is replaced with a permanent admin service account as soon as possible. \ No newline at end of file diff --git a/js/apps/admin-ui/src/clients/ClientsSection.tsx b/js/apps/admin-ui/src/clients/ClientsSection.tsx index 4639c1544442..9143badeac16 100644 --- a/js/apps/admin-ui/src/clients/ClientsSection.tsx +++ b/js/apps/admin-ui/src/clients/ClientsSection.tsx @@ -10,7 +10,9 @@ import { Tab, TabTitleText, ToolbarItem, + Tooltip, } from "@patternfly/react-core"; +import { WarningTriangleIcon } from "@patternfly/react-icons"; import { IFormatter, IFormatterValueType, @@ -71,6 +73,14 @@ const ClientDetailLink = (client: ClientRepresentation) => { )} + {client.attributes?.["temporary_admin"] && ( + + + + )} ); }; diff --git a/js/apps/admin-ui/src/components/users/UserDataTable.tsx b/js/apps/admin-ui/src/components/users/UserDataTable.tsx index cf0404c965fa..afb948dd9f96 100644 --- a/js/apps/admin-ui/src/components/users/UserDataTable.tsx +++ b/js/apps/admin-ui/src/components/users/UserDataTable.tsx @@ -48,11 +48,23 @@ export type UserAttribute = { }; const UserDetailLink = (user: BruteUser) => { + const { t } = useTranslation(); const { realm } = useRealm(); return ( - - {user.username} - + <> + + {user.username} + + + {user.attributes?.["temporary_admin"] && ( + + + + )} + ); }; diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java index 12c70a8a4b16..b426b22facbc 100644 --- a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java +++ b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/BruteForceUsersResource.java @@ -29,6 +29,8 @@ import org.keycloak.services.resources.admin.permissions.UserPermissionEvaluator; import org.keycloak.utils.SearchQueryUtils; +import static org.keycloak.services.managers.ApplianceBootstrap.TEMP_ADMIN_ATTR_NAME; + public class BruteForceUsersResource { private static final Logger logger = Logger.getLogger(BruteForceUsersResource.class); private static final String SEARCH_ID_PARAMETER = "id:"; @@ -167,6 +169,12 @@ private Stream toRepresentation(RealmModel realm, UserPermissionEvalu ModelToRepresentation.toBriefRepresentation(user) : ModelToRepresentation.toRepresentation(session, realm, user); userRep.setAccess(usersEvaluator.getAccess(user)); + if (Boolean.parseBoolean(user.getFirstAttribute(TEMP_ADMIN_ATTR_NAME))) { + if (userRep.getAttributes() == null) { + userRep.setAttributes(new HashMap<>()); + } + userRep.getAttributes().put(TEMP_ADMIN_ATTR_NAME, Collections.singletonList("true")); + } return userRep; }).map(this::getBruteForceStatus); } diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java index 11e088f55763..72018d98c389 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java @@ -155,15 +155,13 @@ public void createTemporaryMasterRealmAdminService(String clientId, String clien adminClient.setSecret(clientSecret); ClientModel adminClientModel = ClientManager.createClient(session, realm, adminClient); + adminClientModel.setAttribute(TEMP_ADMIN_ATTR_NAME, Boolean.TRUE.toString()); new ClientManager(new RealmManager(session)).enableServiceAccount(adminClientModel); UserModel serviceAccount = session.users().getServiceAccount(adminClientModel); RoleModel adminRole = realm.getRole(AdminRoles.ADMIN); serviceAccount.grantRole(adminRole); - serviceAccount.setSingleAttribute(TEMP_ADMIN_ATTR_NAME, Boolean.TRUE.toString()); - // also set the expiration - could be relative to a creation timestamp, or computed - ServicesLogger.LOGGER.createdTemporaryAdminService(clientId); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index aa665bb2ff04..5b5d046abf7b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -61,10 +61,13 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; + +import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; import static java.lang.Boolean.TRUE; +import static org.keycloak.services.managers.ApplianceBootstrap.TEMP_ADMIN_ATTR_NAME; import static org.keycloak.utils.StreamsUtil.paginatedStream; /** @@ -144,14 +147,17 @@ public Stream getClients(@Parameter(description = "filter Stream s = ModelToRepresentation.filterValidRepresentations(clientModels, c -> { ClientRepresentation representation = null; + boolean isTemporaryAdmin = Boolean.parseBoolean(c.getAttribute(TEMP_ADMIN_ATTR_NAME)); if (canView || auth.clients().canView(c)) { representation = ModelToRepresentation.toRepresentation(c, session); representation.setAccess(auth.clients().getAccess(c)); + addTemporaryAdminAttribute(isTemporaryAdmin, representation); } else if (!viewableOnly && auth.clients().canView(c)) { representation = new ClientRepresentation(); representation.setId(c.getId()); representation.setClientId(c.getClientId()); representation.setDescription(c.getDescription()); + addTemporaryAdminAttribute(isTemporaryAdmin, representation); } return representation; @@ -164,6 +170,15 @@ public Stream getClients(@Parameter(description = "filter return s; } + private void addTemporaryAdminAttribute(boolean isTemporaryAdmin, ClientRepresentation representation) { + if (isTemporaryAdmin) { + if (representation.getAttributes() == null) { + representation.setAttributes(new HashMap<>()); + } + representation.getAttributes().put(TEMP_ADMIN_ATTR_NAME, "true"); + } + } + private AuthorizationService getAuthorizationService(ClientModel clientModel) { return new AuthorizationService(session, clientModel, auth, adminEvent); }