From 178288f2b8694f00fbb1377354c57955743a72d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Wed, 3 Jul 2024 20:01:44 +0200 Subject: [PATCH] Temp admin banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Václav Muzikář --- .../admin/messages/messages_en.properties | 4 ++- js/apps/admin-ui/src/App.tsx | 4 +-- js/apps/admin-ui/src/Banner.tsx | 19 -------------- js/apps/admin-ui/src/Banners.tsx | 26 +++++++++++++++++++ .../admin-ui/src/context/whoami/WhoAmI.tsx | 4 +++ .../src/defs/whoAmIRepresentation.ts | 1 + .../services/managers/ApplianceBootstrap.java | 6 ++--- .../resources/admin/AdminConsole.java | 14 ++++++++-- 8 files changed, 51 insertions(+), 27 deletions(-) delete mode 100644 js/apps/admin-ui/src/Banner.tsx create mode 100644 js/apps/admin-ui/src/Banners.tsx 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 d6fbdadfc85d..3f0efdf616f6 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 @@ -3206,4 +3206,6 @@ redirectWhenEmailMatchesHelp=Automatically redirect the user to this identity pr emailVerificationHelp=Specifies independent timeout for email verification. idpAccountEmailVerificationHelp=Specifies independent timeout for IdP account email verification. forgotPasswordHelp=Specifies independent timeout for forgot password. -executeActionsHelp=Specifies independent timeout for execute actions. \ No newline at end of file +executeActionsHelp=Specifies independent timeout for execute actions. +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 diff --git a/js/apps/admin-ui/src/App.tsx b/js/apps/admin-ui/src/App.tsx index c6e5f979f83f..4e43bbc88c7c 100644 --- a/js/apps/admin-ui/src/App.tsx +++ b/js/apps/admin-ui/src/App.tsx @@ -26,7 +26,7 @@ import { WhoAmIContextProvider } from "./context/whoami/WhoAmI"; import type { Environment } from "./environment"; import { SubGroups } from "./groups/SubGroupsContext"; import { AuthWall } from "./root/AuthWall"; -import { Banners } from "./Banner"; +import { Banners } from "./Banners"; const AppContexts = ({ children }: PropsWithChildren) => ( @@ -69,10 +69,10 @@ export const App = () => { breadcrumb={} mainContainerId={mainPageContentId} > + }> - diff --git a/js/apps/admin-ui/src/Banner.tsx b/js/apps/admin-ui/src/Banner.tsx deleted file mode 100644 index 090af2f70d2c..000000000000 --- a/js/apps/admin-ui/src/Banner.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Banner, Flex, FlexItem } from "@patternfly/react-core"; -import { ExclamationTriangleIcon } from "@patternfly/react-icons"; - -const WarnBanner = (text: string) => { - return ( - - - - - - {text} - - - ); -}; - -export const Banners = () => { - return WarnBanner("This is a warning"); -}; diff --git a/js/apps/admin-ui/src/Banners.tsx b/js/apps/admin-ui/src/Banners.tsx new file mode 100644 index 000000000000..9ec4c914b141 --- /dev/null +++ b/js/apps/admin-ui/src/Banners.tsx @@ -0,0 +1,26 @@ +import { Banner, Flex, FlexItem } from "@patternfly/react-core"; +import { ExclamationTriangleIcon } from "@patternfly/react-icons"; +import { useWhoAmI } from "./context/whoami/WhoAmI"; +import { useTranslation } from "react-i18next"; + +const WarnBanner = (msg: string) => { + const { t } = useTranslation(); + + return ( + + + + + + {t(msg)} + + + ); +}; + +export const Banners = () => { + const { whoAmI } = useWhoAmI(); + + if (whoAmI.isTemporary()) return WarnBanner("loggedInAsTempAdminUser"); + // more banners in the future? +}; diff --git a/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx b/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx index 7dac7b999d23..0373c6b1f7ad 100644 --- a/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx +++ b/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx @@ -56,6 +56,10 @@ export class WhoAmI { return this.#me.realm_access; } + + public isTemporary(): boolean { + return this.#me?.temporary ?? false; + } } type WhoAmIProps = { diff --git a/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts b/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts index e2560e8d1f71..a61ab37d6544 100644 --- a/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts +++ b/js/libs/keycloak-admin-client/src/defs/whoAmIRepresentation.ts @@ -32,4 +32,5 @@ export default interface WhoAmIRepresentation { locale: string; createRealm: boolean; realm_access: { [key: string]: AccessType[] }; + temporary: boolean; } 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 f8dbab5a9d9e..11e088f55763 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java @@ -45,6 +45,7 @@ public class ApplianceBootstrap { public static final String DEFAULT_TEMP_ADMIN_USERNAME = "temp-admin"; public static final String DEFAULT_TEMP_ADMIN_SERVICE = "temp-admin-service"; public static final int DEFAULT_TEMP_ADMIN_EXPIRATION = 120; + public static final String TEMP_ADMIN_ATTR_NAME = "temporary_admin"; private final KeycloakSession session; @@ -127,8 +128,7 @@ public void createTemporaryMasterRealmAdminUser(String username, String password UserModel adminUser = session.users().addUser(realm, username); adminUser.setEnabled(true); - // TODO: is this appropriate, does it need to be managed? - // adminUser.setSingleAttribute("temporary_admin", Boolean.TRUE.toString()); + adminUser.setSingleAttribute(TEMP_ADMIN_ATTR_NAME, Boolean.TRUE.toString()); // also set the expiration - could be relative to a creation timestamp, or computed UserCredentialModel usrCredModel = UserCredentialModel.password(password); @@ -161,7 +161,7 @@ public void createTemporaryMasterRealmAdminService(String clientId, String clien RoleModel adminRole = realm.getRole(AdminRoles.ADMIN); serviceAccount.grantRole(adminRole); - // TODO: set temporary + 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/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 9c24070d237e..21301fcada28 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -98,6 +98,7 @@ public static class WhoAmI { protected String realm; protected String displayName; protected Locale locale; + protected boolean isTemporary; @JsonProperty("createRealm") protected boolean createRealm; @@ -107,13 +108,14 @@ public static class WhoAmI { public WhoAmI() { } - public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map> realmAccess, Locale locale) { + public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map> realmAccess, Locale locale, boolean isTemporary) { this.userId = userId; this.realm = realm; this.displayName = displayName; this.createRealm = createRealm; this.realmAccess = realmAccess; this.locale = locale; + this.isTemporary = isTemporary; } public String getUserId() { @@ -168,6 +170,14 @@ public void setLocale(Locale locale) { public String getLocaleLanguageTag() { return locale != null ? locale.toLanguageTag() : null; } + + public boolean isTemporary() { + return isTemporary; + } + + public void setTemporary(boolean temporary) { + isTemporary = temporary; + } } /** @@ -269,7 +279,7 @@ public Response whoAmI(@QueryParam("currentRealm") String currentRealm) { .allowedOrigins(authResult.getToken()) .allowedMethods("GET") .auth() - .add(Response.ok(new WhoAmI(user.getId(), realm.getName(), displayName, createRealm, realmAccess, locale))); + .add(Response.ok(new WhoAmI(user.getId(), realm.getName(), displayName, createRealm, realmAccess, locale, Boolean.parseBoolean(user.getFirstAttribute("temporary_admin"))))); } private void addRealmAccess(RealmModel realm, UserModel user, Map> realmAdminAccess) {