From a95dc5d92ed3c5297a30b189ba1f5640fc51e1f4 Mon Sep 17 00:00:00 2001 From: decentral1se Date: Wed, 5 May 2021 11:47:12 +0200 Subject: [PATCH] Actually, take all the themes --- base/account/account.ftl | 70 + base/account/applications.ftl | 76 + base/account/federatedIdentity.ftl | 42 + base/account/log.ftl | 35 + base/account/messages/messages_en.properties | 376 ++ base/account/password.ftl | 59 + base/account/resource-detail.ftl | 277 ++ base/account/resources.ftl | 403 ++ base/account/sessions.ftl | 44 + base/account/template.ftl | 88 + base/account/totp.ftl | 141 + base/admin/index.ftl | 101 + .../messages/admin-messages_en.properties | 1838 ++++++++ base/admin/messages/messages_en.properties | 41 + base/admin/resources/js/app.js | 3495 ++++++++++++++++ base/admin/resources/js/authz/authz-app.js | 549 +++ .../resources/js/authz/authz-controller.js | 3013 ++++++++++++++ .../resources/js/authz/authz-services.js | 218 + .../admin/resources/js/controllers/clients.js | 3693 +++++++++++++++++ base/admin/resources/js/controllers/groups.js | 625 +++ base/admin/resources/js/controllers/realm.js | 3229 ++++++++++++++ base/admin/resources/js/controllers/roles.js | 48 + base/admin/resources/js/controllers/users.js | 2044 +++++++++ base/admin/resources/js/loaders.js | 570 +++ base/admin/resources/js/services.js | 2225 ++++++++++ .../authentication-flow-bindings.html | 83 + .../partials/authentication-flows.html | 72 + .../partials/authenticator-config.html | 52 + .../authz/mgmt/broker-permissions.html | 40 + .../authz/mgmt/client-permissions.html | 39 + .../authz/mgmt/client-role-permissions.html | 40 + .../authz/mgmt/group-permissions.html | 39 + .../authz/mgmt/realm-role-permissions.html | 39 + .../authz/mgmt/users-permissions.html | 35 + ...esource-server-policy-resource-detail.html | 131 + .../resource-server-policy-scope-detail.html | 134 + .../resource-server-permission-list.html | 118 + ...source-server-policy-aggregate-detail.html | 123 + .../resource-server-policy-client-detail.html | 93 + ...rce-server-policy-client-scope-detail.html | 126 + .../resource-server-policy-group-detail.html | 126 + .../resource-server-policy-js-detail.html | 69 + .../resource-server-policy-role-detail.html | 169 + .../resource-server-policy-time-detail.html | 119 + .../resource-server-policy-user-detail.html | 93 + ...esource-server-policy-evaluate-result.html | 72 + .../resource-server-policy-evaluate.html | 267 ++ .../policy/resource-server-policy-list.html | 117 + .../authz/resource-server-detail.html | 77 + .../resource-server-export-settings.html | 35 + .../partials/authz/resource-server-list.html | 49 + .../resource-server-resource-detail.html | 126 + .../authz/resource-server-resource-list.html | 169 + .../authz/resource-server-scope-detail.html | 50 + .../authz/resource-server-scope-list.html | 102 + .../admin/resources/partials/brute-force.html | 114 + .../admin/resources/partials/ciba-policy.html | 62 + base/admin/resources/partials/claims.html | 62 + .../partials/client-clustering-node.html | 37 + .../resources/partials/client-clustering.html | 76 + .../partials/client-credentials-generic.html | 14 + .../client-credentials-jwt-key-export.html | 57 + .../client-credentials-jwt-key-import.html | 62 + .../partials/client-credentials-jwt.html | 89 + .../client-credentials-secret-jwt.html | 39 + .../partials/client-credentials-secret.html | 17 + .../partials/client-credentials-x509.html | 21 + .../partials/client-credentials.html | 38 + .../resources/partials/client-detail.html | 815 ++++ .../resources/partials/client-import.html | 46 + .../client-initial-access-create.html | 63 + .../partials/client-initial-access.html | 55 + .../partials/client-installation.html | 36 + .../admin/resources/partials/client-keys.html | 146 + .../admin/resources/partials/client-list.html | 68 + .../partials/client-mappers-add.html | 53 + .../resources/partials/client-mappers.html | 55 + .../partials/client-offline-sessions.html | 59 + .../client-protocol-mapper-detail.html | 13 + .../partials/client-reg-policies.html | 106 + .../partials/client-reg-policy-detail.html | 68 + .../client-reg-trusted-host-create.html | 55 + .../client-reg-trusted-host-detail.html | 64 + .../client-registration-access-token.html | 18 + .../resources/partials/client-revocation.html | 30 + .../partials/client-role-attributes.html | 45 + .../partials/client-role-detail.html | 140 + .../resources/partials/client-role-list.html | 64 + .../resources/partials/client-role-users.html | 52 + .../partials/client-saml-key-export.html | 63 + .../partials/client-saml-key-import.html | 62 + .../resources/partials/client-saml-keys.html | 66 + .../partials/client-scope-detail.html | 84 + .../resources/partials/client-scope-list.html | 60 + .../partials/client-scope-mappers-add.html | 53 + .../partials/client-scope-mappers.html | 55 + .../partials/client-scope-mappings.html | 127 + .../client-scope-protocol-mapper-detail.html | 13 + .../partials/client-scope-scope-mappings.html | 116 + .../partials/client-scopes-evaluate.html | 268 ++ .../partials/client-scopes-realm-default.html | 99 + .../partials/client-scopes-setup.html | 123 + .../client-service-account-roles.html | 127 + .../resources/partials/client-sessions.html | 57 + .../partials/client-storage-generic.html | 207 + .../partials/client-storage-list.html | 67 + .../resources/partials/create-client.html | 72 + .../resources/partials/create-execution.html | 31 + .../partials/create-flow-execution.html | 55 + .../admin/resources/partials/create-flow.html | 43 + .../resources/partials/create-group.html | 25 + .../resources/partials/default-groups.html | 91 + .../resources/partials/defense-headers.html | 71 + base/admin/resources/partials/forbidden.html | 7 + .../resources/partials/group-attributes.html | 41 + .../resources/partials/group-detail.html | 28 + base/admin/resources/partials/group-list.html | 50 + .../resources/partials/group-members.html | 48 + .../partials/group-role-mappings.html | 111 + base/admin/resources/partials/home.html | 4 + .../identity-provider-mapper-detail.html | 84 + .../partials/identity-provider-mappers.html | 49 + base/admin/resources/partials/menu.html | 26 + .../modal/realm-events-admin-auth.html | 8 + .../realm-events-admin-representation.html | 3 + .../partials/modal/role-selector.html | 39 + ...unregistered-required-action-selector.html | 21 + .../partials/modal/user-credential-data.html | 8 + .../resources/partials/modal/view-key.html | 18 + .../resources/partials/modal/view-object.html | 3 + base/admin/resources/partials/notfound.html | 7 + base/admin/resources/partials/otp-policy.html | 88 + .../resources/partials/pagenotfound.html | 7 + .../resources/partials/partial-export.html | 34 + .../resources/partials/partial-import.html | 130 + .../resources/partials/password-policy.html | 51 + .../partials/protocol-mapper-detail.html | 64 + .../partials/realm-cache-settings.html | 30 + .../resources/partials/realm-create.html | 45 + .../partials/realm-default-roles.html | 88 + .../resources/partials/realm-detail.html | 82 + .../partials/realm-events-admin.html | 134 + .../partials/realm-events-config.html | 106 + .../resources/partials/realm-events.html | 124 + .../realm-identity-provider-bitbucket.html | 142 + .../realm-identity-provider-facebook-ext.html | 7 + .../realm-identity-provider-facebook.html | 1 + .../realm-identity-provider-github-ext.html | 0 .../realm-identity-provider-github.html | 1 + .../realm-identity-provider-gitlab.html | 142 + .../realm-identity-provider-google-ext.html | 21 + .../realm-identity-provider-google.html | 1 + ...realm-identity-provider-instagram-ext.html | 0 .../realm-identity-provider-instagram.html | 1 + ...realm-identity-provider-keycloak-oidc.html | 1 + .../realm-identity-provider-linkedin-ext.html | 0 .../realm-identity-provider-linkedin.html | 1 + ...realm-identity-provider-microsoft-ext.html | 0 .../realm-identity-provider-microsoft.html | 1 + .../realm-identity-provider-oidc.html | 388 ++ ...lm-identity-provider-openshift-v3-ext.html | 7 + .../realm-identity-provider-openshift-v3.html | 164 + ...lm-identity-provider-openshift-v4-ext.html | 7 + .../realm-identity-provider-openshift-v4.html | 164 + .../realm-identity-provider-paypal-ext.html | 7 + .../realm-identity-provider-paypal.html | 1 + .../realm-identity-provider-saml.html | 414 ++ .../realm-identity-provider-social.html | 157 + ...m-identity-provider-stackoverflow-ext.html | 7 + ...realm-identity-provider-stackoverflow.html | 1 + .../realm-identity-provider-twitter-ext.html | 0 .../realm-identity-provider-twitter.html | 1 + .../partials/realm-identity-provider.html | 81 + .../partials/realm-keys-disabled.html | 70 + .../partials/realm-keys-generic.html | 69 + .../partials/realm-keys-passive.html | 70 + .../partials/realm-keys-providers.html | 75 + base/admin/resources/partials/realm-keys.html | 71 + base/admin/resources/partials/realm-list.html | 20 + .../partials/realm-localization-detail.html | 50 + .../partials/realm-localization-upload.html | 37 + .../partials/realm-localization.html | 61 + .../partials/realm-login-settings.html | 87 + .../resources/partials/realm-role-users.html | 50 + base/admin/resources/partials/realm-smtp.html | 96 + .../partials/realm-theme-settings.html | 97 + .../resources/partials/realm-tokens.html | 385 ++ .../resources/partials/required-actions.html | 38 + .../resources/partials/role-attributes.html | 41 + .../admin/resources/partials/role-detail.html | 135 + base/admin/resources/partials/role-list.html | 63 + .../resources/partials/role-mappings.html | 119 + .../partials/server-info-providers.html | 55 + .../admin/resources/partials/server-info.html | 135 + .../resources/partials/session-realm.html | 34 + .../partials/session-revocation.html | 30 + .../resources/partials/user-attributes.html | 41 + .../resources/partials/user-consents.html | 41 + .../resources/partials/user-credentials.html | 173 + .../admin/resources/partials/user-detail.html | 186 + .../user-federated-identity-detail.html | 53 + .../user-federated-identity-list.html | 41 + .../resources/partials/user-federation.html | 69 + .../partials/user-group-membership.html | 114 + base/admin/resources/partials/user-list.html | 69 + .../partials/user-offline-sessions.html | 35 + .../resources/partials/user-sessions.html | 43 + .../partials/user-storage-generic.html | 246 ++ .../partials/user-storage-kerberos.html | 264 ++ .../user-storage-ldap-mapper-detail.html | 64 + .../partials/user-storage-ldap-mappers.html | 46 + .../resources/partials/user-storage-ldap.html | 567 +++ .../resources/partials/user-storage.html | 45 + .../webauthn-policy-passwordless.html | 177 + .../resources/partials/webauthn-policy.html | 159 + .../templates/authz/kc-authz-modal.html | 11 + .../authz/kc-tabs-resource-server.html | 14 + .../templates/kc-component-config.html | 67 + base/admin/resources/templates/kc-copy.html | 18 + .../resources/templates/kc-dropdown.html | 12 + base/admin/resources/templates/kc-edit.html | 22 + base/admin/resources/templates/kc-menu.html | 64 + .../resources/templates/kc-modal-message.html | 10 + base/admin/resources/templates/kc-modal.html | 11 + base/admin/resources/templates/kc-paging.html | 25 + .../templates/kc-provider-config.html | 91 + base/admin/resources/templates/kc-switch.html | 12 + .../templates/kc-tabs-authentication.html | 16 + .../templates/kc-tabs-client-role.html | 17 + .../templates/kc-tabs-client-scope.html | 20 + .../resources/templates/kc-tabs-client.html | 63 + .../resources/templates/kc-tabs-clients.html | 16 + .../templates/kc-tabs-group-list.html | 11 + .../resources/templates/kc-tabs-group.html | 17 + .../templates/kc-tabs-identity-provider.html | 16 + .../resources/templates/kc-tabs-ldap.html | 12 + .../resources/templates/kc-tabs-realm.html | 20 + .../resources/templates/kc-tabs-role.html | 16 + .../templates/kc-tabs-user-storage.html | 11 + .../resources/templates/kc-tabs-user.html | 18 + .../resources/templates/kc-tabs-users.html | 11 + base/email/html/email-test.ftl | 5 + .../html/email-verification-with-code.ftl | 5 + base/email/html/email-verification.ftl | 5 + base/email/html/event-login_error.ftl | 5 + base/email/html/event-remove_totp.ftl | 5 + base/email/html/event-update_password.ftl | 5 + base/email/html/event-update_totp.ftl | 5 + base/email/html/executeActions.ftl | 9 + base/email/html/identity-provider-link.ftl | 5 + base/email/html/password-reset.ftl | 5 + base/email/messages/messages_en.properties | 51 + base/email/text/email-test.ftl | 2 + .../text/email-verification-with-code.ftl | 2 + base/email/text/email-verification.ftl | 2 + base/email/text/event-login_error.ftl | 2 + base/email/text/event-remove_totp.ftl | 2 + base/email/text/event-update_password.ftl | 2 + base/email/text/event-update_totp.ftl | 2 + base/email/text/executeActions.ftl | 4 + base/email/text/identity-provider-link.ftl | 2 + base/email/text/password-reset.ftl | 2 + base/login/cli_splash.ftl | 7 + base/login/code.ftl | 19 + base/login/delete-account-confirm.ftl | 33 + base/login/error.ftl | 13 + base/login/info.ftl | 24 + base/login/login-config-totp-text.ftl | 31 + base/login/login-config-totp.ftl | 108 + base/login/login-idp-link-confirm.ftl | 13 + base/login/login-idp-link-email.ftl | 16 + base/login/login-oauth-grant.ftl | 41 + .../login-oauth2-device-verify-user-code.ftl | 31 + base/login/login-otp.ftl | 58 + base/login/login-page-expired.ftl | 11 + base/login/login-password.ftl | 43 + base/login/login-reset-password.ftl | 40 + base/login/login-update-password.ftl | 71 + base/login/login-update-profile.ftl | 97 + base/login/login-username.ftl | 92 + base/login/login-verify-email-code-text.ftl | 2 + base/login/login-verify-email.ftl | 14 + base/login/login-x509-info.ftl | 55 + base/login/login.ftl | 99 + base/login/messages/messages_en.properties | 415 ++ base/login/register.ftl | 141 + base/login/resources/js/base64url.js | 114 + base/login/saml-post-form.ftl | 25 + base/login/select-authenticator.ftl | 43 + base/login/template.ftl | 152 + base/login/terms.ftl | 15 + base/login/webauthn-authenticate.ftl | 115 + base/login/webauthn-error.ftl | 55 + base/login/webauthn-register.ftl | 174 + keycloak.v2/account/index.ftl | 279 ++ .../account/messages/messages_en.properties | 121 + keycloak.v2/account/resources/.gitignore | 14 + keycloak.v2/account/resources/content.json | 60 + .../account/resources/public}/favicon.ico | Bin .../account/resources/public/layout.css | 16 + keycloak.v2/account/resources/public/logo.svg | 1 + keycloak.v2/account/src/.babelrc | 19 + keycloak.v2/account/src/.eslintrc.js | 32 + keycloak.v2/account/src/.gitignore | 2 + keycloak.v2/account/src/app/App.tsx | 85 + keycloak.v2/account/src/app/ContentPages.tsx | 174 + keycloak.v2/account/src/app/Main.tsx | 113 + keycloak.v2/account/src/app/PageNav.tsx | 61 + keycloak.v2/account/src/app/PageToolbar.tsx | 86 + .../account-service/AccountServiceContext.tsx | 4 + .../app/account-service/account.service.ts | 154 + .../account/src/app/content/ContentAlert.tsx | 112 + .../account/src/app/content/ContentPage.tsx | 66 + .../app/content/account-page/AccountPage.tsx | 301 ++ .../aia-page/AppInitiatedActionPage.tsx | 91 + .../applications-page/ApplicationsPage.tsx | 237 ++ .../authenticator-page/AuthenticatorPage.tsx | 35 + .../DeviceActivityPage.tsx | 320 ++ .../content/forbidden-page/ForbiddenPage.tsx | 36 + .../LinkedAccountsPage.tsx | 232 ++ .../AbstractResourceTable.tsx | 53 + .../my-resources-page/EditTheResource.tsx | 145 + .../my-resources-page/MyResourcesPage.tsx | 256 ++ .../my-resources-page/PermissionRequest.tsx | 179 + .../my-resources-page/PermissionSelect.tsx | 108 + .../my-resources-page/ResourcesTable.tsx | 341 ++ .../my-resources-page/ShareTheResource.tsx | 243 ++ .../SharedResourcesTable.tsx | 123 + .../my-resources-page/resource-model.ts | 40 + .../content/page-not-found/PageNotFound.tsx | 31 + .../content/signingin-page/SigningInPage.tsx | 365 ++ .../app/keycloak-service/KeycloakContext.tsx | 4 + .../app/keycloak-service/keycloak.service.ts | 78 + .../account/src/app/util/AIACommand.ts | 32 + keycloak.v2/account/src/app/util/ParseLink.ts | 22 + .../account/src/app/util/RedirectUri.ts | 42 + keycloak.v2/account/src/app/util/TimeUtil.ts | 41 + .../src/app/widgets/ContinueCancelModal.tsx | 108 + .../src/app/widgets/EmptyMessageState.tsx | 53 + .../src/app/widgets/LocaleSelectors.tsx | 57 + .../account/src/app/widgets/Logout.tsx | 56 + keycloak.v2/account/src/app/widgets/Msg.tsx | 84 + .../src/app/widgets/ReferrerDropdownItem.tsx | 47 + .../account/src/app/widgets/ReferrerLink.tsx | 48 + .../account/src/app/widgets/features.ts | 29 + keycloak.v2/account/src/eslint.cmd | 7 + keycloak.v2/account/src/package.json | 56 + keycloak.v2/account/src/snowpack.config.js | 11 + keycloak.v2/account/src/tsconfig.json | 22 + keycloak.v2/account/theme.properties | 16 + .../account}/resources/css/account.css | 0 .../resources/img/icon-sidebar-active.png | Bin .../account}/resources/img/keycloak-logo.png | Bin .../account}/resources/img/logo.png | Bin .../account}/theme.properties | 0 .../admin}/resources/css/styles.css | 0 .../admin}/resources/img/keyclok-logo.png | Bin .../admin}/resources/img/keyclok-logo.svg | 0 .../admin}/resources/img/select-arrow.png | Bin {admin => keycloak/admin}/theme.properties | 0 keycloak/common/resources/img/favicon.ico | Bin 0 -> 627 bytes .../common}/resources/lib/angular/errors.json | 0 .../resources/lib/angular/treeview/LICENSE | 0 .../resources/lib/angular/treeview/README.md | 0 .../lib/angular/treeview/angular.treeview.js | 0 .../angular/treeview/angular.treeview.min.js | 0 .../angular/treeview/css/angular.treeview.css | 0 .../lib/angular/treeview/img/file.png | Bin .../angular/treeview/img/folder-closed.png | Bin .../lib/angular/treeview/img/folder.png | Bin .../lib/angular/ui-bootstrap-tpls-0.11.0.js | 0 .../resources/lib/angular/version.json | 0 .../resources/lib/filesaver/FileSaver.js | 0 .../resources/lib/fileupload/FileAPI.min.js | 0 .../angular-file-upload-html5-shim.js | 0 .../angular-file-upload-html5-shim.min.js | 0 .../fileupload/angular-file-upload-shim.js | 0 .../angular-file-upload-shim.min.js | 0 .../lib/fileupload/angular-file-upload.js | 0 .../lib/fileupload/angular-file-upload.min.js | 0 .../common}/resources/lib/pficon/pficon.css | 0 .../common}/resources/lib/pficon/pficon.woff | Bin .../common}/resources/lib/pficon/pficon.woff2 | Bin .../common}/resources/lib/ui-ace/ace.js | 0 .../common}/resources/lib/ui-ace/min/ace.js | 0 .../lib/ui-ace/min/mode-javascript.js | 0 .../resources/lib/ui-ace/min/theme-github.js | 0 .../lib/ui-ace/min/worker-javascript.js | 0 .../resources/lib/ui-ace/mode-javascript.js | 0 .../resources/lib/ui-ace/theme-github.js | 0 .../common}/resources/lib/ui-ace/ui-ace.js | 0 .../resources/lib/ui-ace/ui-ace.min.js | 0 .../resources/lib/ui-ace/worker-javascript.js | 0 .../lib/zocial/zocial-regular-webfont.eot | Bin .../lib/zocial/zocial-regular-webfont.svg | 0 .../lib/zocial/zocial-regular-webfont.ttf | Bin .../lib/zocial/zocial-regular-webfont.woff | Bin .../common}/resources/lib/zocial/zocial.css | 0 .../common}/resources/package.json | 0 {email => keycloak/email}/theme.properties | 0 .../login}/resources/css/login.css | 0 .../login}/resources/css/tile.css | 0 .../img/feedback-error-arrow-down.png | Bin .../resources/img/feedback-error-sign.png | Bin .../img/feedback-success-arrow-down.png | Bin .../resources/img/feedback-success-sign.png | Bin .../img/feedback-warning-arrow-down.png | Bin .../resources/img/feedback-warning-sign.png | Bin .../login}/resources/img/keycloak-bg.png | Bin .../resources/img/keycloak-logo-text.png | Bin .../login}/resources/img/keycloak-logo.png | Bin {login => keycloak/login}/theme.properties | 0 {welcome => keycloak/welcome}/index.ftl | 0 .../welcome}/resources/admin-console.png | Bin .../welcome}/resources/alert.png | Bin .../welcome}/resources/bg.png | Bin .../welcome}/resources/bug.png | Bin .../welcome}/resources/css/welcome.css | 0 .../welcome}/resources/jboss_community.png | Bin .../welcome}/resources/keycloak-project.png | Bin .../welcome}/resources/keycloak_logo.png | Bin .../welcome}/resources/logo.png | Bin .../welcome}/resources/mail.png | Bin .../welcome}/resources/user.png | Bin .../welcome}/theme.properties | 0 425 files changed, 46718 insertions(+) create mode 100755 base/account/account.ftl create mode 100755 base/account/applications.ftl create mode 100755 base/account/federatedIdentity.ftl create mode 100644 base/account/log.ftl create mode 100755 base/account/messages/messages_en.properties create mode 100755 base/account/password.ftl create mode 100755 base/account/resource-detail.ftl create mode 100755 base/account/resources.ftl create mode 100755 base/account/sessions.ftl create mode 100644 base/account/template.ftl create mode 100755 base/account/totp.ftl create mode 100755 base/admin/index.ftl create mode 100644 base/admin/messages/admin-messages_en.properties create mode 100644 base/admin/messages/messages_en.properties create mode 100755 base/admin/resources/js/app.js create mode 100644 base/admin/resources/js/authz/authz-app.js create mode 100644 base/admin/resources/js/authz/authz-controller.js create mode 100644 base/admin/resources/js/authz/authz-services.js create mode 100755 base/admin/resources/js/controllers/clients.js create mode 100755 base/admin/resources/js/controllers/groups.js create mode 100644 base/admin/resources/js/controllers/realm.js create mode 100644 base/admin/resources/js/controllers/roles.js create mode 100755 base/admin/resources/js/controllers/users.js create mode 100755 base/admin/resources/js/loaders.js create mode 100755 base/admin/resources/js/services.js create mode 100755 base/admin/resources/partials/authentication-flow-bindings.html create mode 100755 base/admin/resources/partials/authentication-flows.html create mode 100755 base/admin/resources/partials/authenticator-config.html create mode 100644 base/admin/resources/partials/authz/mgmt/broker-permissions.html create mode 100644 base/admin/resources/partials/authz/mgmt/client-permissions.html create mode 100644 base/admin/resources/partials/authz/mgmt/client-role-permissions.html create mode 100644 base/admin/resources/partials/authz/mgmt/group-permissions.html create mode 100644 base/admin/resources/partials/authz/mgmt/realm-role-permissions.html create mode 100644 base/admin/resources/partials/authz/mgmt/users-permissions.html create mode 100644 base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html create mode 100644 base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html create mode 100644 base/admin/resources/partials/authz/permission/resource-server-permission-list.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-scope-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-js-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html create mode 100644 base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html create mode 100644 base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html create mode 100644 base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html create mode 100644 base/admin/resources/partials/authz/policy/resource-server-policy-list.html create mode 100644 base/admin/resources/partials/authz/resource-server-detail.html create mode 100644 base/admin/resources/partials/authz/resource-server-export-settings.html create mode 100644 base/admin/resources/partials/authz/resource-server-list.html create mode 100644 base/admin/resources/partials/authz/resource-server-resource-detail.html create mode 100644 base/admin/resources/partials/authz/resource-server-resource-list.html create mode 100644 base/admin/resources/partials/authz/resource-server-scope-detail.html create mode 100644 base/admin/resources/partials/authz/resource-server-scope-list.html create mode 100755 base/admin/resources/partials/brute-force.html create mode 100644 base/admin/resources/partials/ciba-policy.html create mode 100755 base/admin/resources/partials/claims.html create mode 100644 base/admin/resources/partials/client-clustering-node.html create mode 100644 base/admin/resources/partials/client-clustering.html create mode 100644 base/admin/resources/partials/client-credentials-generic.html create mode 100644 base/admin/resources/partials/client-credentials-jwt-key-export.html create mode 100644 base/admin/resources/partials/client-credentials-jwt-key-import.html create mode 100644 base/admin/resources/partials/client-credentials-jwt.html create mode 100644 base/admin/resources/partials/client-credentials-secret-jwt.html create mode 100644 base/admin/resources/partials/client-credentials-secret.html create mode 100644 base/admin/resources/partials/client-credentials-x509.html create mode 100755 base/admin/resources/partials/client-credentials.html create mode 100755 base/admin/resources/partials/client-detail.html create mode 100755 base/admin/resources/partials/client-import.html create mode 100755 base/admin/resources/partials/client-initial-access-create.html create mode 100755 base/admin/resources/partials/client-initial-access.html create mode 100755 base/admin/resources/partials/client-installation.html create mode 100755 base/admin/resources/partials/client-keys.html create mode 100755 base/admin/resources/partials/client-list.html create mode 100755 base/admin/resources/partials/client-mappers-add.html create mode 100755 base/admin/resources/partials/client-mappers.html create mode 100644 base/admin/resources/partials/client-offline-sessions.html create mode 100755 base/admin/resources/partials/client-protocol-mapper-detail.html create mode 100644 base/admin/resources/partials/client-reg-policies.html create mode 100644 base/admin/resources/partials/client-reg-policy-detail.html create mode 100644 base/admin/resources/partials/client-reg-trusted-host-create.html create mode 100644 base/admin/resources/partials/client-reg-trusted-host-detail.html create mode 100644 base/admin/resources/partials/client-registration-access-token.html create mode 100755 base/admin/resources/partials/client-revocation.html create mode 100755 base/admin/resources/partials/client-role-attributes.html create mode 100755 base/admin/resources/partials/client-role-detail.html create mode 100755 base/admin/resources/partials/client-role-list.html create mode 100644 base/admin/resources/partials/client-role-users.html create mode 100755 base/admin/resources/partials/client-saml-key-export.html create mode 100755 base/admin/resources/partials/client-saml-key-import.html create mode 100755 base/admin/resources/partials/client-saml-keys.html create mode 100755 base/admin/resources/partials/client-scope-detail.html create mode 100755 base/admin/resources/partials/client-scope-list.html create mode 100755 base/admin/resources/partials/client-scope-mappers-add.html create mode 100755 base/admin/resources/partials/client-scope-mappers.html create mode 100755 base/admin/resources/partials/client-scope-mappings.html create mode 100755 base/admin/resources/partials/client-scope-protocol-mapper-detail.html create mode 100755 base/admin/resources/partials/client-scope-scope-mappings.html create mode 100644 base/admin/resources/partials/client-scopes-evaluate.html create mode 100644 base/admin/resources/partials/client-scopes-realm-default.html create mode 100644 base/admin/resources/partials/client-scopes-setup.html create mode 100644 base/admin/resources/partials/client-service-account-roles.html create mode 100755 base/admin/resources/partials/client-sessions.html create mode 100755 base/admin/resources/partials/client-storage-generic.html create mode 100755 base/admin/resources/partials/client-storage-list.html create mode 100755 base/admin/resources/partials/create-client.html create mode 100755 base/admin/resources/partials/create-execution.html create mode 100755 base/admin/resources/partials/create-flow-execution.html create mode 100755 base/admin/resources/partials/create-flow.html create mode 100755 base/admin/resources/partials/create-group.html create mode 100755 base/admin/resources/partials/default-groups.html create mode 100755 base/admin/resources/partials/defense-headers.html create mode 100755 base/admin/resources/partials/forbidden.html create mode 100755 base/admin/resources/partials/group-attributes.html create mode 100755 base/admin/resources/partials/group-detail.html create mode 100755 base/admin/resources/partials/group-list.html create mode 100755 base/admin/resources/partials/group-members.html create mode 100755 base/admin/resources/partials/group-role-mappings.html create mode 100755 base/admin/resources/partials/home.html create mode 100755 base/admin/resources/partials/identity-provider-mapper-detail.html create mode 100755 base/admin/resources/partials/identity-provider-mappers.html create mode 100755 base/admin/resources/partials/menu.html create mode 100644 base/admin/resources/partials/modal/realm-events-admin-auth.html create mode 100644 base/admin/resources/partials/modal/realm-events-admin-representation.html create mode 100755 base/admin/resources/partials/modal/role-selector.html create mode 100755 base/admin/resources/partials/modal/unregistered-required-action-selector.html create mode 100644 base/admin/resources/partials/modal/user-credential-data.html create mode 100644 base/admin/resources/partials/modal/view-key.html create mode 100644 base/admin/resources/partials/modal/view-object.html create mode 100755 base/admin/resources/partials/notfound.html create mode 100755 base/admin/resources/partials/otp-policy.html create mode 100755 base/admin/resources/partials/pagenotfound.html create mode 100644 base/admin/resources/partials/partial-export.html create mode 100644 base/admin/resources/partials/partial-import.html create mode 100755 base/admin/resources/partials/password-policy.html create mode 100755 base/admin/resources/partials/protocol-mapper-detail.html create mode 100755 base/admin/resources/partials/realm-cache-settings.html create mode 100755 base/admin/resources/partials/realm-create.html create mode 100755 base/admin/resources/partials/realm-default-roles.html create mode 100755 base/admin/resources/partials/realm-detail.html create mode 100755 base/admin/resources/partials/realm-events-admin.html create mode 100755 base/admin/resources/partials/realm-events-config.html create mode 100755 base/admin/resources/partials/realm-events.html create mode 100755 base/admin/resources/partials/realm-identity-provider-bitbucket.html create mode 100755 base/admin/resources/partials/realm-identity-provider-facebook-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-facebook.html create mode 100755 base/admin/resources/partials/realm-identity-provider-github-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-github.html create mode 100755 base/admin/resources/partials/realm-identity-provider-gitlab.html create mode 100755 base/admin/resources/partials/realm-identity-provider-google-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-google.html create mode 100644 base/admin/resources/partials/realm-identity-provider-instagram-ext.html create mode 100644 base/admin/resources/partials/realm-identity-provider-instagram.html create mode 100755 base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html create mode 100755 base/admin/resources/partials/realm-identity-provider-linkedin-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-linkedin.html create mode 100755 base/admin/resources/partials/realm-identity-provider-microsoft-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-microsoft.html create mode 100755 base/admin/resources/partials/realm-identity-provider-oidc.html create mode 100644 base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-openshift-v3.html create mode 100644 base/admin/resources/partials/realm-identity-provider-openshift-v4-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-openshift-v4.html create mode 100644 base/admin/resources/partials/realm-identity-provider-paypal-ext.html create mode 100644 base/admin/resources/partials/realm-identity-provider-paypal.html create mode 100755 base/admin/resources/partials/realm-identity-provider-saml.html create mode 100755 base/admin/resources/partials/realm-identity-provider-social.html create mode 100755 base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-stackoverflow.html create mode 100755 base/admin/resources/partials/realm-identity-provider-twitter-ext.html create mode 100755 base/admin/resources/partials/realm-identity-provider-twitter.html create mode 100755 base/admin/resources/partials/realm-identity-provider.html create mode 100755 base/admin/resources/partials/realm-keys-disabled.html create mode 100755 base/admin/resources/partials/realm-keys-generic.html create mode 100755 base/admin/resources/partials/realm-keys-passive.html create mode 100755 base/admin/resources/partials/realm-keys-providers.html create mode 100755 base/admin/resources/partials/realm-keys.html create mode 100755 base/admin/resources/partials/realm-list.html create mode 100644 base/admin/resources/partials/realm-localization-detail.html create mode 100644 base/admin/resources/partials/realm-localization-upload.html create mode 100644 base/admin/resources/partials/realm-localization.html create mode 100755 base/admin/resources/partials/realm-login-settings.html create mode 100644 base/admin/resources/partials/realm-role-users.html create mode 100755 base/admin/resources/partials/realm-smtp.html create mode 100755 base/admin/resources/partials/realm-theme-settings.html create mode 100755 base/admin/resources/partials/realm-tokens.html create mode 100755 base/admin/resources/partials/required-actions.html create mode 100755 base/admin/resources/partials/role-attributes.html create mode 100755 base/admin/resources/partials/role-detail.html create mode 100755 base/admin/resources/partials/role-list.html create mode 100755 base/admin/resources/partials/role-mappings.html create mode 100755 base/admin/resources/partials/server-info-providers.html create mode 100755 base/admin/resources/partials/server-info.html create mode 100755 base/admin/resources/partials/session-realm.html create mode 100755 base/admin/resources/partials/session-revocation.html create mode 100755 base/admin/resources/partials/user-attributes.html create mode 100644 base/admin/resources/partials/user-consents.html create mode 100755 base/admin/resources/partials/user-credentials.html create mode 100755 base/admin/resources/partials/user-detail.html create mode 100644 base/admin/resources/partials/user-federated-identity-detail.html create mode 100644 base/admin/resources/partials/user-federated-identity-list.html create mode 100755 base/admin/resources/partials/user-federation.html create mode 100755 base/admin/resources/partials/user-group-membership.html create mode 100755 base/admin/resources/partials/user-list.html create mode 100644 base/admin/resources/partials/user-offline-sessions.html create mode 100755 base/admin/resources/partials/user-sessions.html create mode 100755 base/admin/resources/partials/user-storage-generic.html create mode 100644 base/admin/resources/partials/user-storage-kerberos.html create mode 100644 base/admin/resources/partials/user-storage-ldap-mapper-detail.html create mode 100644 base/admin/resources/partials/user-storage-ldap-mappers.html create mode 100755 base/admin/resources/partials/user-storage-ldap.html create mode 100755 base/admin/resources/partials/user-storage.html create mode 100644 base/admin/resources/partials/webauthn-policy-passwordless.html create mode 100644 base/admin/resources/partials/webauthn-policy.html create mode 100644 base/admin/resources/templates/authz/kc-authz-modal.html create mode 100755 base/admin/resources/templates/authz/kc-tabs-resource-server.html create mode 100755 base/admin/resources/templates/kc-component-config.html create mode 100755 base/admin/resources/templates/kc-copy.html create mode 100644 base/admin/resources/templates/kc-dropdown.html create mode 100644 base/admin/resources/templates/kc-edit.html create mode 100755 base/admin/resources/templates/kc-menu.html create mode 100755 base/admin/resources/templates/kc-modal-message.html create mode 100644 base/admin/resources/templates/kc-modal.html create mode 100644 base/admin/resources/templates/kc-paging.html create mode 100755 base/admin/resources/templates/kc-provider-config.html create mode 100644 base/admin/resources/templates/kc-switch.html create mode 100755 base/admin/resources/templates/kc-tabs-authentication.html create mode 100755 base/admin/resources/templates/kc-tabs-client-role.html create mode 100755 base/admin/resources/templates/kc-tabs-client-scope.html create mode 100755 base/admin/resources/templates/kc-tabs-client.html create mode 100755 base/admin/resources/templates/kc-tabs-clients.html create mode 100755 base/admin/resources/templates/kc-tabs-group-list.html create mode 100755 base/admin/resources/templates/kc-tabs-group.html create mode 100644 base/admin/resources/templates/kc-tabs-identity-provider.html create mode 100644 base/admin/resources/templates/kc-tabs-ldap.html create mode 100755 base/admin/resources/templates/kc-tabs-realm.html create mode 100755 base/admin/resources/templates/kc-tabs-role.html create mode 100644 base/admin/resources/templates/kc-tabs-user-storage.html create mode 100755 base/admin/resources/templates/kc-tabs-user.html create mode 100755 base/admin/resources/templates/kc-tabs-users.html create mode 100644 base/email/html/email-test.ftl create mode 100644 base/email/html/email-verification-with-code.ftl create mode 100644 base/email/html/email-verification.ftl create mode 100644 base/email/html/event-login_error.ftl create mode 100644 base/email/html/event-remove_totp.ftl create mode 100644 base/email/html/event-update_password.ftl create mode 100644 base/email/html/event-update_totp.ftl create mode 100755 base/email/html/executeActions.ftl create mode 100644 base/email/html/identity-provider-link.ftl create mode 100755 base/email/html/password-reset.ftl create mode 100755 base/email/messages/messages_en.properties create mode 100644 base/email/text/email-test.ftl create mode 100644 base/email/text/email-verification-with-code.ftl create mode 100644 base/email/text/email-verification.ftl create mode 100644 base/email/text/event-login_error.ftl create mode 100644 base/email/text/event-remove_totp.ftl create mode 100644 base/email/text/event-update_password.ftl create mode 100644 base/email/text/event-update_totp.ftl create mode 100755 base/email/text/executeActions.ftl create mode 100644 base/email/text/identity-provider-link.ftl create mode 100755 base/email/text/password-reset.ftl create mode 100644 base/login/cli_splash.ftl create mode 100755 base/login/code.ftl create mode 100644 base/login/delete-account-confirm.ftl create mode 100755 base/login/error.ftl create mode 100755 base/login/info.ftl create mode 100755 base/login/login-config-totp-text.ftl create mode 100755 base/login/login-config-totp.ftl create mode 100644 base/login/login-idp-link-confirm.ftl create mode 100644 base/login/login-idp-link-email.ftl create mode 100755 base/login/login-oauth-grant.ftl create mode 100644 base/login/login-oauth2-device-verify-user-code.ftl create mode 100755 base/login/login-otp.ftl create mode 100644 base/login/login-page-expired.ftl create mode 100755 base/login/login-password.ftl create mode 100755 base/login/login-reset-password.ftl create mode 100755 base/login/login-update-password.ftl create mode 100755 base/login/login-update-profile.ftl create mode 100755 base/login/login-username.ftl create mode 100644 base/login/login-verify-email-code-text.ftl create mode 100755 base/login/login-verify-email.ftl create mode 100644 base/login/login-x509-info.ftl create mode 100755 base/login/login.ftl create mode 100755 base/login/messages/messages_en.properties create mode 100755 base/login/register.ftl create mode 100644 base/login/resources/js/base64url.js create mode 100644 base/login/saml-post-form.ftl create mode 100644 base/login/select-authenticator.ftl create mode 100644 base/login/template.ftl create mode 100755 base/login/terms.ftl create mode 100644 base/login/webauthn-authenticate.ftl create mode 100644 base/login/webauthn-error.ftl create mode 100644 base/login/webauthn-register.ftl create mode 100644 keycloak.v2/account/index.ftl create mode 100644 keycloak.v2/account/messages/messages_en.properties create mode 100644 keycloak.v2/account/resources/.gitignore create mode 100644 keycloak.v2/account/resources/content.json rename {common/resources/img => keycloak.v2/account/resources/public}/favicon.ico (100%) create mode 100644 keycloak.v2/account/resources/public/layout.css create mode 100644 keycloak.v2/account/resources/public/logo.svg create mode 100644 keycloak.v2/account/src/.babelrc create mode 100644 keycloak.v2/account/src/.eslintrc.js create mode 100644 keycloak.v2/account/src/.gitignore create mode 100644 keycloak.v2/account/src/app/App.tsx create mode 100644 keycloak.v2/account/src/app/ContentPages.tsx create mode 100644 keycloak.v2/account/src/app/Main.tsx create mode 100644 keycloak.v2/account/src/app/PageNav.tsx create mode 100644 keycloak.v2/account/src/app/PageToolbar.tsx create mode 100644 keycloak.v2/account/src/app/account-service/AccountServiceContext.tsx create mode 100644 keycloak.v2/account/src/app/account-service/account.service.ts create mode 100644 keycloak.v2/account/src/app/content/ContentAlert.tsx create mode 100644 keycloak.v2/account/src/app/content/ContentPage.tsx create mode 100644 keycloak.v2/account/src/app/content/account-page/AccountPage.tsx create mode 100644 keycloak.v2/account/src/app/content/aia-page/AppInitiatedActionPage.tsx create mode 100644 keycloak.v2/account/src/app/content/applications-page/ApplicationsPage.tsx create mode 100644 keycloak.v2/account/src/app/content/authenticator-page/AuthenticatorPage.tsx create mode 100644 keycloak.v2/account/src/app/content/device-activity-page/DeviceActivityPage.tsx create mode 100644 keycloak.v2/account/src/app/content/forbidden-page/ForbiddenPage.tsx create mode 100644 keycloak.v2/account/src/app/content/linked-accounts-page/LinkedAccountsPage.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/AbstractResourceTable.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/EditTheResource.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/MyResourcesPage.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/PermissionRequest.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/PermissionSelect.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/ResourcesTable.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/ShareTheResource.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/SharedResourcesTable.tsx create mode 100644 keycloak.v2/account/src/app/content/my-resources-page/resource-model.ts create mode 100644 keycloak.v2/account/src/app/content/page-not-found/PageNotFound.tsx create mode 100644 keycloak.v2/account/src/app/content/signingin-page/SigningInPage.tsx create mode 100644 keycloak.v2/account/src/app/keycloak-service/KeycloakContext.tsx create mode 100644 keycloak.v2/account/src/app/keycloak-service/keycloak.service.ts create mode 100644 keycloak.v2/account/src/app/util/AIACommand.ts create mode 100644 keycloak.v2/account/src/app/util/ParseLink.ts create mode 100644 keycloak.v2/account/src/app/util/RedirectUri.ts create mode 100644 keycloak.v2/account/src/app/util/TimeUtil.ts create mode 100644 keycloak.v2/account/src/app/widgets/ContinueCancelModal.tsx create mode 100644 keycloak.v2/account/src/app/widgets/EmptyMessageState.tsx create mode 100644 keycloak.v2/account/src/app/widgets/LocaleSelectors.tsx create mode 100644 keycloak.v2/account/src/app/widgets/Logout.tsx create mode 100644 keycloak.v2/account/src/app/widgets/Msg.tsx create mode 100644 keycloak.v2/account/src/app/widgets/ReferrerDropdownItem.tsx create mode 100644 keycloak.v2/account/src/app/widgets/ReferrerLink.tsx create mode 100644 keycloak.v2/account/src/app/widgets/features.ts create mode 100644 keycloak.v2/account/src/eslint.cmd create mode 100644 keycloak.v2/account/src/package.json create mode 100644 keycloak.v2/account/src/snowpack.config.js create mode 100644 keycloak.v2/account/src/tsconfig.json create mode 100644 keycloak.v2/account/theme.properties rename {account => keycloak/account}/resources/css/account.css (100%) rename {account => keycloak/account}/resources/img/icon-sidebar-active.png (100%) rename {account => keycloak/account}/resources/img/keycloak-logo.png (100%) rename {account => keycloak/account}/resources/img/logo.png (100%) rename {account => keycloak/account}/theme.properties (100%) rename {admin => keycloak/admin}/resources/css/styles.css (100%) rename {admin => keycloak/admin}/resources/img/keyclok-logo.png (100%) rename {admin => keycloak/admin}/resources/img/keyclok-logo.svg (100%) rename {admin => keycloak/admin}/resources/img/select-arrow.png (100%) rename {admin => keycloak/admin}/theme.properties (100%) create mode 100644 keycloak/common/resources/img/favicon.ico rename {common => keycloak/common}/resources/lib/angular/errors.json (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/LICENSE (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/README.md (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/angular.treeview.js (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/angular.treeview.min.js (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/css/angular.treeview.css (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/img/file.png (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/img/folder-closed.png (100%) rename {common => keycloak/common}/resources/lib/angular/treeview/img/folder.png (100%) rename {common => keycloak/common}/resources/lib/angular/ui-bootstrap-tpls-0.11.0.js (100%) rename {common => keycloak/common}/resources/lib/angular/version.json (100%) rename {common => keycloak/common}/resources/lib/filesaver/FileSaver.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/FileAPI.min.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/angular-file-upload-html5-shim.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/angular-file-upload-html5-shim.min.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/angular-file-upload-shim.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/angular-file-upload-shim.min.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/angular-file-upload.js (100%) rename {common => keycloak/common}/resources/lib/fileupload/angular-file-upload.min.js (100%) rename {common => keycloak/common}/resources/lib/pficon/pficon.css (100%) rename {common => keycloak/common}/resources/lib/pficon/pficon.woff (100%) rename {common => keycloak/common}/resources/lib/pficon/pficon.woff2 (100%) rename {common => keycloak/common}/resources/lib/ui-ace/ace.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/min/ace.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/min/mode-javascript.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/min/theme-github.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/min/worker-javascript.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/mode-javascript.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/theme-github.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/ui-ace.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/ui-ace.min.js (100%) rename {common => keycloak/common}/resources/lib/ui-ace/worker-javascript.js (100%) rename {common => keycloak/common}/resources/lib/zocial/zocial-regular-webfont.eot (100%) rename {common => keycloak/common}/resources/lib/zocial/zocial-regular-webfont.svg (100%) rename {common => keycloak/common}/resources/lib/zocial/zocial-regular-webfont.ttf (100%) rename {common => keycloak/common}/resources/lib/zocial/zocial-regular-webfont.woff (100%) rename {common => keycloak/common}/resources/lib/zocial/zocial.css (100%) rename {common => keycloak/common}/resources/package.json (100%) rename {email => keycloak/email}/theme.properties (100%) rename {login => keycloak/login}/resources/css/login.css (100%) rename {login => keycloak/login}/resources/css/tile.css (100%) rename {login => keycloak/login}/resources/img/feedback-error-arrow-down.png (100%) rename {login => keycloak/login}/resources/img/feedback-error-sign.png (100%) rename {login => keycloak/login}/resources/img/feedback-success-arrow-down.png (100%) rename {login => keycloak/login}/resources/img/feedback-success-sign.png (100%) rename {login => keycloak/login}/resources/img/feedback-warning-arrow-down.png (100%) rename {login => keycloak/login}/resources/img/feedback-warning-sign.png (100%) rename {login => keycloak/login}/resources/img/keycloak-bg.png (100%) rename {login => keycloak/login}/resources/img/keycloak-logo-text.png (100%) rename {login => keycloak/login}/resources/img/keycloak-logo.png (100%) rename {login => keycloak/login}/theme.properties (100%) rename {welcome => keycloak/welcome}/index.ftl (100%) rename {welcome => keycloak/welcome}/resources/admin-console.png (100%) rename {welcome => keycloak/welcome}/resources/alert.png (100%) rename {welcome => keycloak/welcome}/resources/bg.png (100%) rename {welcome => keycloak/welcome}/resources/bug.png (100%) rename {welcome => keycloak/welcome}/resources/css/welcome.css (100%) rename {welcome => keycloak/welcome}/resources/jboss_community.png (100%) rename {welcome => keycloak/welcome}/resources/keycloak-project.png (100%) rename {welcome => keycloak/welcome}/resources/keycloak_logo.png (100%) rename {welcome => keycloak/welcome}/resources/logo.png (100%) rename {welcome => keycloak/welcome}/resources/mail.png (100%) rename {welcome => keycloak/welcome}/resources/user.png (100%) rename {welcome => keycloak/welcome}/theme.properties (100%) diff --git a/base/account/account.ftl b/base/account/account.ftl new file mode 100755 index 0000000..9254b96 --- /dev/null +++ b/base/account/account.ftl @@ -0,0 +1,70 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='account' bodyClass='user'; section> + +
+
+

${msg("editAccountHtmlTitle")}

+
+
+ * ${msg("requiredFields")} +
+
+ +
+ + + + <#if !realm.registrationEmailAsUsername> +
+
+ <#if realm.editUsernameAllowed>* +
+ +
+ disabled="disabled" value="${(account.username!'')}"/> +
+
+ + +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+
+ <#if url.referrerURI??>${kcSanitize(msg("backToApplication")?no_esc)} + + +
+
+
+
+ + diff --git a/base/account/applications.ftl b/base/account/applications.ftl new file mode 100755 index 0000000..a8edc38 --- /dev/null +++ b/base/account/applications.ftl @@ -0,0 +1,76 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='applications' bodyClass='applications'; section> + +
+
+

${msg("applicationsHtmlTitle")}

+
+
+ +
+ + + + + + + + + + + + + + + + <#list applications.applications as application> + + + + + + + + + + + + + +
${msg("application")}${msg("availableRoles")}${msg("grantedPermissions")}${msg("additionalGrants")}${msg("action")}
+ <#if application.effectiveUrl?has_content> + <#if application.client.name?has_content>${advancedMsg(application.client.name)}<#else>${application.client.clientId} + <#if application.effectiveUrl?has_content> + + <#list application.realmRolesAvailable as role> + <#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)} + <#if role_has_next>, + + <#list application.resourceRolesAvailable?keys as resource> + <#if application.realmRolesAvailable?has_content>, + <#list application.resourceRolesAvailable[resource] as clientRole> + <#if clientRole.roleDescription??>${advancedMsg(clientRole.roleDescription)}<#else>${advancedMsg(clientRole.roleName)} + ${msg("inResource")} <#if clientRole.clientName??>${advancedMsg(clientRole.clientName)}<#else>${clientRole.clientId} + <#if clientRole_has_next>, + + + + <#if application.client.consentRequired> + <#list application.clientScopesGranted as claim> + ${advancedMsg(claim)}<#if claim_has_next>, + + <#else> + ${msg("fullAccess")} + + + <#list application.additionalGrants as grant> + ${advancedMsg(grant)}<#if grant_has_next>, + + + <#if (application.client.consentRequired && application.clientScopesGranted?has_content) || application.additionalGrants?has_content> + + +
+
+ + \ No newline at end of file diff --git a/base/account/federatedIdentity.ftl b/base/account/federatedIdentity.ftl new file mode 100755 index 0000000..c2eb769 --- /dev/null +++ b/base/account/federatedIdentity.ftl @@ -0,0 +1,42 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='social' bodyClass='social'; section> + +
+
+

${msg("federatedIdentitiesHtmlTitle")}

+
+
+ +
+ <#list federatedIdentity.identities as identity> +
+
+ +
+
+ +
+
+ <#if identity.connected> + <#if federatedIdentity.removeLinkPossible> +
+ + + + +
+ + <#else> +
+ + + + +
+ +
+
+ +
+ + diff --git a/base/account/log.ftl b/base/account/log.ftl new file mode 100644 index 0000000..29046cf --- /dev/null +++ b/base/account/log.ftl @@ -0,0 +1,35 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='log' bodyClass='log'; section> + +
+
+

${msg("accountLogHtmlTitle")}

+
+
+ + + + + + + + + + + + + + <#list log.events as event> + + + + + + + + + + +
${msg("date")}${msg("event")}${msg("ip")}${msg("client")}${msg("details")}
${event.date?datetime}${event.event}${event.ipAddress}${event.client!}<#list event.details as detail>${detail.key} = ${detail.value} <#if detail_has_next>,
+ + \ No newline at end of file diff --git a/base/account/messages/messages_en.properties b/base/account/messages/messages_en.properties new file mode 100755 index 0000000..c97c181 --- /dev/null +++ b/base/account/messages/messages_en.properties @@ -0,0 +1,376 @@ +doSave=Save +doCancel=Cancel +doLogOutAllSessions=Log out all sessions +doRemove=Remove +doAdd=Add +doSignOut=Sign Out +doLogIn=Log In +doLink=Link +noAccessMessage=Access not allowed + + +editAccountHtmlTitle=Edit Account +personalInfoHtmlTitle=Personal Info +federatedIdentitiesHtmlTitle=Federated Identities +accountLogHtmlTitle=Account Log +changePasswordHtmlTitle=Change Password +deviceActivityHtmlTitle=Device Activity +sessionsHtmlTitle=Sessions +accountManagementTitle=Keycloak Account Management +authenticatorTitle=Authenticator +applicationsHtmlTitle=Applications +linkedAccountsHtmlTitle=Linked Accounts + +accountManagementWelcomeMessage=Welcome to Keycloak Account Management +personalInfoIntroMessage=Manage your basic information +accountSecurityTitle=Account Security +accountSecurityIntroMessage=Control your password and account access +applicationsIntroMessage=Track and manage your app permission to access your account +resourceIntroMessage=Share your resources among team members +passwordLastUpdateMessage=Your password was updated at +updatePasswordTitle=Update Password +updatePasswordMessageTitle=Make sure you choose a strong password +updatePasswordMessage=A strong password contains a mix of numbers, letters, and symbols. It is hard to guess, does not resemble a real word, and is only used for this account. +personalSubTitle=Your Personal Info +personalSubMessage=Manage this basic information: your first name, last name and email + +authenticatorCode=One-time code +email=Email +firstName=First name +givenName=Given name +fullName=Full name +lastName=Last name +familyName=Family name +password=Password +currentPassword=Current Password +passwordConfirm=Confirmation +passwordNew=New Password +username=Username +address=Address +street=Street +locality=City or Locality +region=State, Province, or Region +postal_code=Zip or Postal code +country=Country +emailVerified=Email verified +website=Web page +phoneNumber=Phone number +phoneNumberVerified=Phone number verified +gender=Gender +birthday=Birthdate +zoneinfo=Time zone +gssDelegationCredential=GSS Delegation Credential + +profileScopeConsentText=User profile +emailScopeConsentText=Email address +addressScopeConsentText=Address +phoneScopeConsentText=Phone number +offlineAccessScopeConsentText=Offline Access +samlRoleListScopeConsentText=My Roles +rolesScopeConsentText=User roles + +role_admin=Admin +role_realm-admin=Realm Admin +role_create-realm=Create realm +role_view-realm=View realm +role_view-users=View users +role_view-applications=View applications +role_view-clients=View clients +role_view-events=View events +role_view-identity-providers=View identity providers +role_view-consent=View consents +role_manage-realm=Manage realm +role_manage-users=Manage users +role_manage-applications=Manage applications +role_manage-identity-providers=Manage identity providers +role_manage-clients=Manage clients +role_manage-events=Manage events +role_view-profile=View profile +role_manage-account=Manage account +role_manage-account-links=Manage account links +role_manage-consent=Manage consents +role_read-token=Read token +role_offline-access=Offline access +role_uma_authorization=Obtain permissions +client_account=Account +client_account-console=Account Console +client_security-admin-console=Security Admin Console +client_admin-cli=Admin CLI +client_realm-management=Realm Management +client_broker=Broker + + +requiredFields=Required fields +allFieldsRequired=All fields required + +backToApplication=« Back to application +backTo=Back to {0} + +date=Date +event=Event +ip=IP +client=Client +clients=Clients +details=Details +started=Started +lastAccess=Last Access +expires=Expires +applications=Applications + +account=Account +federatedIdentity=Federated Identity +authenticator=Authenticator +device-activity=Device Activity +sessions=Sessions +log=Log + +application=Application +availableRoles=Available Roles +grantedPermissions=Granted Permissions +grantedPersonalInfo=Granted Personal Info +additionalGrants=Additional Grants +action=Action +inResource=in +fullAccess=Full Access +offlineToken=Offline Token +revoke=Revoke Grant + +configureAuthenticators=Configured Authenticators +mobile=Mobile +totpStep1=Install one of the following applications on your mobile: +totpStep2=Open the application and scan the barcode: +totpStep3=Enter the one-time code provided by the application and click Save to finish the setup. +totpStep3DeviceName=Provide a Device Name to help you manage your OTP devices. + +totpManualStep2=Open the application and enter the key: +totpManualStep3=Use the following configuration values if the application allows setting them: +totpUnableToScan=Unable to scan? +totpScanBarcode=Scan barcode? + +totp.totp=Time-based +totp.hotp=Counter-based + +totpType=Type +totpAlgorithm=Algorithm +totpDigits=Digits +totpInterval=Interval +totpCounter=Counter +totpDeviceName=Device Name + +irreversibleAction=This action is irreversible +deletingImplies=Deleting your account implies: +errasingData=Erasing all your data +loggingOutImmediately=Logging you out immediately +accountUnusable=Any subsequent use of the application will not be possible with this account + +missingUsernameMessage=Please specify username. +missingFirstNameMessage=Please specify first name. +invalidEmailMessage=Invalid email address. +missingLastNameMessage=Please specify last name. +missingEmailMessage=Please specify email. +missingPasswordMessage=Please specify password. +notMatchPasswordMessage=Passwords don''t match. +invalidUserMessage=Invalid user +updateReadOnlyAttributesRejectedMessage=Update of read-only attribute rejected + +missingTotpMessage=Please specify authenticator code. +missingTotpDeviceNameMessage=Please specify device name. +invalidPasswordExistingMessage=Invalid existing password. +invalidPasswordConfirmMessage=Password confirmation doesn''t match. +invalidTotpMessage=Invalid authenticator code. + +usernameExistsMessage=Username already exists. +emailExistsMessage=Email already exists. + +readOnlyUserMessage=You can''t update your account as it is read-only. +readOnlyUsernameMessage=You can''t update your username as it is read-only. +readOnlyPasswordMessage=You can''t update your password as your account is read-only. + +successTotpMessage=Mobile authenticator configured. +successTotpRemovedMessage=Mobile authenticator removed. + +successGrantRevokedMessage=Grant revoked successfully. + +accountUpdatedMessage=Your account has been updated. +accountPasswordUpdatedMessage=Your password has been updated. + +missingIdentityProviderMessage=Identity provider not specified. +invalidFederatedIdentityActionMessage=Invalid or missing action. +identityProviderNotFoundMessage=Specified identity provider not found. +federatedIdentityLinkNotActiveMessage=This identity is not active anymore. +federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have a password. +identityProviderRedirectErrorMessage=Failed to redirect to identity provider. +identityProviderRemovedMessage=Identity provider removed successfully. +identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. +staleCodeAccountMessage=The page expired. Please try one more time. +consentDenied=Consent denied. + +accountDisabledMessage=Account is disabled, contact your administrator. + +accountTemporarilyDisabledMessage=Account is temporarily disabled, contact your administrator or try again later. +invalidPasswordMinLengthMessage=Invalid password: minimum length {0}. +invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters. +invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits. +invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters. +invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters. +invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username. +invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email. +invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s). +invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords. +invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted. +invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies. + +# Authorization +myResources=My Resources +myResourcesSub=My resources +doDeny=Deny +doRevoke=Revoke +doApprove=Approve +doRemoveSharing=Remove Sharing +doRemoveRequest=Remove Request +peopleAccessResource=People with access to this resource +resourceManagedPolicies=Permissions granting access to this resource +resourceNoPermissionsGrantingAccess=No permissions granting access to this resource +anyAction=Any action +description=Description +name=Name +scopes=Scopes +resource=Resource +user=User +peopleSharingThisResource=People sharing this resource +shareWithOthers=Share with others +needMyApproval=Need my approval +requestsWaitingApproval=Your requests waiting approval +icon=Icon +requestor=Requestor +owner=Owner +resourcesSharedWithMe=Resources shared with me +permissionRequestion=Permission Requestion +permission=Permission +shares=share(s) +notBeingShared=This resource is not being shared. +notHaveAnyResource=You don't have any resources +noResourcesSharedWithYou=There are no resources shared with you +havePermissionRequestsWaitingForApproval=You have {0} permission request(s) waiting for approval. +clickHereForDetails=Click here for details. +resourceIsNotBeingShared=The resource is not being shared + +locale_ca=Catal\u00e0 +locale_cs=\u010Ce\u0161tina +locale_de=Deutsch +locale_en=English +locale_es=Espa\u00f1ol +locale_fr=Fran\u00e7ais +locale_hu=Magyar +locale_it=Italiano +locale_ja=\u65e5\u672c\u8a9e +locale_lt=Lietuvi\u0173 +locale_nl=Nederlands +locale_no=Norsk +locale_pl=Polski +locale_pt-BR=Portugu\u00eas (Brasil) +locale_ru=\u0420\u0443\u0441\u0441\u043a\u0438\u0439 +locale_sk=Sloven\u010dina +locale_sv=Svenska +locale_tr=T\u00FCrk\u00E7e +locale_zh-CN=\u4e2d\u6587\u7b80\u4f53 + +# Applications +applicationName=Name +applicationType=Application Type +applicationInUse=In-use app only +clearAllFilter=Clear all filters +activeFilters=Active filters +filterByName=Filter By Name ... +allApps=All applications +internalApps=Internal applications +thirdpartyApps=Third-Party applications +appResults=Results +clientNotFoundMessage=Client not found. + +# Linked account +authorizedProvider=Authorized Provider +authorizedProviderMessage=Authorized Providers linked with your account +identityProvider=Identity Provider +identityProviderMessage=To link your account with identity providers you have configured +socialLogin=Social Login +userDefined=User Defined +removeAccess=Remove Access +removeAccessMessage=You will need to grant access again, if you want to use this app account. + +#Authenticator +authenticatorStatusMessage=Two-factor authentication is currently +authenticatorFinishSetUpTitle=Your Two-Factor Authentication +authenticatorFinishSetUpMessage=Each time you sign in to your Keycloak account, you will be asked to provide a two-factor authentication code. +authenticatorSubTitle=Set Up Two-Factor Authentication +authenticatorSubMessage=To enhance the security of your account, enable at least one of the available two-factor authentication methods. +authenticatorMobileTitle=Mobile Authenticator +authenticatorMobileMessage=Use mobile Authenticator to get Verification codes as the two-factor authentication. +authenticatorMobileFinishSetUpMessage=The authenticator has been bound to your phone. +authenticatorActionSetup=Set up +authenticatorSMSTitle=SMS Code +authenticatorSMSMessage=Keycloak will send the Verification code to your phone as the two-factor authentication. +authenticatorSMSFinishSetUpMessage=Text messages are sent to +authenticatorDefaultStatus=Default +authenticatorChangePhone=Change Phone Number +authenticatorBackupCodesTitle=Backup Codes +authenticatorBackupCodesMessage=Get your 8-digit backup codes +authenticatorBackupCodesFinishSetUpMessage=12 backup codes were generated at this time. Each one can be used once. + +#Authenticator - Mobile Authenticator setup +authenticatorMobileSetupTitle=Mobile Authenticator Setup +smscodeIntroMessage=Enter your phone number and a verification code will be sent to your phone. +mobileSetupStep1=Install an authenticator application on your phone. The applications listed here are supported. +mobileSetupStep2=Open the application and scan the barcode: +mobileSetupStep3=Enter the one-time code provided by the application and click Save to finish the setup. +scanBarCode=Want to scan the barcode? +enterBarCode=Enter the one-time code +doCopy=Copy +doFinish=Finish + +#Authenticator - SMS Code setup +authenticatorSMSCodeSetupTitle=SMS Code Setup +chooseYourCountry=Choose your country +enterYourPhoneNumber=Enter your phone number +sendVerficationCode=Send Verification Code +enterYourVerficationCode=Enter your verification code + +#Authenticator - backup Code setup +authenticatorBackupCodesSetupTitle=Backup Codes Setup +backupcodesIntroMessage=If you lose access to your phone, you can still log into your account through backup codes. Keep them somewhere safe and accessible. +realmName=Realm +doDownload=Download +doPrint=Print +backupCodesTips-1=Each backup code can be used once. +backupCodesTips-2=These codes were generated on +generateNewBackupCodes=Generate New Backup Codes +backupCodesTips-3=When you generate new backup codes, the current codes will not work anymore. +backtoAuthenticatorPage=Back to Authenticator Page + + +#Resources +resources=Resources +sharedwithMe=Shared with Me +share=Share +sharedwith=Shared with +accessPermissions=Access Permissions +permissionRequests=Permission Requests +approve=Approve +approveAll=Approve all +people=people +perPage=per page +currentPage=Current Page +sharetheResource=Share the resource +group=Group +selectPermission=Select Permission +addPeople=Add people to share your resource with +addTeam=Add team to share your resource with +myPermissions=My Permissions +waitingforApproval=Waiting for approval +anyPermission=Any Permission + +# Openshift messages +openshift.scope.user_info=User information +openshift.scope.user_check-access=User access information +openshift.scope.user_full=Full Access +openshift.scope.list-projects=List projects diff --git a/base/account/password.ftl b/base/account/password.ftl new file mode 100755 index 0000000..4a043f2 --- /dev/null +++ b/base/account/password.ftl @@ -0,0 +1,59 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='password' bodyClass='password'; section> + +
+
+

${msg("changePasswordHtmlTitle")}

+
+
+ ${msg("allFieldsRequired")} +
+
+ +
+ + + <#if password.passwordSet> +
+
+ +
+ +
+ +
+
+ + + + +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+ + diff --git a/base/account/resource-detail.ftl b/base/account/resource-detail.ftl new file mode 100755 index 0000000..2c963d7 --- /dev/null +++ b/base/account/resource-detail.ftl @@ -0,0 +1,277 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='authorization' bodyClass='authorization'; section> + + + + +
+
+

+ ${msg("myResources")} <#if authorization.resource.displayName??>${authorization.resource.displayName}<#else>${authorization.resource.name} +

+
+
+ + <#if authorization.resource.iconUri??> + +
+ + +
+
+

+ ${msg("peopleAccessResource")} +

+
+
+
+
+ + + + + + + + + + + <#if authorization.resource.shares?size != 0> + <#list authorization.resource.shares as permission> + + + + + + + + + + + + + <#else> + + + + + +
${msg("user")}${msg("permission")}${msg("date")}${msg("action")}
+ <#if permission.requester.email??>${permission.requester.email}<#else>${permission.requester.username} + + <#if permission.scopes?size != 0> + <#list permission.scopes as scope> + <#if scope.granted && scope.scope??> + + <#else> + ${msg("anyPermission")} + + + <#else> + Any action + + + ${permission.createdDate?datetime} + + ${msg("doRevoke")} +
${msg("resourceIsNotBeingShared")}
+ +
+
+
+
+

+ ${msg("resourceManagedPolicies")} +

+
+
+
+
+ + + + + + + + + + <#if authorization.resource.policies?size != 0> + <#list authorization.resource.policies as permission> + + + + + + + + + + + + <#else> + + + + + +
${msg("description")}${msg("permission")}${msg("action")}
+ <#if permission.description??> + ${permission.description} + + + <#if permission.scopes?size != 0> + <#list permission.scopes as scope> + + + <#else> + ${msg("anyAction")} + + + ${msg("doRevoke")} +
+ ${msg("resourceNoPermissionsGrantingAccess")} +
+ +
+
+
+
+

+ ${msg("shareWithOthers")} +

+
+
+
+
+
+ +
+ * +
+
+
+
+ +
+
+
+ <#list authorization.resource.scopes as scope> + + +
+ +
+
+
+
+
+
+ diff --git a/base/account/resources.ftl b/base/account/resources.ftl new file mode 100755 index 0000000..d86e8bc --- /dev/null +++ b/base/account/resources.ftl @@ -0,0 +1,403 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='authorization' bodyClass='authorization'; section> + + +
+
+

+ ${msg("myResources")} +

+
+
+ + <#if authorization.resourcesWaitingApproval?size != 0> +
+
+

+ ${msg("needMyApproval")} +

+
+
+
+
+ + + + + + + + + + + <#list authorization.resourcesWaitingApproval as resource> + <#list resource.permissions as permission> + + + + + + + + + + + + + + +
${msg("resource")}${msg("requestor")}${msg("permissionRequestion")}${msg("action")}
+ <#if resource.displayName??>${resource.displayName}<#else>${resource.name} + + <#if permission.requester.email??>${permission.requester.email}<#else>${permission.requester.username} + + <#list permission.scopes as scope> + <#if scope.scope??> + + <#else> + ${msg("anyPermission")} + + + + ${msg("doApprove")} + ${msg("doDeny")} +
+
+
+ + +
+
+

+ ${msg("myResourcesSub")} +

+
+
+
+
+ + + + + + + + + + + <#if authorization.resources?size != 0> + <#list authorization.resources as resource> + + + + + + + <#else> + + + + + +
${msg("resource")}${msg("application")}${msg("peopleSharingThisResource")}
+ + <#if resource.displayName??>${resource.displayName}<#else>${resource.name} + + + <#if resource.resourceServer.baseUri??> + ${resource.resourceServer.name} + <#else> + ${resource.resourceServer.name} + + + <#if resource.shares?size != 0> + ${resource.shares?size} + <#else> + ${msg("notBeingShared")} + +
${msg("notHaveAnyResource")}
+
+
+ +
+
+

+ ${msg("resourcesSharedWithMe")} +

+
+
+
+
+
+ + + + + + + + + + + + + + <#if authorization.sharedResources?size != 0> + <#list authorization.sharedResources as resource> + + + + + + + + + + <#else> + + + + + +
disabled="true" + ${msg("resource")}${msg("owner")}${msg("application")}${msg("permission")}${msg("date")}
+ + + <#if resource.displayName??>${resource.displayName}<#else>${resource.name} + + ${resource.ownerName} + + <#if resource.resourceServer.baseUri??> + ${resource.resourceServer.name} + <#else> + ${resource.resourceServer.name} + + + <#if resource.permissions?size != 0> +
    + <#list resource.permissions as permission> + <#list permission.scopes as scope> + <#if scope.granted && scope.scope??> +
  • + <#if scope.scope.displayName??> + ${scope.scope.displayName} + <#else> + ${scope.scope.name} + +
  • + <#else> + ${msg("anyPermission")} + + + +
+ <#else> + Any action + +
+ ${resource.permissions[0].grantedDate?datetime} +
${msg("noResourcesSharedWithYou")}
+
+
+ <#if authorization.sharedResources?size != 0> + + +
+ + <#if authorization.resourcesWaitingOthersApproval?size != 0> +
+
+
+

+ ${msg("requestsWaitingApproval")} +

+
+
+
+
+ ${msg("havePermissionRequestsWaitingForApproval",authorization.resourcesWaitingOthersApproval?size)} + ${msg("clickHereForDetails")} +
+
+
+
+
+
+
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/base/account/sessions.ftl b/base/account/sessions.ftl new file mode 100755 index 0000000..89dbf65 --- /dev/null +++ b/base/account/sessions.ftl @@ -0,0 +1,44 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='sessions' bodyClass='sessions'; section> + +
+
+

${msg("sessionsHtmlTitle")}

+
+
+ + + + + + + + + + + + + + <#list sessions.sessions as session> + + + + + + + + + + +
${msg("ip")}${msg("started")}${msg("lastAccess")}${msg("expires")}${msg("clients")}
${session.ipAddress}${session.started?datetime}${session.lastAccess?datetime}${session.expires?datetime} + <#list session.clients as client> + ${client}
+ +
+ +
+ + +
+ + diff --git a/base/account/template.ftl b/base/account/template.ftl new file mode 100644 index 0000000..6f08eef --- /dev/null +++ b/base/account/template.ftl @@ -0,0 +1,88 @@ +<#macro mainLayout active bodyClass> + + + + + + + + ${msg("accountManagementTitle")} + + <#if properties.stylesCommon?has_content> + <#list properties.stylesCommon?split(' ') as style> + + + + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(' ') as script> + + + + + + + + +
+
+ +
+ +
+ <#if message?has_content> +
+ <#if message.type=='success' > + <#if message.type=='error' > + +
+ + + <#nested "content"> +
+
+ + + + \ No newline at end of file diff --git a/base/account/totp.ftl b/base/account/totp.ftl new file mode 100755 index 0000000..987fe24 --- /dev/null +++ b/base/account/totp.ftl @@ -0,0 +1,141 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='totp' bodyClass='totp'; section> + +
+
+

${msg("authenticatorTitle")}

+
+ <#if totp.otpCredentials?size == 0> +
+ * ${msg("requiredFields")} +
+ +
+ + <#if totp.enabled> + + + <#if totp.otpCredentials?size gt 1> + + + + <#else> + + + + + + + <#list totp.otpCredentials as credential> + + + <#if totp.otpCredentials?size gt 1> + + + + + + + +
${msg("configureAuthenticators")}
${msg("configureAuthenticators")}
${msg("mobile")}${credential.id}${credential.userLabel!} +
+ + + + +
+
+ <#else> + +
+ +
    +
  1. +

    ${msg("totpStep1")}

    + +
      + <#list totp.policy.supportedApplications as app> +
    • ${app}
    • + +
    +
  2. + + <#if mode?? && mode = "manual"> +
  3. +

    ${msg("totpManualStep2")}

    +

    ${totp.totpSecretEncoded}

    +

    ${msg("totpScanBarcode")}

    +
  4. +
  5. +

    ${msg("totpManualStep3")}

    +
      +
    • ${msg("totpType")}: ${msg("totp." + totp.policy.type)}
    • +
    • ${msg("totpAlgorithm")}: ${totp.policy.getAlgorithmKey()}
    • +
    • ${msg("totpDigits")}: ${totp.policy.digits}
    • + <#if totp.policy.type = "totp"> +
    • ${msg("totpInterval")}: ${totp.policy.period}
    • + <#elseif totp.policy.type = "hotp"> +
    • ${msg("totpCounter")}: ${totp.policy.initialCounter}
    • + +
    +
  6. + <#else> +
  7. +

    ${msg("totpStep2")}

    +

    Figure: Barcode

    +

    ${msg("totpUnableToScan")}

    +
  8. + +
  9. +

    ${msg("totpStep3")}

    +

    ${msg("totpStep3DeviceName")}

    +
  10. +
+ +
+ +
+ +
+
+ * +
+ +
+ + +
+ + +
+ +
+
+ <#if totp.otpCredentials?size gte 1>* +
+ +
+ +
+
+ +
+
+
+ + +
+
+
+
+ + + diff --git a/base/admin/index.ftl b/base/admin/index.ftl new file mode 100755 index 0000000..ec858db --- /dev/null +++ b/base/admin/index.ftl @@ -0,0 +1,101 @@ + + + + + + + + + + + + <#if properties.stylesCommon?has_content> + <#list properties.stylesCommon?split(' ') as style> + + + <#list properties.styles?split(' ') as style> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(' ') as script> + + + + + + + + +
+
+
+
+
+ +
+
+ + + + + + + {{notification.header}} {{notification.message}} +
+
+ +
Loading...
+ + + diff --git a/base/admin/messages/admin-messages_en.properties b/base/admin/messages/admin-messages_en.properties new file mode 100644 index 0000000..0b03ae6 --- /dev/null +++ b/base/admin/messages/admin-messages_en.properties @@ -0,0 +1,1838 @@ +consoleTitle=Keycloak Admin Console + +# Common messages +enabled=Enabled +hidden=Hidden +link-only-column=Link only +name=Name +displayName=Display name +displayNameHtml=HTML Display name +save=Save +cancel=Cancel +next=Next +onText=ON +offText=OFF +client=Client +clients=Clients +clear=Clear +selectOne=Select One... + +true=True +false=False + +endpoints=Endpoints + +# Angular date filter format strings: https://docs.angularjs.org/api/ng/filter/date +dateFormat=shortDate +timeFormat=mediumTime + +# Realm settings +realm-detail.enabled.tooltip=Users and clients can only access a realm if it's enabled +realm-detail.protocol-endpoints.tooltip=Shows the configuration of the protocol endpoints +realm-detail.protocol-endpoints.oidc=OpenID Endpoint Configuration +realm-detail.protocol-endpoints.saml=SAML 2.0 Identity Provider Metadata +realm-detail.userManagedAccess.tooltip=If enabled, users are allowed to manage their resources and permissions using the Account Management Console. +userManagedAccess=User-Managed Access +registrationAllowed=User registration +registrationAllowed.tooltip=Enable/disable the registration page. A link for registration will show on login page too. +registrationEmailAsUsername=Email as username +registrationEmailAsUsername.tooltip=If enabled then username field is hidden from registration form and email is used as username for new user. +editUsernameAllowed=Edit username +editUsernameAllowed.tooltip=If enabled, the username field is editable, readonly otherwise. +resetPasswordAllowed=Forgot password +resetPasswordAllowed.tooltip=Show a link on login page for user to click on when they have forgotten their credentials. +rememberMe=Remember Me +rememberMe.tooltip=Show checkbox on login page to allow user to remain logged in between browser restarts until session expires. +loginWithEmailAllowed=Login with email +loginWithEmailAllowed.tooltip=Allow users to log in with their email address. +duplicateEmailsAllowed=Duplicate emails +duplicateEmailsAllowed.tooltip=Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses. +verifyEmail=Verify email +verifyEmail.tooltip=Require users to verify their email address after initial login or after address changes are submitted. +sslRequired=Require SSL +sslRequired.option.all=all requests +sslRequired.option.external=external requests +sslRequired.option.none=none +sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses. +publicKeys=Public keys +publicKey=Public key +privateKey=Private key +gen-new-keys=Generate new keys +certificate=Certificate +host=Host +smtp-host=SMTP Host +port=Port +smtp-port=SMTP Port (defaults to 25) +smtp-password.tooltip=SMTP password. This field is able to obtain its value from vault, use ${vault.ID} format. +from=From +fromDisplayName=From Display Name +fromDisplayName.tooltip=A user-friendly name for the 'From' address (optional). +replyTo=Reply To +replyToDisplayName=Reply To Display Name +replyToDisplayName.tooltip=A user-friendly name for the 'Reply-To' address (optional). +envelopeFrom=Envelope From +envelopeFrom.tooltip=An email address used for bounces (optional). +sender-email-addr=Sender Email Address +sender-email-addr-display=Display Name for Sender Email Address +reply-to-email-addr=Reply To Email Address +reply-to-email-addr-display=Display Name for Reply To Email Address +sender-envelope-email-addr=Sender Envelope Email Address +enable-ssl=Enable SSL +enable-start-tls=Enable StartTLS +enable-auth=Enable Authentication +username=Username +login-username=Login Username +password=Password +login-password=Login Password +login-theme=Login Theme +login-theme.tooltip=Select theme for login, OTP, grant, registration, and forgot password pages. +account-theme=Account Theme +account-theme.tooltip=Select theme for user account management pages. +admin-console-theme=Admin Console Theme +select-theme-admin-console=Select theme for admin console. +email-theme=Email Theme +select-theme-email=Select theme for emails that are sent by the server. +i18n-enabled=Internationalization Enabled +supported-locales=Supported Locales +supported-locales.placeholder=Type a locale and enter +default-locale=Default Locale +localization-upload-file=Upload localization JSON file +missing-locale=Missing locale. +missing-file=Missing file. Please select a file to upload. +localization-file.upload.success=The localization data has been loaded from file. +localization-file.upload.error=The file can not be uploaded. Please verify the file. +localization-show=Show realm specific localizations +no-localizations-configured=No realm specific localizations configured +add-localization-text=Add localization text +localization-text.create.success=The localization text has been created. +localization-text.update.success=The localization text has been updated. +localization-text.remove.success=The localization text has been deleted. +realm-cache-clear=Realm Cache +realm-cache-clear.tooltip=Clears all entries from the realm cache (this will clear entries for all realms) +user-cache-clear=User Cache +user-cache-clear.tooltip=Clears all entries from the user cache (this will clear entries for all realms) +keys-cache-clear=Keys Cache +keys-cache-clear.tooltip=Clears all entries from the cache of external public keys. These are keys of external clients or identity providers. (this will clear entries for all realms) +default-signature-algorithm=Default Signature Algorithm +default-signature-algorithm.tooltip=Default algorithm used to sign tokens for the realm +revoke-refresh-token=Revoke Refresh Token +revoke-refresh-token.tooltip=If enabled a refresh token can only be used up to 'Refresh Token Max Reuse' and is revoked when a different token is used. Otherwise refresh tokens are not revoked when used and can be used multiple times. +refresh-token-max-reuse=Refresh Token Max Reuse +refresh-token-max-reuse.tooltip=Maximum number of times a refresh token can be reused. When a different token is used, revocation is immediate. +sso-session-idle=SSO Session Idle +seconds=Seconds +minutes=Minutes +hours=Hours +days=Days +sso-session-max=SSO Session Max +sso-session-idle.tooltip=Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired. +sso-session-max.tooltip=Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired. +sso-session-idle-remember-me=SSO Session Idle Remember Me +sso-session-idle-remember-me.tooltip=Time a remember me session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired. If not set it uses the standard SSO Session Idle value. +sso-session-max-remember-me=SSO Session Max Remember Me +sso-session-max-remember-me.tooltip=Max time before a session is expired when the user has set the remember me option. Tokens and browser sessions are invalidated when a session is expired. If not set, it uses the standard SSO Session Max value. +offline-session-idle=Offline Session Idle +offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period; otherwise offline session will expire. +realm-detail.hostname=Hostname +realm-detail.hostname.tooltip=Set the hostname for the realm. Use in combination with the fixed hostname provider to override the server hostname for a specific realm. +realm-detail.frontendUrl=Frontend URL +realm-detail.frontendUrl.tooltip=Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm. + +## KEYCLOAK-7688 Offline Session Max for Offline Token +offline-session-max-limited=Offline Session Max Limited +offline-session-max-limited.tooltip=Enable Offline Session Max. +offline-session-max=Offline Session Max +offline-session-max.tooltip=Max time before an offline session is expired regardless of activity. +client-session-idle=Client Session Idle +client-session-idle.tooltip=Time a client session is allowed to be idle before it expires. Tokens are invalidated when a client session is expired. If not set it uses the standard SSO Session Idle value. +client-session-max=Client Session Max +client-session-max.tooltip=Max time before a client session is expired. Tokens are invalidated when a client session is expired. If not set, it uses the standard SSO Session Max value. +client-offline-session-idle=Client Offline Session Idle +client-offline-session-idle.tooltip=Time a client offline session is allowed to be idle before it expires. Offline tokens are invalidated when a client offline session is expired. If not set it uses the Offline Session Idle value. +client-offline-session-max=Client Offline Session Max +client-offline-session-max.tooltip=Max time before a client offline session is expired. Offline tokens are invalidated when a client offline session is expired. If not set, it uses the Offline Session Max value. +access-token-lifespan=Access Token Lifespan +access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout. +access-token-lifespan-for-implicit-flow=Access Token Lifespan For Implicit Flow +access-token-lifespan-for-implicit-flow.tooltip=Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is a separate timeout different to 'Access Token Lifespan'. +action-token-generated-by-admin-lifespan=Default Admin-Initiated Action Lifespan +action-token-generated-by-admin-lifespan.tooltip=Maximum time before an action permit sent to a user by administrator is expired. This value is recommended to be long to allow administrators send e-mails for users that are currently offline. The default timeout can be overridden immediately before issuing the token. +action-token-generated-by-user-lifespan=User-Initiated Action Lifespan +action-token-generated-by-user-lifespan.tooltip=Maximum time before an action permit sent by a user (such as a forgot password e-mail) is expired. This value is recommended to be short because it is expected that the user would react to self-created action quickly. +saml-assertion-lifespan=Assertion Lifespan +saml-assertion-lifespan.tooltip=Lifespan set in the SAML assertion conditions. After that time the assertion will be invalid. The "SessionNotOnOrAfter" attribute is not modified and continue using the "SSO Session Max" time defined at realm level. + +action-token-generated-by-user.execute-actions=Execute Actions +action-token-generated-by-user.idp-verify-account-via-email=IdP Account E-mail Verification +action-token-generated-by-user.reset-credentials=Forgot Password +action-token-generated-by-user.verify-email=E-mail Verification +action-token-generated-by-user.tooltip=Override default settings of maximum time before an action permit sent by a user (such as a forgot password e-mail) is expired for specific action. This value is recommended to be short because it is expected that the user would react to self-created action quickly. +action-token-generated-by-user.reset=Reset +action-token-generated-by-user.operation=Override User-Initiated Action Lifespan + +client-login-timeout=Client login timeout +client-login-timeout.tooltip=Max time a client has to finish the access token protocol. This should normally be 1 minute. +login-timeout=Login timeout +login-timeout.tooltip=Max time a user has to complete a login. This is recommended to be relatively long, such as 30 minutes or more. +login-action-timeout=Login action timeout +login-action-timeout.tooltip=Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long, such as 5 minutes or more. + +oauth2-device-code-lifespan=OAuth 2.0 Device Code Lifespan +oauth2-device-code-lifespan.tooltip=Max time before the device code and user code are expired. This value needs to be a long enough lifetime to be usable (allowing the user to retrieve their secondary device, navigate to the verification URI, login, etc.), but should be sufficiently short to limit the usability of a code obtained for phishing. +oauth2-device-polling-interval=OAuth 2.0 Device Polling Interval +oauth2-device-polling-interval.tooltip=The minimum amount of time in seconds that the client should wait between polling requests to the token endpoint. + +headers=Headers +brute-force-detection=Brute Force Detection +x-frame-options=X-Frame-Options +x-frame-options-tooltip=Default value prevents pages from being included by non-origin iframes (click label for more information) +content-sec-policy=Content-Security-Policy +content-sec-policy-tooltip=Default value prevents pages from being included by non-origin iframes (click label for more information) +content-sec-policy-report-only=Content-Security-Policy-Report-Only +content-sec-policy-report-only-tooltip=For testing Content Security Policies +content-type-options=X-Content-Type-Options +content-type-options-tooltip=Default value prevents Internet Explorer and Google Chrome from MIME-sniffing a response away from the declared content-type (click label for more information) +robots-tag=X-Robots-Tag +robots-tag-tooltip=Prevent pages from appearing in search engines (click label for more information) +x-xss-protection=X-XSS-Protection +x-xss-protection-tooltip=This header configures the Cross-site scripting (XSS) filter in your browser. Using the default behavior, the browser will prevent rendering of the page when a XSS attack is detected (click label for more information) +strict-transport-security=HTTP Strict Transport Security (HSTS) +strict-transport-security-tooltip=The Strict-Transport-Security HTTP header tells browsers to always use HTTPS. Once a browser sees this header, it will only visit the site over HTTPS for the time specified (1 year) at max-age, including the subdomains. +permanent-lockout=Permanent Lockout +permanent-lockout.tooltip=Lock the user permanently when the user exceeds the maximum login failures. +max-login-failures=Max Login Failures +max-login-failures.tooltip=How many failures before wait is triggered. +wait-increment=Wait Increment +wait-increment.tooltip=When failure threshold has been met, how much time should the user be locked out? +quick-login-check-millis=Quick Login Check Milli Seconds +quick-login-check-millis.tooltip=If a failure happens concurrently too quickly, lock out the user. +min-quick-login-wait=Minimum Quick Login Wait +min-quick-login-wait.tooltip=How long to wait after a quick login failure. +max-wait=Max Wait +max-wait.tooltip=Max time a user will be locked out. +failure-reset-time=Failure Reset Time +failure-reset-time.tooltip=When will failure count be reset? +realm-tab-login=Login +realm-tab-keys=Keys +realm-tab-email=Email +realm-tab-themes=Themes +realm-tab-localization=Localization +realm-tab-cache=Cache +realm-tab-tokens=Tokens +realm-tab-client-registration=Client Registration +realm-tab-security-defenses=Security Defenses +realm-tab-general=General +add-realm=Add realm + +#Session settings +realm-sessions=Realm Sessions +revocation=Revocation +logout-all=Logout all +active-sessions=Active Sessions +offline-sessions=Offline Sessions +sessions=Sessions +not-before=Not Before +not-before.tooltip=Revoke any tokens issued before this date. +set-to-now=Set to now +push=Push +push.tooltip=For every client that has an admin URL, notify them of the new revocation policy. + +#Protocol Mapper +usermodel.prop.label=Property +usermodel.prop.tooltip=Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method. +usermodel.attr.label=User Attribute +usermodel.attr.tooltip=Name of stored user attribute which is the name of an attribute within the UserModel.attribute map. +userSession.modelNote.label=User Session Note +userSession.modelNote.tooltip=Name of stored user session note within the UserSessionModel.note map. +multivalued.label=Multivalued +multivalued.tooltip=Indicates if attribute supports multiple values. If true, the list of all values of this attribute will be set as claim. If false, just first value will be set as claim +aggregate.attrs.label=Aggregate attribute values +aggregate.attrs.tooltip=Indicates if attribute values should be aggregated with the group attributes. If using OpenID Connect mapper the multivalued option needs to be enabled too in order to get all the values. Duplicated values are discarded and the order of values is not guaranteed with this option. +selectRole.label=Select Role +selectRole.tooltip=Enter role in the textbox to the left, or click this button to browse and select the role you want. +tokenClaimName.label=Token Claim Name +tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created. To prevent nesting and use dot literally, escape the dot with backslash (\\.). +jsonType.label=Claim JSON Type +jsonType.tooltip=JSON type that should be used to populate the json claim in the token. long, int, boolean, String and JSON are valid values. +includeInIdToken.label=Add to ID token +includeInIdToken.tooltip=Should the claim be added to the ID token? +includeInAccessToken.label=Add to access token +includeInAccessToken.tooltip=Should the claim be added to the access token? +includeInUserInfo.label=Add to userinfo +includeInUserInfo.tooltip=Should the claim be added to the userinfo? +usermodel.clientRoleMapping.clientId.label=Client ID +usermodel.clientRoleMapping.clientId.tooltip=Client ID for role mappings. Just client roles of this client will be added to the token. If this is unset, client roles of all clients will be added to the token. +usermodel.clientRoleMapping.rolePrefix.label=Client Role prefix +usermodel.clientRoleMapping.rolePrefix.tooltip=A prefix for each client role (optional). +usermodel.clientRoleMapping.tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created. To prevent nesting and use dot literally, escape the dot with backslash (\\.). The special token ${client_id} can be used and this will be replaced by the actual client ID. Example usage is 'resource_access.${client_id}.roles'. This is useful especially when you are adding roles from all the clients (Hence 'Client ID' switch is unset) and you want client roles of each client stored separately. +usermodel.realmRoleMapping.rolePrefix.label=Realm Role prefix +usermodel.realmRoleMapping.rolePrefix.tooltip=A prefix for each Realm Role (optional). +sectorIdentifierUri.label=Sector Identifier URI +sectorIdentifierUri.tooltip=Providers that use pairwise sub values and support Dynamic Client Registration SHOULD use the sector_identifier_uri parameter. It provides a way for a group of websites under common administrative control to have consistent pairwise sub values independent of the individual domain names. It also provides a way for Clients to change redirect_uri domains without having to reregister all their users. +pairwiseSubAlgorithmSalt.label=Salt +pairwiseSubAlgorithmSalt.tooltip=Salt used when calculating the pairwise subject identifier. If left blank, a salt will be generated. +addressClaim.street.label=User Attribute Name for Street +addressClaim.street.tooltip=Name of User Attribute, which will be used to map to 'street_address' subclaim inside 'address' token claim. Defaults to 'street' . +addressClaim.locality.label=User Attribute Name for Locality +addressClaim.locality.tooltip=Name of User Attribute, which will be used to map to 'locality' subclaim inside 'address' token claim. Defaults to 'locality' . +addressClaim.region.label=User Attribute Name for Region +addressClaim.region.tooltip=Name of User Attribute, which will be used to map to 'region' subclaim inside 'address' token claim. Defaults to 'region' . +addressClaim.postal_code.label=User Attribute Name for Postal Code +addressClaim.postal_code.tooltip=Name of User Attribute, which will be used to map to 'postal_code' subclaim inside 'address' token claim. Defaults to 'postal_code' . +addressClaim.country.label=User Attribute Name for Country +addressClaim.country.tooltip=Name of User Attribute, which will be used to map to 'country' subclaim inside 'address' token claim. Defaults to 'country' . +addressClaim.formatted.label=User Attribute Name for Formatted Address +addressClaim.formatted.tooltip=Name of User Attribute, which will be used to map to 'formatted' subclaim inside 'address' token claim. Defaults to 'formatted' . +included.client.audience.label=Included Client Audience +included.client.audience.tooltip=The Client ID of the specified audience client will be included in audience (aud) field of the token. If there are existing audiences in the token, the specified value is just added to them. It won't override existing audiences. +included.custom.audience.label=Included Custom Audience +included.custom.audience.tooltip=This is used just if 'Included Client Audience' is not filled. The specified value will be included in audience (aud) field of the token. If there are existing audiences in the token, the specified value is just added to them. It won't override existing audiences. + +# client details +clients.tooltip=Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles. +search.placeholder=Search... +search.loading=Searching... +create=Create +import=Import +client-id=Client ID +base-url=Base URL +actions=Actions +not-defined=Not defined +edit=Edit +delete=Delete +no-results=No results +no-clients-available=No clients available +add-client=Add Client +select-file=Select file +view-details=View details +clear-import=Clear import +client-id.tooltip=Specifies ID referenced in URI and tokens. For example 'my-client'. For SAML this is also the expected issuer value from authn requests +client.name.tooltip=Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client} +client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens. +alwaysDisplayInConsole=Always Display in Console +alwaysDisplayInConsole.tooltip=Always list this client in the Account Console, even if the user does not have an active session. +consent-required=Consent Required +consent-required.tooltip=If enabled, users have to consent to client access. +client.display-on-consent-screen=Display Client On Consent Screen +client.display-on-consent-screen.tooltip=Applicable just if Consent Required is on. If this switch is off, consent screen will contain just the consents corresponding to configured client scopes. If on, there will be also one item on consent screen about this client itself +client.consent-screen-text=Client Consent Screen Text +client.consent-screen-text.tooltip=Applicable just if 'Display Client On Consent Screen' is on for this client. Contains the text, which will be on consent screen about permissions specific just for this client +client-protocol=Client Protocol +client-protocol.tooltip='OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information. +access-type=Access Type +access-type.tooltip='Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login. +standard-flow-enabled=Standard Flow Enabled +standard-flow-enabled.tooltip=This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client. +implicit-flow-enabled=Implicit Flow Enabled +implicit-flow-enabled.tooltip=This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client. +direct-access-grants-enabled=Direct Access Grants Enabled +direct-access-grants-enabled.tooltip=This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client. +service-accounts-enabled=Service Accounts Enabled +service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client. In terms of OAuth2 specification, this enables support of 'Client Credentials Grant' for this client. +oauth2-device-authorization-grant-enabled=OAuth 2.0 Device Authorization Grant Enabled +oauth2-device-authorization-grant-enabled.tooltip=This enables support for OAuth 2.0 Device Authorization Grant, which means that client is an application on device that has limited input capabilities or lack a suitable browser. +oidc-ciba-grant-enabled=OIDC CIBA Grant Enabled +oidc-ciba-grant-enabled.tooltip=This enables support for OIDC CIBA Grant, which means that the user is authenticated via some external authentication device instead of the user's browser. +include-authnstatement=Include AuthnStatement +include-authnstatement.tooltip=Should a statement specifying the method and timestamp be included in login responses? +include-onetimeuse-condition=Include OneTimeUse Condition +include-onetimeuse-condition.tooltip=Should a OneTimeUse Condition be included in login responses? +artifact-binding = Force Artifact Binding +artifact-binding.tooltip = Should response messages be returned to the client through the SAML ARTIFACT binding system? +sign-documents=Sign Documents +sign-documents.tooltip=Should SAML documents be signed by the realm? +sign-documents-redirect-enable-key-info-ext=Optimize REDIRECT signing key lookup +sign-documents-redirect-enable-key-info-ext.tooltip=When signing SAML documents in REDIRECT binding for SP that is secured by Keycloak adapter, should the ID of the signing key be included in SAML protocol message in element? This optimizes validation of the signature as the validating party uses a single key instead of trying every known key for validation. +sign-assertions=Sign Assertions +sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting is not needed if document is already being signed. +signature-algorithm=Signature Algorithm +signature-algorithm.tooltip=The signature algorithm to use to sign documents. +canonicalization-method=Canonicalization Method +canonicalization-method.tooltip=Canonicalization Method for XML signatures. +encrypt-assertions=Encrypt Assertions +encrypt-assertions.tooltip=Should SAML assertions be encrypted with client's public key using AES? +client-signature-required=Client Signature Required +client-signature-required.tooltip=Will the client sign their saml requests and responses? And should they be validated? +force-post-binding=Force POST Binding +force-post-binding.tooltip=Always use POST binding for responses. +front-channel-logout=Front Channel Logout +front-channel-logout.tooltip=When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout. +force-name-id-format=Force Name ID Format +force-name-id-format.tooltip=Ignore requested NameID subject format and use admin console configured one. +name-id-format=Name ID Format +name-id-format.tooltip=The name ID format to use for the subject. +mapper.nameid.format.tooltip=Name ID Format using Mapper +root-url=Root URL +root-url.tooltip=Root URL appended to relative URLs +valid-redirect-uris=Valid Redirect URIs +valid-redirect-uris.tooltip=Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed such as 'http://example.com/*'. Relative path can be specified too such as /my/relative/path/*. Relative paths are relative to the client root URL, or if none is specified the auth server root URL is used. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request. +base-url.tooltip=Default URL to use when the auth server needs to redirect or link back to the client. +admin-url=Admin URL +admin-url.tooltip=URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other administrative tasks. Usually this is set to the base URL of the client. +master-saml-processing-url=Master SAML Processing URL +master-saml-processing-url.tooltip=If configured, this URL will be used for every binding to both the SP's Assertion Consumer and Single Logout Services. This can be individually overriden for each binding and service in the Fine Grain SAML Endpoint Configuration. +idp-sso-url-ref=IDP Initiated SSO URL Name +idp-sso-url-ref.tooltip=URL fragment name to reference client when you want to do IDP Initiated SSO. Leaving this empty will disable IDP Initiated SSO. The URL you will reference from your browser will be: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name} +idp-sso-url-ref.urlhint=Target IDP initiated SSO URL: +idp-sso-relay-state=IDP Initiated SSO Relay State +idp-sso-relay-state.tooltip=Relay state you want to send with SAML request when you want to do IDP Initiated SSO. +web-origins=Web Origins +web-origins.tooltip=Allowed CORS origins. To permit all origins of Valid Redirect URIs, add '+'. This does not include the '*' wildcard though. To permit all origins, explicitly add '*'. +backchannel-logout-url=Backchannel Logout URL +backchannel-logout-url.tooltip=URL that will cause the client to log itself out when a logout request is sent to this realm (via end_session_endpoint). If omitted, no logout request will be sent to the client is this case. +backchannel-logout-session-required=Backchannel Logout Session Required +backchannel-logout-session-required.tooltip=Specifying whether a sid (session ID) Claim is included in the Logout Token when the Backchannel Logout URL is used. +backchannel-logout-revoke-offline-sessions=Backchannel Logout Revoke Offline Sessions +backchannel-logout-revoke-offline-sessions.tooltip=Specifying whether a "revoke_offline_access" event is included in the Logout Token when the Backchannel Logout URL is used. Keycloak will revoke offline sessions when receiving a Logout Token with this event. +fine-oidc-endpoint-conf=Fine Grain OpenID Connect Configuration +fine-oidc-endpoint-conf.tooltip=Expand this section to configure advanced settings of this client related to OpenID Connect protocol +access-token-signed-response-alg=Access Token Signature Algorithm +access-token-signed-response-alg.tooltip=JWA algorithm used for signing access tokens. +id-token-signed-response-alg=ID Token Signature Algorithm +id-token-signed-response-alg.tooltip=JWA algorithm used for signing ID tokens. +id-token-encrypted-response-alg=ID Token Encryption Key Management Algorithm +id-token-encrypted-response-alg.tooltip=JWA Algorithm used for key management in encrypting ID tokens. This option is needed if you want encrypted ID tokens. If left empty, ID Tokens are just signed, but not encrypted. +id-token-encrypted-response-enc=ID Token Encryption Content Encryption Algorithm +id-token-encrypted-response-enc.tooltip=JWA Algorithm used for content encryption in encrypting ID tokens. This option is needed just if you want encrypted ID tokens. If left empty, ID Tokens are just signed, but not encrypted. +user-info-signed-response-alg=User Info Signed Response Algorithm +user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', User Info Response won't be signed and will be returned in application/json format. +request-object-signature-alg=Request Object Signature Algorithm +request-object-signature-alg.tooltip=JWA algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', Request object can be signed by any algorithm (including 'none' ). +request-object-required=Request Object Required +request-object-required.tooltip=Specifies if the client needs to provide a request object with their authorization requests, and what method they can use for this. If set to "not required", providing a request object is optional. In all other cases, providing a request object is mandatory. If set to "request", the request object must be provided by value. If set to "request_uri", the request object must be provided by reference. If set to "request or request_uri", either method can be used. +request-uris=Valid Request URIs +request-uris.tooltip=List of valid URIs, which can be used as values of 'request_uri' parameter during OpenID Connect authentication request. There is support for the same capabilities like for Valid Redirect URIs. For example wildcards or relative paths. +fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration +fine-saml-endpoint-conf.tooltip=Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service. +assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL +assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. +assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL +assertion-consumer-redirect-binding-url.tooltip=SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. +logout-service-post-binding-url=Logout Service POST Binding URL +logout-service-post-binding-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding +logout-service-redir-binding-url=Logout Service Redirect Binding URL +logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding. +logout-service-artifact-binding-url=Logout Service ARTIFACT Binding URL +logout-service-artifact-binding-url.tooltip=SAML ARTIFACT Binding URL for the client's single logout service. You can leave this blank if you are using a different binding. +artifact-binding-url= Artifact Binding URL +artifact-binding-url.tooltip=URL to send the HTTP ARTIFACT messages to. You can leave this blank if you are using a different binding. This value should be set when forcing ARTIFACT binding together with IdP initiated login. +artifact-resolution-service-url= Artifact Resolution Service +artifact-resolution-service-url.tooltip= SAML Artifact resolution service for the client. This is the endpoint to which Keycloak will send a SOAP ArtifactResolve mesasge. You can leave this blank if you do not have a URL for this binding. +saml-signature-keyName-transformer=SAML Signature Key Name +saml-signature-keyName-transformer.tooltip=Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counterparty, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works. +oidc-compatibility-modes=OpenID Connect Compatibility Modes +oidc-compatibility-modes.tooltip=Expand this section to configure settings for backwards compatibility with older OpenID Connect / OAuth2 adapters. It is useful especially if your client uses older version of Keycloak / RH-SSO adapter. +exclude-session-state-from-auth-response=Exclude Session State From Authentication Response +exclude-session-state-from-auth-response.tooltip=If this is on, the parameter 'session_state' will not be included in OpenID Connect Authentication Response. It is useful if your client uses older OIDC / OAuth2 adapter, which does not support 'session_state' parameter. +use-refresh-tokens=Use Refresh Tokens +use-refresh-tokens.tooltip=If this is on, a refresh_token will be created and added to the token response. If this is off then no refresh_token will be generated. +use-refresh-token-for-client-credentials-grant=Use Refresh Tokens For Client Credentials Grant +use-refresh-token-for-client-credentials-grant.tooltip=If this is on, a refresh_token will be created and added to the token response if the client_credentials grant is used. The OAuth 2.0 RFC6749 Section 4.4.3 states that a refresh_token should not be generated when client_credentials grant is used. If this is off then no refresh_token will be generated and the associated user session will be removed. + +# client import +import-client=Import Client +format-option=Format Option +select-format=Select a Format +import-file=Import File + +# client tabs +settings=Settings +credentials=Credentials +saml-keys=SAML Keys +roles=Roles +mappers=Mappers +mappers.tooltip=Protocol mappers perform transformation on tokens and documents. They can do things like map user data into protocol claims, or just transform any requests going between the client and auth server. +scope=Scope +scope.tooltip=Scope mappings allow you to restrict which user role mappings are included within the access token requested by the client. +sessions.tooltip=View active sessions for this client. Allows you to see which users are active and when they logged in. +offline-access=Offline Access +offline-access.tooltip=View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it. To revoke all tokens for the client, go to the Revocation tab and set Not Before to Now. +clustering=Clustering +installation=Installation +installation.tooltip=Helper utility for generating various client adapter configuration formats which you can download or cut and paste to configure your clients. +service-account-roles=Service Account Roles +service-account-roles.tooltip=Allows you to authenticate role mappings for the service account dedicated to this client. + +# client credentials +client-authenticator=Client Authenticator +client-authenticator.tooltip=Client Authenticator used for authentication of this client against Keycloak server +certificate.tooltip=Client Certificate for validate JWT issued by client and signed by Client private key from your keystore. +publicKey.tooltip=Public Key for validate JWT issued by client and signed by Client private key. +no-client-certificate-configured=No client certificate configured +gen-new-keys-and-cert=Generate new keys and certificate +import-certificate=Import Certificate +gen-client-private-key=Generate Client Private Key +generate-private-key=Generate Private Key +kid=Kid +kid.tooltip=KID (Key ID) of the client public key from imported JWKS. +token-endpoint-auth-signing-alg=Signature Algorithm +token-endpoint-auth-signing-alg.tooltip=JWA algorithm, which the client needs to use when signing a JWT for authentication. If left blank, the client is allowed to use any algorithm. +use-jwks-url=Use JWKS URL +use-jwks-url.tooltip=If the switch is on, client public keys will be downloaded from given JWKS URL. This allows great flexibility because new keys will be always re-downloaded again when client generates new keypair. If the switch is off, public key (or certificate) from the Keycloak DB is used, so when client keypair changes, you always need to import new key (or certificate) to the Keycloak DB as well. +jwks-url=JWKS URL +jwks-url.tooltip=URL where client keys in JWK format are stored. See JWK specification for more details. If you use Keycloak client adapter with "jwt" credential, you can use URL of your app with '/k_jwks' suffix. For example 'http://www.myhost.com/myapp/k_jwks' . +pkce-enabled=Use PKCE +pkce-enabled.tooltip=Use PKCE (Proof of Key-code exchange) for IdP Brokering +pkce-method=PKCE Method +pkce-method.tooltip=PKCE Method to use +pkce.plain.option=Plain +pkce.s256.option=S256 +archive-format=Archive Format +archive-format.tooltip=Java keystore or PKCS12 archive format. +key-alias=Key Alias +key-alias.tooltip=Archive alias for your private key and certificate. +key-password=Key Password +key-password.tooltip=Password to access the private key in the archive +store-password=Store Password +store-password.tooltip=Password to access the archive itself +generate-and-download=Generate and Download +client-certificate-import=Client Certificate Import +import-client-certificate=Import Client Certificate +jwt-import.key-alias.tooltip=Archive alias for your certificate. +secret=Secret +regenerate-secret=Regenerate Secret +registrationAccessToken=Registration access token +registrationAccessToken.regenerate=Regenerate registration access token +registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service. +add-role=Add Role +role-name=Role Name +composite=Composite +description=Description +no-client-roles-available=No client roles available +composite-roles=Composite Roles +composite-roles.tooltip=When this role is (un)assigned to a user any role associated with it will be (un)assigned implicitly. +realm-roles=Realm Roles +available-roles=Available Roles +add-selected=Add selected +associated-roles=Associated Roles +composite.associated-realm-roles.tooltip=Realm level roles associated with this composite role. +composite.available-realm-roles.tooltip=Realm level roles that you can associate to this composite role. +remove-selected=Remove selected +client-roles=Client Roles +select-client-to-view-roles=Select client to view roles for client +available-roles.tooltip=Roles from this client that you can associate to this composite role. +client.associated-roles.tooltip=Client roles associated with this composite role. +add-builtin=Add Builtin +category=Category +type=Type +priority-order=Priority Order +no-mappers-available=No mappers available +add-builtin-protocol-mappers=Add Builtin Protocol Mappers +add-builtin-protocol-mapper=Add Builtin Protocol Mapper +scope-mappings=Scope Mappings +full-scope-allowed=Full Scope Allowed +full-scope-allowed.tooltip=Allows you to disable all restrictions. +scope.available-roles.tooltip=Realm level roles that can be assigned to scope. +assigned-roles=Assigned Roles +assigned-roles.tooltip=Realm level roles assigned to scope. +effective-roles=Effective Roles +realm.effective-roles.tooltip=Assigned realm level roles that may have been inherited from a composite role. +select-client-roles.tooltip=Select client to view roles for client +assign.available-roles.tooltip=Client roles available to be assigned. +client.assigned-roles.tooltip=Assigned client roles. +client.effective-roles.tooltip=Assigned client roles that may have been inherited from a composite role. +basic-configuration=Basic configuration +node-reregistration-timeout=Node Re-registration Timeout +node-reregistration-timeout.tooltip=Interval to specify max time for registered clients cluster nodes to re-register. If cluster node will not send re-registration request to Keycloak within this time, it will be unregistered from Keycloak +registered-cluster-nodes=Registered cluster nodes +register-node-manually=Register node manually +test-cluster-availability=Test cluster availability +last-registration=Last registration +node-host=Node host +no-registered-cluster-nodes=No registered cluster nodes available +cluster-nodes=Cluster Nodes +add-node=Add Node +active-sessions.tooltip=Total number of active user sessions for this client. +show-sessions=Show Sessions +show-sessions.tooltip=Warning, this is a potentially expensive operation depending on the number of active sessions. +user=User +from-ip=From IP +session-start=Session Start +first-page=First Page +previous-page=Previous Page +next-page=Next Page +client-revoke.not-before.tooltip=Revoke any tokens issued before this date for this client. +client-revoke.push.tooltip=If the admin URL is configured for this client, push this policy to that client. +select-a-format=Select a Format +download=Download +offline-tokens=Offline Tokens +offline-tokens.tooltip=Total number of offline tokens for this client. +show-offline-tokens=Show Offline Tokens +show-offline-tokens.tooltip=Warning, this is a potentially expensive operation depending on the number of offline tokens. +token-issued=Token Issued +last-access=Last Access +last-refresh=Last Refresh +key-export=Key Export +key-import=Key Import +export-saml-key=Export SAML Key +import-saml-key=Import SAML Key +realm-certificate-alias=Realm Certificate Alias +realm-certificate-alias.tooltip=Realm certificate is stored in archive too. This is the alias to it. +signing-key=Signing Key +saml-signing-key=SAML Signing Key. +private-key=Private Key +generate-new-keys=Generate new keys +export=Export +encryption-key=Encryption Key +saml-encryption-key.tooltip=SAML Encryption Key. +service-accounts=Service Accounts +service-account.available-roles.tooltip=Realm level roles that can be assigned to service account. +service-account.assigned-roles.tooltip=Realm level roles assigned to service account. +service-account-is-not-enabled-for=Service account is not enabled for {{client}} +create-protocol-mappers=Create Protocol Mappers +create-protocol-mapper=Create Protocol Mapper +protocol=Protocol +protocol.tooltip=Protocol... +id=ID +mapper.name.tooltip=Name of the mapper. +mapper.consent-required.tooltip=When granting temporary access, must the user consent to providing this data to the client? +consent-text=Consent Text +consent-text.tooltip=Text to display on consent page. +mapper-type=Mapper Type +mapper-type.tooltip=Type of the mapper +user-label=User Label +data=Data +show-data=Show data... +position=Position +# realm identity providers +identity-providers=Identity Providers +table-of-identity-providers=Table of identity providers +add-provider.placeholder=Add provider... +provider=Provider +gui-order=GUI order +first-broker-login-flow=First Login Flow +post-broker-login-flow=Post Login Flow +sync-mode=Sync Mode +sync-mode.tooltip=Default sync mode for all mappers. The sync mode determines when user data will be synced using the mappers. Possible values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider. +sync-mode.inherit=inherit +sync-mode.legacy=legacy +sync-mode.import=import +sync-mode.force=force +sync-mode-override=Sync Mode Override +sync-mode-override.tooltip=Overrides the default sync mode of the IDP for this mapper. Values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider and 'inherit' to use the sync mode defined in the identity provider for this mapper. +redirect-uri=Redirect URI +redirect-uri.tooltip=The redirect uri to use when configuring the identity provider. +alias=Alias +display-name=Display Name +identity-provider.alias.tooltip=The alias uniquely identifies an identity provider and it is also used to build the redirect uri. +identity-provider.display-name.tooltip=Friendly name for Identity Providers. +identity-provider.enabled.tooltip=Enable/disable this identity provider. +authenticate-by-default=Authenticate by Default +identity-provider.authenticate-by-default.tooltip=Indicates if this provider should be tried by default for authentication even before displaying login screen. +store-tokens=Store Tokens +identity-provider.store-tokens.tooltip=Enable/disable if tokens must be stored after authenticating users. +stored-tokens-readable=Stored Tokens Readable +identity-provider.stored-tokens-readable.tooltip=Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role. +disableUserInfo=Disable User Info +identity-provider.disableUserInfo.tooltip=Disable usage of User Info service to obtain additional user information? Default is to use this OIDC service. +userIp=Use userIp Param +identity-provider.google-userIp.tooltip=Set 'userIp' query parameter when invoking on Google's User Info service. This will use the user's ip address. Useful if Google is throttling access to the User Info service. +offlineAccess=Request refresh token +identity-provider.google-offlineAccess.tooltip=Set 'access_type' query parameter to 'offline' when redirecting to google authorization endpoint, to get a refresh token back. Useful if planning to use Token Exchange to retrieve Google token to access Google APIs when the user is not at the browser. +hostedDomain=Hosted Domain +identity-provider.google-hostedDomain.tooltip=Set 'hd' query parameter when logging in with Google. Google will list accounts only for this domain. Keycloak validates that the returned identity token has a claim for this domain. When '*' is entered, any hosted account can be used. +identity-provider.facebook-fetchedFields.label=Additional user's profile fields +identity-provider.facebook-fetchedFields.tooltip=Provide additional fields which would be fetched using the profile request. This will be appended to the default set of 'id,name,email,first_name,last_name'. +sandbox=Target Sandbox +identity-provider.paypal-sandbox.tooltip=Target PayPal's sandbox environment +update-profile-on-first-login=Update Profile on First Login +on=On +on-missing-info=On missing info +off=Off +update-profile-on-first-login.tooltip=Define conditions under which a user has to update their profile during first-time login. +trust-email=Trust Email +trust-email.tooltip=If enabled, email provided by this provider is not verified even if verification is enabled for the realm. +link-only=Account Linking Only +link-only.tooltip=If true, users cannot log in through this provider. They can only link to this provider. This is useful if you don't want to allow login from the provider, but want to integrate with a provider +hide-on-login-page=Hide on Login Page +hide-on-login-page.tooltip=If hidden, login with this provider is possible only if requested explicitly, for example using the 'kc_idp_hint' parameter. +gui-order.tooltip=Number defining order of the provider in GUI (for example, on Login page). +first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that no Keycloak account is currently linked to the authenticated identity provider account. +post-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after each login with this identity provider. Useful if you want additional verification of each user authenticated with this identity provider (for example OTP). Leave this empty if you need no any additional authenticators to be triggered after login with this identity provider. Also note that authenticator implementations must assume that user is already set in ClientSession as identity provider already set it. +openid-connect-config=OpenID Connect Config +openid-connect-config.tooltip=OIDC SP and external IDP configuration. +authorization-url=Authorization URL +authorization-url.tooltip=The Authorization Url. +token-url=Token URL +token-url.tooltip=The Token URL. +loginHint=Pass login_hint +loginHint.tooltip=Pass login_hint to identity provider. +uiLocales=Pass current locale +uiLocales.tooltip=Pass the current locale to the identity provider as a ui_locales parameter. +logout-url=Logout URL +identity-provider.logout-url.tooltip=End session endpoint to use to logout user from external IDP. +backchannel-logout=Backchannel Logout +backchannel-logout.tooltip=Does the external IDP support backchannel logout? +user-info-url=User Info URL +user-info-url.tooltip=The User Info Url. This is optional. +client-auth=Client Authentication +client-auth.tooltip=The client authentication method (cfr. https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication). In case of JWT signed with private key, the realm private key is used. +client-auth.client_secret_post=Client secret sent as post +client-auth.client_secret_basic=Client secret sent as basic auth +client-auth.client_secret_jwt=Client secret as jwt +client-auth.private_key_jwt=JWT signed with private key +identity-provider.client-id.tooltip=The client or client identifier registered within the identity provider. +client-secret=Client Secret +client-assertion-signing-algorithm=Client Assertion Signature Algorithm +client-assertion-signing-algorithm.tooltip=Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or Client secret as jwt, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of Client secret as jwt. +show-secret=Show secret +hide-secret=Hide secret +client-secret.tooltip=The client or client secret registered within the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format. +issuer=Issuer +issuer.tooltip=The issuer identifier for the issuer of the response. If not provided, no validation will be performed. +default-scopes=Default Scopes +identity-provider.default-scopes.tooltip=The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'. +prompt=Prompt +unspecified.option=unspecified +none.option=none +consent.option=consent +login.option=login +select-account.option=select_account +prompt.tooltip=Specifies whether the Authorization Server prompts the End-User for reauthentication and consent. +accepts-prompt-none-forward-from-client=Accepts prompt=none forward from client +accepts-prompt-none-forward-from-client.tooltip=This is just used together with Identity Provider Authenticator or when kc_idp_hint points to this identity provider. In case that client sends a request with prompt=none and user is not yet authenticated, the error will not be directly returned to client, but the request with prompt=none will be forwarded to this identity provider. +validate-signatures=Validate Signatures +identity-provider.validate-signatures.tooltip=Enable/disable signature validation of external IDP signatures. +identity-provider.use-jwks-url.tooltip=If the switch is on, identity provider public keys will be downloaded from given JWKS URL. This allows great flexibility because new keys will be always re-downloaded again when identity provider generates new keypair. If the switch is off, public key (or certificate) from the Keycloak DB is used, so when the identity provider keypair changes, you always need to import the new key to the Keycloak DB as well. +identity-provider.jwks-url.tooltip=URL where identity provider keys in JWK format are stored. See JWK specification for more details. If you use external Keycloak identity provider, you can use URL like 'http://broker-keycloak:8180/auth/realms/test/protocol/openid-connect/certs' assuming your brokered Keycloak is running on 'http://broker-keycloak:8180' and its realm is 'test' . +validating-public-key=Validating Public Key +identity-provider.validating-public-key.tooltip=The public key in PEM format that must be used to verify external IDP signatures. +validating-public-key-id=Validating Public Key Id +identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave blank if the key above should be used always, regardless of key ID specified by external IDP; set it if the key should only be used for verifying if the key ID from external IDP matches. +allowed-clock-skew=Allowed clock skew +identity-provider.allowed-clock-skew.tooltip=Clock skew in seconds that is tolerated when validating identity provider tokens. Default value is zero. +forwarded-query-parameters=Forwarded Query Parameters +identity-provider.forwarded-query-parameters.tooltip=Non OpenID Connect/OAuth standard query parameters to be forwarded to external IDP from the initial application request to Authorization Endpoint. Multiple parameters can be entered, separated by comma (,). +import-external-idp-config=Import External IDP Config +import-external-idp-config.tooltip=Allows you to load external IDP metadata from a config file or to download it from a URL. +import-from-url=Import from URL +identity-provider.import-from-url.tooltip=Import metadata from a remote IDP discovery descriptor. +import-from-file=Import from file +identity-provider.import-from-file.tooltip=Import metadata from a downloaded IDP discovery descriptor. +identity-provider.saml.entity-id=Service Provider Entity ID +identity-provider.saml.entity-id.tooltip=The Entity ID that will be used to uniquely identify this SAML Service Provider +identity-provider.saml.protocol-endpoints.saml=SAML 2.0 Service Provider Metadata +identity-provider.saml.protocol-endpoints.saml.tooltip=Shows the configuration of the Service Provider endpoint +saml-config=SAML Config +identity-provider.saml-config.tooltip=SAML SP and external IDP configuration. +single-signon-service-url=Single Sign-On Service URL +saml.single-signon-service-url.tooltip=The Url that must be used to send authentication requests (SAML AuthnRequest). +single-logout-service-url=Single Logout Service URL +saml.single-logout-service-url.tooltip=The Url that must be used to send logout requests. +nameid-policy-format=NameID Policy Format +nameid-policy-format.tooltip=Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. +saml.principal-type=Principal Type +saml.principal-type.tooltip=Way to identify and track external users from the assertion. Default is using Subject NameID, alternatively you can set up identifying attribute. +saml.principal-attribute=Principal Attribute +saml.principal-attribute.tooltip=Name or Friendly Name of the attribute used to identify external users. +saml.allow-create=Allow create +saml.allow-create.tooltip=Allow the external identity provider to create a new identifier to represent the principal +http-post-binding-response=HTTP-POST Binding Response +http-post-binding-response.tooltip=Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. +http-post-binding-for-authn-request=HTTP-POST Binding for AuthnRequest +http-post-binding-for-authn-request.tooltip=Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. +http-post-binding-logout=HTTP-POST Binding Logout +http-post-binding-logout.tooltip=Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. +want-authn-requests-signed=Want AuthnRequests Signed +want-authn-requests-signed.tooltip=Indicates whether the identity provider expects a signed AuthnRequest. +want-assertions-signed=Want Assertions Signed +want-assertions-signed.tooltip=Indicates whether this service provider expects a signed Assertion. +want-assertions-encrypted=Want Assertions Encrypted +want-assertions-encrypted.tooltip=Indicates whether this service provider expects an encrypted Assertion. +force-authentication=Force Authentication +identity-provider.force-authentication.tooltip=Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context. +validate-signature=Validate Signature +saml.validate-signature.tooltip=Enable/disable signature validation of SAML responses. +validating-x509-certificate=Validating X509 Certificates +validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures. Multiple certificates can be entered, separated by comma (,). +saml.loginHint=Pass subject +saml.loginHint.tooltip=During login phase, forward an optional login_hint query parameter to SAML AuthnRequest's Subject. +saml.import-from-url.tooltip=Import metadata from a remote IDP SAML entity descriptor. +identity-provider.saml.sign-sp-metadata=Sign Service Provider Metadata +identity-provider.saml.sign-sp-metadata.tooltip=Enable/disable signature of the provider SAML metadata +identity-provider.saml.requested-authncontext=Requested AuthnContext Constraints +identity-provider.saml.requested-authncontext.tooltip=Allows the SP to specify the authentication context requirements of authentication statements returned. +identity-provider.saml.authncontext-comparison-type=Comparison +identity-provider.saml.authncontext-comparison-type.tooltip=Specifies the comparison method used to evaluate the requested context classes or statements. The default is "Exact". +identity-provider.saml.authncontext-comparison-type.exact=Exact +identity-provider.saml.authncontext-comparison-type.minimum=Minimum +identity-provider.saml.authncontext-comparison-type.maximum=Maximum +identity-provider.saml.authncontext-comparison-type.better=Better +identity-provider.saml.authncontext-class-ref=AuthnContext ClassRefs +identity-provider.saml.authncontext-class-ref.tooltip=Ordered list of requested AuthnContext ClassRefs. +identity-provider.saml.authncontext-decl-ref=AuthnContext DeclRefs +identity-provider.saml.authncontext-decl-ref.tooltip=Ordered list of requested AuthnContext DeclRefs. +social.client-id.tooltip=The client identifier registered with the identity provider. +social.client-secret.tooltip=The client secret registered with the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format. +social.default-scopes.tooltip=The scopes to be sent when asking for authorization. See the documentation for possible values, separator and default value'. +key=Key +stackoverflow.key.tooltip=The Key obtained from Stack Overflow client registration. +openshift.base-url=Base Url +openshift.base-url.tooltip=Base Url to OpenShift Online API +openshift4.base-url=Base Url +openshift4.base-url.tooltip=Base Url to OpenShift Online API +gitlab-application-id=Application Id +gitlab-application-secret=Application Secret +gitlab.application-id.tooltip=Application Id for the application you created in your GitLab Applications account menu +gitlab.application-secret.tooltip=Secret for the application that you created in your GitLab Applications account menu +gitlab.default-scopes.tooltip=Scopes to ask for on login. Will always ask for openid. Additionally adds read_user if you do not specify anything. +bitbucket-consumer-key=Consumer Key +bitbucket-consumer-secret=Consumer Secret +bitbucket.key.tooltip=Bitbucket OAuth Consumer Key +bitbucket.secret.tooltip=Bitbucket OAuth Consumer Secret +bitbucket.default-scopes.tooltip=Scopes to ask for on login. If you do not specify anything, scope defaults to 'email'. +# User federation +sync-ldap-roles-to-keycloak=Sync LDAP Roles To Keycloak +sync-keycloak-roles-to-ldap=Sync Keycloak Roles To LDAP +sync-ldap-groups-to-keycloak=Sync LDAP Groups To Keycloak +sync-keycloak-groups-to-ldap=Sync Keycloak Groups To LDAP +realms=Realms +realm=Realm +identity-provider-mappers=Identity Provider Mappers +create-identity-provider-mapper=Create Identity Provider Mapper +add-identity-provider-mapper=Add Identity Provider Mapper +client.description.tooltip=Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description} +expires=Expires +expiration=Expiration +expiration.tooltip=Specifies how long the token should be valid +count=Count +count.tooltip=Specifies how many clients can be created using the token +remainingCount=Remaining Count +created=Created +back=Back +initial-access-tokens=Initial Access Tokens +add-initial-access-tokens=Add Initial Access Token +initial-access-token=Initial Access Token +initial-access.copyPaste.tooltip=Copy/paste the initial access token before navigating away from this page as it is not possible to retrieve later +continue=Continue +initial-access-token.confirm.title=Copy Initial Access Token +initial-access-token.confirm.text=Please copy and paste the initial access token before confirming as it cannot be retrieved later +no-initial-access-available=No Initial Access Tokens available +client-reg-policies=Client Registration Policies +client-reg-policy.name.tooltip=Display Name of the policy +anonymous-policies=Anonymous Access Policies +anonymous-policies.tooltip=Those Policies are used when the Client Registration Service is invoked by unauthenticated request. This means that the request does not contain Initial Access Token nor Bearer Token. +auth-policies=Authenticated Access Policies +auth-policies.tooltip=Those Policies are used when Client Registration Service is invoked by authenticated request. This means that the request contains Initial Access Token or Bearer Token. +policy-name=Policy Name +no-client-reg-policies-configured=No Client Registration Policies +trusted-hosts.label=Trusted Hosts +trusted-hosts.tooltip=List of Hosts, which are trusted and are allowed to invoke Client Registration Service and/or be used as values of Client URIs. You can use hostnames or IP addresses. If you use star at the beginning (for example '*.example.com' ) then whole domain example.com will be trusted. +host-sending-registration-request-must-match.label=Host Sending Client Registration Request Must Match +host-sending-registration-request-must-match.tooltip=If on, any request to Client Registration Service is allowed just if it was sent from some trusted host or domain. +client-uris-must-match.label=Client URIs Must Match +client-uris-must-match.tooltip=If on, all Client URIs (Redirect URIs and others) are allowed just if they match some trusted host or domain. +consent-required-for-all-mappers.label=Consent Required For Mappers +consent-required-for-all-mappers.tooltip=If on, all newly registered protocol mappers will automatically have consentRequired switch on. This means that user will need to approve consent screen. NOTE: Consent screen is shown just if client has consentRequired switch on. So it is usually good to use this switch together with consent-required policy. +allowed-client-scopes.label=Allowed Client Scopes +allowed-client-scopes.tooltip=Whitelist of the client scopes, which can be used on a newly registered client. Attempt to register client with some client scope, which is not whitelisted, will be rejected. By default, the whitelist is either empty or contains just realm default client scopes (based on 'Allow Default Scopes' configuration property) +allow-default-scopes.label=Allow Default Scopes +allow-default-scopes.tooltip=If on, newly registered clients will be allowed to have client scopes mentioned in realm default client scopes or realm optional client scopes + +# Client Registration Policies providers +allowed-protocol-mappers.label=Allowed Protocol Mappers +allowed-protocol-mappers.tooltip=Whitelist of allowed protocol mapper providers. If there is an attempt to register client, which contains some protocol mappers, which were not whitelisted, registration request will be rejected. + +allowed-client-templates.label=Allowed Client Templates +client-disabled.label=Client Disabled +scope.label=Scope +consent-required.label=Consent Required + +max-clients.label=Max Clients Per Realm +max-clients.tooltip=It will not be allowed to register a new client if count of existing clients in realm is same or bigger than the configured limit. + +client-scopes=Client Scopes +client-scopes.tooltip=Client scopes allow you to define a common set of protocol mappers and roles, which are shared between multiple clients + + +groups=Groups + +group.add-selected.tooltip=Realm roles that can be assigned to the group. +group.assigned-roles.tooltip=Realm roles mapped to the group +group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role. +group.available-roles.tooltip=Assignable roles from this client. +group.assigned-roles-client.tooltip=Role mappings for this client. +group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role. + +group.move.success=Group moved. +group.remove.confirm.title=Delete Group +group.remove.confirm.message=Are you sure you want to permanently delete the group {{name}}? +group.remove.success=The group has been deleted. +group.fetch.fail=Unable to fetch {{params}} +group.create.success=Group Created. +group.edit.success=Your changes have been saved to the group. +group.roles.add.success=Role mappings updated. +group.roles.remove.success=Role mappings updated. +group.default.add.error=Please select a group to add +group.default.add.success=Added default group +group.default.remove.success=Removed default group + +default-roles=Default Roles +no-realm-roles-available=No realm roles available + +users=Users +user.add-selected.tooltip=Realm roles that can be assigned to the user. +user.assigned-roles.tooltip=Realm roles mapped to the user +user.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role. +user.available-roles.tooltip=Assignable roles from this client. +user.assigned-roles-client.tooltip=Role mappings for this client. +user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role. + +user.roles.add.success=Role mappings updated. +user.roles.remove.success=Role mappings updated. +user.logout.all.success=Logged out user in all clients +user.logout.session.success=Logged out session +user.fedid.link.remove.confirm.title=Delete Identity Provider Link +user.fedid.link.remove.confirm.message=Are you sure you want to permanently delete the Identity Provider Link {{name}}? +user.fedid.link.remove.success=The provider link has been deleted. +user.fedid.link.add.success=Provider link has been created. +user.consent.revoke.success=Grant revoked successfully +user.consent.revoke.error=Grant couldn't be revoked +user.remove.confirm.title=Delete User +user.remove.confirm.message=Are you sure you want to permanently delete the user {{name}}? +user.unlock.success=Any temporarily locked users are now unlocked. +user.remove.success=The user has been deleted. +user.remove.error=User couldn't be deleted +user.create.success=The user has been created. +user.edit.success=Your changes have been saved to the user. +user.credential.update.success=Credentials saved! +user.credential.update.error=Error while updating the credential. See console for more information. +user.credential.remove.confirm.title=Delete credentials +user.credential.remove.confirm.message=Are you sure you want to delete these users credentials? +user.credential.remove.success=Credentials deleted! +user.credential.remove.error=Error while deleting the credential. See console for more information. +user.credential.move-top.error=Error while moving the credential to top. See console for more information. +user.credential.move-up.error=Error while moving the credential up. See console for more information. +user.credential.move-down.error=Error while moving the credential down. See console for more information. +user.credential.fetch.error=Error while loading user credentials. See console for more information. +user.credential.storage.fetch.error=Error while loading user storage credentials. See console for more information. +user.password.error.not-matching=Password and confirmation does not match. +user.password.reset.confirm.title=Reset password +user.password.reset.confirm.message=Are you sure you want to reset the password for the user? +user.password.reset.success=The password has been reset. +user.password.set.confirm.title=Set password +user.password.set.confirm.message=Are you sure you want to set a password for the user? +user.password.set.success=The password has been set. +user.credential.disable.confirm.title=Disable credentials +user.credential.disable.confirm.message=Are you sure you want to disable these users credentials? +user.credential.disable.confirm.success=Credentials disabled +user.credential.disable.confirm.error=Failed to disable credentials +user.actions-email.send.pending-changes.title=Cannot send email +user.actions-email.send.pending-changes.message=You must save your current changes before you can send an email +user.actions-email.send.confirm.title=Send Email +user.actions-email.send.confirm.message=Are you sure you want to send email to user? +user.actions-email.send.confirm.success=Email sent to user +user.actions-email.send.confirm.error=Failed to send email to user +user.storage.remove.confirm.title=Delete User storage provider +user.storage.remove.confirm.message=Are you sure you want to permanently delete the user storage provider {{name}}? +user.storage.remove.success=The provider has been deleted. +user.storage.create.success=The provider has been created. +user.storage.edit.success=The provider has been updated. +user.storage.sync.success=Sync of users finished successfully. {{status}} +user.storage.sync.error=Error during sync of users +user.storage.remove-users.success=Remove imported users finished successfully. +user.storage.remove-users.error=Error during remove +user.storage.unlink.success=Unlink of users finished successfully. +user.storage.unlink.error=Error during unlink +user.groups.fetch.all.error=Unable to fetch all group memberships {{params}} +user.groups.fetch.error=Unable to fetch {{params}} +user.groups.join.error.no-group-selected=Please select a group to add +user.groups.join.error.already-added=Group already added +user.groups.join.success=Added group membership +user.groups.leave.error.no-group-selected=Please select a group to remove +user.groups.leave.success=Removed group membership + +default.available-roles.tooltip=Realm level roles that can be assigned. +realm-default-roles=Realm Default Roles +realm-default-roles.tooltip=Realm level roles assigned to new users. +default.available-roles-client.tooltip=Roles from this client that are assignable as a default. +client-default-roles=Client Default Roles +client-default-roles.tooltip=Roles from this client assigned as a default role. +composite.available-roles.tooltip=Realm level roles that you can associate to this composite role. +composite.associated-roles.tooltip=Realm level roles associated with this composite role. +composite.available-roles-client.tooltip=Roles from this client that you can associate to this composite role. +composite.associated-roles-client.tooltip=Client roles associated with this composite role. +partial-import=Partial Import +partial-import.tooltip=Partial import allows you to import users, clients, and other resources from a previously exported json file. + +file=File +exported-json-file=Exported json file +import-from-realm=Import from realm +import-users=Import users +import-groups=Import groups +import-clients=Import clients +import-identity-providers=Import identity providers +import-realm-roles=Import realm roles +import-client-roles=Import client roles +if-resource-exists=If a resource exists +fail=Fail +skip=Skip +overwrite=Overwrite +if-resource-exists.tooltip=Specify what should be done if you try to import a resource that already exists. + +partial-export=Partial Export +partial-export.tooltip=Partial export allows you to export realm configuration, and other associated resources into a json file. +export-groups-and-roles=Export groups and roles +export-clients=Export clients + +action=Action +role-selector=Role Selector +realm-roles.tooltip=Realm roles that can be selected. + +select-a-role=Select a role +select-realm-role=Select realm role +client-roles.tooltip=Client roles that can be selected. +select-client-role=Select client role + +client-saml-endpoint=Client SAML Endpoint +add-client-scope=Add client scope + +default-client-scopes=Default Client Scopes +default-client-scopes.tooltip=Client Scopes, which will be added automatically to each created client +default-client-scopes.default=Default Client Scopes +default-client-scopes.default.tooltip=Allow to define client scopes, which will be added as default scopes to each created client +default-client-scopes.default.available=Available Client Scopes +default-client-scopes.default.available.tooltip=Client scopes, which are not yet assigned as realm default scopes or realm optional scopes +default-client-scopes.default.assigned=Assigned Default Client Scopes +default-client-scopes.default.assigned.tooltip=Client scopes, which will be added as default scopes to each created client +default-client-scopes.optional=Optional Client Scopes +default-client-scopes.optional.tooltip=Allow to define client scopes, which will be added as optional scopes to each created client +default-client-scopes.optional.available=Available Client Scopes +default-client-scopes.optional.available.tooltip=Client scopes, which are not yet assigned as realm default scopes or realm optional scopes +default-client-scopes.optional.assigned=Assigned Optional Client Scopes +default-client-scopes.optional.assigned.tooltip=Client scopes, which will be added as optional scopes to each created client + +client-scopes.setup=Setup +client-scopes.setup.tooltip=Allow to setup client scopes linked to this client +client-scopes.default=Default Client Scopes +client-scopes.default.tooltip=Default client scopes are always applied when issuing tokens for this client. Protocol mappers and role scope mappings are always applied regardless of value of used scope parameter in OIDC Authorization request +client-scopes.default.available=Available Client Scopes +client-scopes.default.available.tooltip=Client scopes, which are not yet assigned as default scopes or optional scopes +client-scopes.default.assigned=Assigned Default Client Scopes +client-scopes.default.assigned.tooltip=Client scopes, which will be used as default scopes when generating tokens for this client +client-scopes.optional=Optional Client Scopes +client-scopes.optional.tooltip=Optional client scopes are applied when issuing tokens for this client, however just in case when they are requested by scope parameter in OIDC Authorization request +client-scopes.optional.available=Available Client Scopes +client-scopes.optional.available.tooltip=Client scopes, which are not yet assigned as default scopes or optional scopes +client-scopes.optional.assigned=Assigned Optional Client Scopes +client-scopes.optional.assigned.tooltip=Client scopes, which may be used as optional scopes when generating tokens for this client + +client-scopes.evaluate=Evaluate +client-scopes.evaluate.tooltip=Allow to see all protocol mappers and role scope mapping that will be used in the tokens issued to this client. Also allow to generate example access token based on provided scope parameter +scope-parameter=Scope Parameter +scope-parameter.tooltip=You can copy/paste this value of scope parameter and use it in initial OpenID Connect Authentication Request sent from this client adapter. Default client scopes and selected optional client scopes will be used when generating token issued for this client +client-scopes.evaluate.scopes=Client Scopes +client-scopes.evaluate.scopes.tooltip=Allow to select optional client scopes, which may be used when generating token issued for this client +client-scopes.evaluate.scopes.available=Available Optional Client Scopes +client-scopes.evaluate.scopes.available.tooltip=This contains Optional Client Scopes, which can be optionally used when issuing access token for this client +client-scopes.evaluate.scopes.assigned=Selected Optional Client Scopes +client-scopes.evaluate.scopes.assigned.tooltip=Selected Optional Client Scopes, which will be used when issuing access token for this client. You can see above what value of OAuth Scope Parameter needs to be used when you want to have these optional client scopes applied when the initial OpenID Connect Authentication request will be sent from your client adapter +client-scopes.evaluate.scopes.effective=Effective Client Scopes +client-scopes.evaluate.scopes.effective.tooltip=Contains all default client scopes and selected optional scopes. All protocol mappers and role scope mappings of all those client scopes will be used when generating access token issued for your client +client-scopes.evaluate.user.tooltip=Optionally select user, for whom the example access token will be generated. If you do not select a user, example access token will not be generated during evaluation +send-evaluation-request=Evaluate +send-evaluation-request.tooltip=Click this to see all protocol mappers and role scope mappings that will be used when issuing an access token for this client. It will also optionally generate example access token in case that some user was selected + +evaluated-protocol-mappers=Effective Protocol Mappers +evaluated-protocol-mappers.tooltip=Shows all effective protocol mappers that will be used when issuing token for this client. Also contains protocol mappers of selected optional client scopes. For each protocol mapper, you can see from which client scope it is inherited from +evaluated-roles=Effective Role Scope Mappings +evaluated-roles.tooltip=Shows all effective roles scope mappings that will be used when issuing token for this client. Also contains role scope mappings of selected optional client scopes +parent-client-scope=Parent Client Scope +client-scopes.evaluate.not-granted-roles=Not Granted Roles +client-scopes.evaluate.not-granted-roles.tooltip=Client does not have scope mappings for these roles. Those roles will not be in the access token issued to this client even if the authenticated user is a member of them +client-scopes.evaluate.granted-realm-effective-roles=Granted Effective Realm Roles +client-scopes.evaluate.granted-realm-effective-roles.tooltip=Client has scope mappings for these roles. Those roles will be in the access token issued to this client if the authenticated user is a member of them +client-scopes.evaluate.granted-client-effective-roles=Granted Effective Client Roles +generated-access-token=Generated Access Token +generated-access-token.tooltip=See the example access token, which will be generated and sent to the client when selected user is authenticated. You can see claims and roles that the token will contain based on the effective protocol mappers and role scope mappings and also based on the claims/roles assigned to user himself +generated-id-token=Generated ID Token +generated-id-token.tooltip=See the example ID Token, which will be generated and sent to the client when selected user is authenticated. You can see claims and roles that the token will contain based on the effective protocol mappers and role scope mappings and also based on the claims/roles assigned to user himself +generated-user-info=Generated User Info +generated-user-info.tooltip=See the example User Info, which will be provided by the User Info Endpoint + +manage=Manage +authentication=Authentication +user-federation=User Federation +user-storage=User Storage +events=Events +realm-settings=Realm Settings +configure=Configure +select-realm=Select realm +add=Add + +client-storage=Client Storage +no-client-storage-providers-configured=No client storage providers configured +client-stores.tooltip=Keycloak can retrieve clients and their details from external stores. + +client-scope.name.tooltip=Name of the client scope. Must be unique in the realm. Name should not contain space characters as it is used as value of scope parameter +client-scope.description.tooltip=Description of the client scope +client-scope.protocol.tooltip=Which SSO protocol configuration is being supplied by this client scope +client-scope.display-on-consent-screen=Display On Consent Screen +client-scope.display-on-consent-screen.tooltip=If on, and this client scope is added to some client with consent required, the text specified by 'Consent Screen Text' will be displayed on consent screen. If off, this client scope will not be displayed on the consent screen +client-scope.consent-screen-text=Consent Screen Text +client-scope.consent-screen-text.tooltip=Text that will be shown on the consent screen when this client scope is added to some client with consent required. Defaults to name of client scope if it is not filled +client-scope.gui-order=GUI order +client-scope.gui-order.tooltip=Specify order of the provider in GUI (such as in Consent page) as integer +client-scope.include-in-token-scope=Include In Token Scope +client-scope.include-in-token-scope.tooltip=If on, the name of this client scope will be added to the access token property 'scope' as well as to the Token Introspection Endpoint response. If off, this client scope will be omitted from the token and from the Token Introspection Endpoint response. + +add-user-federation-provider=Add user federation provider +add-user-storage-provider=Add user storage provider +required-settings=Required Settings +provider-id=Provider ID +console-display-name=Console Display Name +console-display-name.tooltip=Display name of provider when linked in admin console. +priority=Priority +priority.tooltip=Priority of provider when doing a user lookup. Lowest first. +user-storage.enabled.tooltip=If provider is disabled, it will not be considered for queries and imported users will be disabled and read-only until the provider is enabled again. +sync-settings=Sync Settings +periodic-full-sync=Periodic Full Sync +periodic-full-sync.tooltip=Does periodic full synchronization of provider users to Keycloak should be enabled or not +full-sync-period=Full Sync Period +full-sync-period.tooltip=Period for full synchronization in seconds +periodic-changed-users-sync=Periodic Changed Users Sync +periodic-changed-users-sync.tooltip=Does periodic synchronization of changed or newly created provider users to Keycloak should be enabled or not +changed-users-sync-period=Changed Users Sync Period +changed-users-sync-period.tooltip=Period for synchronization of changed or newly created provider users in seconds +synchronize-changed-users=Synchronize changed users +synchronize-all-users=Synchronize all users +remove-imported-users=Remove imported +unlink-users=Unlink users +kerberos-realm=Kerberos Realm +kerberos-realm.tooltip=Name of kerberos realm. For example FOO.ORG +server-principal=Server Principal +server-principal.tooltip=Full name of server principal for HTTP service including server and domain name. For example HTTP/host.foo.org@FOO.ORG +keytab=KeyTab +keytab.tooltip=Location of Kerberos KeyTab file containing the credentials of server principal. For example /etc/krb5.keytab +debug=Debug +debug.tooltip=Enable/disable debug logging to standard output for Krb5LoginModule. +allow-password-authentication=Allow Password Authentication +allow-password-authentication.tooltip=Enable/disable possibility of username/password authentication against Kerberos database +edit-mode=Edit Mode +edit-mode.tooltip=READ_ONLY means that password updates are not allowed and user always authenticates with Kerberos password. UNSYNCED means that the user can change the password in the Keycloak database and this one will be used instead of the Kerberos password +ldap.edit-mode.tooltip=READ_ONLY is a read-only LDAP store. WRITABLE means data will be synced back to LDAP on demand. UNSYNCED means user data will be imported, but not synced back to LDAP. +update-profile-first-login=Update Profile First Login +update-profile-first-login.tooltip=Update profile on first login +sync-registrations=Sync Registrations +ldap.sync-registrations.tooltip=Should newly created users be created within LDAP store? Priority effects which provider is chosen to sync the new user. +import-enabled=Import Users +ldap.import-enabled.tooltip=If true, LDAP users will be imported into Keycloak DB and synced by the configured sync policies. +vendor=Vendor +ldap.vendor.tooltip=LDAP vendor (provider) +enable-usePasswordModifyExtendedOp=Enable the LDAPv3 Password Modify Extended Operation +ldap.usePasswordModifyExtendedOp.tooltip=Use the LDAPv3 Password Modify Extended Operation (RFC-3062). The password modify extended operation usually requires that LDAP user already has password in the LDAP server. So when this is used with 'Sync Registrations', it can be good to add also 'Hardcoded LDAP attribute mapper' with randomly generated initial password. +username-ldap-attribute=Username LDAP attribute +ldap-attribute-name-for-username=LDAP attribute name for username +username-ldap-attribute.tooltip=Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server vendors it can be 'uid'. For Active directory it can be 'sAMAccountName' or 'cn'. The attribute should be filled for all LDAP user records you want to import from LDAP to Keycloak. +rdn-ldap-attribute=RDN LDAP attribute +ldap-attribute-name-for-user-rdn=LDAP attribute name for user RDN +rdn-ldap-attribute.tooltip=Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN. Usually it's the same as Username LDAP attribute, however it is not required. For example for Active directory, it is common to use 'cn' as RDN attribute when username attribute might be 'sAMAccountName'. +uuid-ldap-attribute=UUID LDAP attribute +ldap-attribute-name-for-uuid=LDAP attribute name for UUID +uuid-ldap-attribute.tooltip=Name of LDAP attribute, which is used as unique object identifier (UUID) for objects in LDAP. For many LDAP server vendors, it is 'entryUUID'; however some are different. For example for Active directory it should be 'objectGUID'. If your LDAP server does not support the notion of UUID, you can use any other attribute that is supposed to be unique among LDAP users in tree. For example 'uid' or 'entryDN'. +user-object-classes=User Object Classes +ldap-user-object-classes.placeholder=LDAP User Object Classes (div. by comma) +ldap-connection-url=LDAP connection URL +ldap-users-dn=LDAP Users DN +ldap-bind-dn=LDAP Bind DN +ldap-bind-credentials=LDAP Bind Credentials +ldap-filter=LDAP Filter +ldap.user-object-classes.tooltip=All values of LDAP objectClass attribute for users in LDAP divided by comma. For example: 'inetOrgPerson, organizationalPerson' . Newly created Keycloak users will be written to LDAP with all those object classes and existing LDAP user records are found just if they contain all those object classes. +connection-url=Connection URL +ldap.connection-url.tooltip=Connection URL to your LDAP server +test-connection=Test connection +users-dn=Users DN +ldap.users-dn.tooltip=Full DN of LDAP tree where your users are. This DN is the parent of LDAP users. It could be for example 'ou=users,dc=example,dc=com' assuming that your typical user will have DN like 'uid=john,ou=users,dc=example,dc=com' +authentication-type=Bind Type +ldap.authentication-type.tooltip=Type of the Authentication method used during LDAP Bind operation. It is used in most of the requests sent to the LDAP server. Currently only 'none' (anonymous LDAP authentication) or 'simple' (Bind credential + Bind password authentication) mechanisms are available +bind-dn=Bind DN +ldap.bind-dn.tooltip=DN of LDAP admin, which will be used by Keycloak to access LDAP server +bind-credential=Bind Credential +ldap.bind-credential.tooltip=Password of LDAP admin. This field is able to obtain its value from vault, use ${vault.ID} format. +test-authentication=Test authentication +custom-user-ldap-filter=Custom User LDAP Filter +ldap.custom-user-ldap-filter.tooltip=Additional LDAP Filter for filtering searched users. Leave this empty if you don't need additional filter. Make sure that it starts with '(' and ends with ')' +search-scope=Search Scope +ldap.search-scope.tooltip=For one level, the search applies only for users in the DNs specified by User DNs. For subtree, the search applies to the whole subtree. See LDAP documentation for more details +use-truststore-spi=Use Truststore SPI +ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in standalone.xml/domain.xml. 'Always' means that it will always use it. 'Never' means that it will not use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if standalone.xml/domain.xml is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used. +validate-password-policy=Validate Password Policy +connection-pooling=Connection Pooling +connection-pooling-settings=Connection Pooling Settings +connection-pooling-authentication=Connection Pooling Authentication +connection-pooling-authentication-default=none simple +connection-pooling-debug=Connection Pool Debug Level +connection-pooling-debug-default=off +connection-pooling-initsize=Connection Pool Initial Size +connection-pooling-initsize-default=1 +connection-pooling-maxsize=Connection Pool Maximum Size +connection-pooling-maxsize-default=1000 +connection-pooling-prefsize=Connection Pool Preferred Size +connection-pooling-prefsize-default=5 +connection-pooling-protocol=Connection Pool Protocol +connection-pooling-protocol-default=plain ssl +connection-pooling-timeout=Connection Pool Timeout +connection-pooling-timeout-default=300000 +ldap-connection-timeout=Connection Timeout +ldap.connection-timeout.tooltip=LDAP Connection Timeout in milliseconds +ldap-read-timeout=Read Timeout +ldap.read-timeout.tooltip=LDAP Read Timeout in milliseconds. This timeout applies for LDAP read operations +ldap.validate-password-policy.tooltip=Determines if Keycloak should validate the password with the realm password policy before updating it +ldap.connection-pooling.tooltip=Determines if Keycloak should use connection pooling for accessing LDAP server +ldap.connection-pooling.authentication.tooltip=A list of space-separated authentication types of connections that may be pooled. Valid types are "none", "simple", and "DIGEST-MD5". +ldap.connection-pooling.debug.tooltip=A string that indicates the level of debug output to produce. Valid values are "fine" (trace connection creation and removal) and "all" (all debugging information). +ldap.connection-pooling.initsize.tooltip=The string representation of an integer that represents the number of connections per connection identity to create when initially creating a connection for the identity. +ldap.connection-pooling.maxsize.tooltip=The string representation of an integer that represents the maximum number of connections per connection identity that can be maintained concurrently. +ldap.connection-pooling.prefsize.tooltip=The string representation of an integer that represents the preferred number of connections per connection identity that should be maintained concurrently. +ldap.connection-pooling.protocol.tooltip=A list of space-separated protocol types of connections that may be pooled. Valid types are "plain" and "ssl". +ldap.connection-pooling.timeout.tooltip=The string representation of an integer that represents the number of milliseconds that an idle connection may remain in the pool without being closed and removed from the pool. +ldap.pagination.tooltip=Does the LDAP server support pagination. +ldap.startTls.tooltip=Encrypts the connection to LDAP using STARTTLS, which will disable connection pooling. +kerberos-integration=Kerberos Integration +allow-kerberos-authentication=Allow Kerberos authentication +ldap.allow-kerberos-authentication.tooltip=Enable/disable HTTP authentication of users with SPNEGO/Kerberos tokens. The data about authenticated users will be provisioned from this LDAP server +use-kerberos-for-password-authentication=Use Kerberos For Password Authentication +ldap.use-kerberos-for-password-authentication.tooltip=Use Kerberos login module for authenticate username/password against Kerberos server instead of authenticating against LDAP server with Directory Service API +batch-size=Batch Size +ldap.batch-size.tooltip=Count of LDAP users to be imported from LDAP to Keycloak within a single transaction. +ldap.periodic-full-sync.tooltip=Does periodic full synchronization of LDAP users to Keycloak should be enabled or not +ldap.periodic-changed-users-sync.tooltip=Does periodic synchronization of changed or newly created LDAP users to Keycloak should be enabled or not +ldap.changed-users-sync-period.tooltip=Period for synchronization of changed or newly created LDAP users in seconds +user-federation-mappers=User Federation Mappers +create-user-federation-mapper=Create user federation mapper +add-user-federation-mapper=Add user federation mapper +provider-name=Provider Name +no-user-federation-providers-configured=No user federation providers configured +no-user-storage-providers-configured=No user storage providers configured +add-identity-provider=Add identity provider +add-identity-provider-link=Add identity provider link +identity-provider=Identity Provider +identity-provider-user-id=Identity Provider User ID +identity-provider-user-id.tooltip=Unique ID of the user on the Identity Provider side +identity-provider-username=Identity Provider Username +identity-provider-username.tooltip=Username on the Identity Provider side +pagination=Pagination +browser-flow=Browser Flow +browser-flow.tooltip=Select the flow you want to use for browser authentication. +registration-flow=Registration Flow +registration-flow.tooltip=Select the flow you want to use for registration. +direct-grant-flow=Direct Grant Flow +direct-grant-flow.tooltip=Select the flow you want to use for direct grant authentication. +reset-credentials=Reset Credentials +reset-credentials.tooltip=Select the flow you want to use when the user has forgotten their credentials. +client-authentication=Client Authentication +client-authentication.tooltip=Select the flow you want to use for authentication of clients. +docker-auth=Docker Authentication +docker-auth.tooltip=Select the flow you want to use for authentication against a docker client. +new=New +copy=Copy +add-execution=Add execution +add-flow=Add flow +auth-type=Auth Type +requirement=Requirement +config=Config +no-executions-available=No executions available +authentication-flows=Authentication Flows +create-authenticator-config=Create authenticator config +authenticator.alias.tooltip=Name of the configuration +otp-type=OTP Type +time-based=Time Based +counter-based=Counter Based +otp-type.tooltip=totp is Time-Based One Time Password. 'hotp' is a counter base one time password in which the server keeps a counter to hash against. +otp-hash-algorithm=OTP Hash Algorithm +otp-hash-algorithm.tooltip=What hashing algorithm should be used to generate the OTP. +number-of-digits=Number of Digits +otp.number-of-digits.tooltip=How many digits should the OTP have? +look-ahead-window=Look Ahead Window +otp.look-ahead-window.tooltip=How far ahead should the server look just in case the token generator and server are out of time sync or counter sync? +initial-counter=Initial Counter +otp.initial-counter.tooltip=What should the initial counter value be? +otp-token-period=OTP Token Period +otp-token-period.tooltip=How many seconds should an OTP token be valid? Defaults to 30 seconds. +otp-supported-applications=Supported Applications +otp-supported-applications.tooltip=Applications that are known to work with the current OTP policy +table-of-password-policies=Table of Password Policies +add-policy.placeholder=Add policy... +policy-type=Policy Type +policy-value=Policy Value +webauthn-policy=WebAuthn Policy +webauthn-policy.tooltip=Policy for WebAuthn authentication. This one will be used by 'WebAuthn Register' required action and 'WebAuthn Authenticator' authenticator. Typical usage is, when WebAuthn will be used for the two-factor authentication. +webauthn-policy-passwordless=WebAuthn Passwordless Policy +webauthn-policy-passwordless.tooltip=Policy for passwordless WebAuthn authentication. This one will be used by 'Webauthn Register Passwordless' required action and 'WebAuthn Passwordless Authenticator' authenticator. Typical usage is, when WebAuthn will be used as first-factor authentication. Having both 'WebAuthn Policy' and 'WebAuthn Passwordless Policy' allows to use WebAuthn as both first factor and second factor authenticator in the same realm. +webauthn-rp-entity-name=Relying Party Entity Name +webauthn-rp-entity-name.tooltip=Human-readable server name as WebAuthn Relying Party +webauthn-signature-algorithms=Signature Algorithms +webauthn-signature-algorithms.tooltip=What signature algorithms should be used for Authentication Assertion. +webauthn-rp-id=Relying Party ID +webauthn-rp-id.tooltip=This is ID as WebAuthn Relying Party. It must be origin's effective domain. +webauthn-attestation-conveyance-preference=Attestation Conveyance Preference +webauthn-attestation-conveyance-preference.tooltip=Communicates to an authenticator the preference of how to generate an attestation statement. +webauthn-authenticator-attachment=Authenticator Attachment +webauthn-authenticator-attachment.tooltip=Communicates to an authenticator an acceptable attachment pattern. +webauthn-require-resident-key=Require Resident Key +webauthn-require-resident-key.tooltip=It tells an authenticator create a public key credential as Resident Key or not. +webauthn-user-verification-requirement=User Verification Requirement +webauthn-user-verification-requirement.tooltip=Communicates to an authenticator to confirm actually verifying a user. +webauthn-create-timeout=Timeout +webauthn-create-timeout.tooltip=Timeout value for creating user's public key credential in seconds. if set to 0, this timeout option is not adapted. +webauthn-avoid-same-authenticator-register=Avoid Same Authenticator Registration +webauthn-avoid-same-authenticator-register.tooltip=avoid registering the authenticator that has already been registered. +webauthn-acceptable-aaguids=Acceptable AAGUIDs +webauthn-acceptable-aaguids.tooltip=The list of AAGUID of which an authenticator can be registered. +manage-webauthn-authenticator=Manage WebAuthn Authenticator +public-key-credential-id=Public Key Credential ID +public-key-credential-aaguid=Public Key Credential AAGUID +public-key-credential-label=Public Key Credential Label +ciba-policy=CIBA Policy +ciba-backchannel-tokendelivery-mode=Backchannel Token Delivery Mode +ciba-backchannel-tokendelivery-mode.tooltip=Specifies how the CD(Consumption Device) gets the authentication result and related tokens. +ciba-expires-in=Expires In +ciba-expires-in.tooltip=The expiration time of the "auth_req_id" in seconds since the authentication request was received. +ciba-interval=Interval +ciba-interval.tooltip=The minimum amount of time in seconds that the CD(Consumption Device) must wait between polling requests to the token endpoint. +ciba-auth-requested-user-hint=Authentication Requested User Hint +ciba-auth-requested-user-hint.tooltip=The way of identifying the end-user for whom authentication is being requested. +admin-events=Admin Events +admin-events.tooltip=Displays saved admin events for the realm. Events are related to admin account, for example a realm creation. To enable persisted events go to config. +login-events=Login Events +filter=Filter +update=Update +reset=Reset +operation-types=Operation Types +resource-types=Resource Types +select-operations.placeholder=Select operations... +select-resource-types.placeholder=Select resource types... +resource-path=Resource Path +resource-path.tooltip=Filter by resource path. Supports wildcard '*' (for example 'users/*'). +date-(from)=Date (From) +date-(to)=Date (To) +authentication-details=Authentication Details +ip-address=IP Address +time=Time +operation-type=Operation Type +resource-type=Resource Type +auth=Auth +representation=Representation +register=Register +required-action=Required Action +default-action=Default Action +auth.default-action.tooltip=If enabled, any new user will have this required action assigned to it. +no-required-actions-configured=No required actions configured +defaults-to-id=Defaults to id +flows=Flows +bindings=Bindings +client-flow-bindings=Authentication Flow Overrides +client-flow-bindings.tooltip=Override realm authentication flow bindings. +required-actions=Required Actions +password-policy=Password Policy +otp-policy=OTP Policy +user-groups=User Groups +default-groups=Default Groups +groups.default-groups.tooltip=Set of groups that new users will automatically join. +cut=Cut +paste=Paste +create-group=Create group +create-authenticator-execution=Create Authenticator Execution +edit-flow=Edit Flow +create-form-action-execution=Create Form Action Execution +create-top-level-form=Create Top Level Form +flow.alias.tooltip=Specifies display name for the flow. +top-level-flow-type=Top Level Flow Type +flow.generic=generic +flow.client=client +top-level-flow-type.tooltip=What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for users and everything else +create-execution-flow=Create Execution Flow +flow-type=Flow Type +flow.form.type=form +flow.generic.type=generic +flow-type.tooltip=What kind of form is it +form-provider=Form Provider +default-groups.tooltip=Newly created or registered users will automatically be added to these groups +select-a-type.placeholder=select a type +available-groups=Available Groups +available-groups.tooltip=Select a group you want to add as a default. +value=Value +table-of-group-members=Table of group members +table-of-role-members=Table of role members +last-name=Last Name +first-name=First Name +email=Email +toggle-navigation=Toggle navigation +manage-account=Manage account +sign-out=Sign Out +server-info=Server Info +resource-not-found=Resource not found... +resource-not-found.instruction=We could not find the resource you are looking for. Please make sure the URL you entered is correct. +go-to-the-home-page=Go to the home page » +page-not-found=Page not found... +page-not-found.instruction=We could not find the page you are looking for. Please make sure the URL you entered is correct. +events.tooltip=Displays saved events for the realm. Events are related to user accounts, for example a user login. To enable persisted events go to config. +select-event-types.placeholder=Select event types... +events-config.tooltip=Displays configuration options to enable persistence of user and admin events. +select-an-action.placeholder=Select an action... +event-listeners.tooltip=Configure what listeners receive events for the realm. +login.save-events.tooltip=If enabled, login events are saved to the database, which makes events available to the admin and account management consoles. +clear-events.tooltip=Deletes all events in the database. +events.expiration.tooltip=Sets the expiration for events. Expired events are periodically deleted from the database. +admin-events-settings=Admin Events Settings +save-events=Save Events +admin.save-events.tooltip=If enabled, admin events are saved to the database, which makes events available to the admin console. +saved-types.tooltip=Configure what event types are saved. +include-representation=Include Representation +include-representation.tooltip=Include JSON representation for create and update requests. +clear-admin-events.tooltip=Deletes all admin events in the database. +server-version=Server Version +server-profile=Server Profile +server-disabled=Disabled Features +server-disabled.tooltip=Features that are not currently enabled. Some features are not enabled by default. This applies to all preview and experimental features. +server-preview=Preview Features +server-preview.tooltip=Preview features are not supported in production use and may be significantly changed or removed in the future. +server-experimental=Experimental Features +server-experimental.tooltip=Experimental features, which may not be fully functional. Never use experimental features in production. +info=Info +providers=Providers +server-time=Server Time +server-uptime=Server Uptime +profile=Profile +memory=Memory +total-memory=Total Memory +free-memory=Free Memory +used-memory=Used Memory +system=System +current-working-directory=Current Working Directory +java-version=Java Version +java-vendor=Java Vendor +java-runtime=Java Runtime +java-vm=Java VM +java-vm-version=Java VM Version +java-home=Java Home +user-name=User Name +user-timezone=User Timezone +user-locale=User Locale +system-encoding=System Encoding +operating-system=Operating System +os-architecture=OS Architecture +spi=SPI +granted-client-scopes=Granted Client Scopes +additional-grants=Additional Grants +consent-created-date=Created +consent-last-updated-date=Last updated +revoke=Revoke +new-password=New Password +password-confirmation=Password Confirmation +reset-password=Reset Password +set-password=Set Password +credentials.temporary.tooltip=If enabled, the user must change the password on next login +remove-totp=Remove OTP +credentials.remove-totp.tooltip=Remove one time password generator for user. +reset-actions=Reset Actions +credentials.reset-actions.tooltip=Set of actions to execute when sending the user a Reset Actions Email. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure OTP' requires setup of a mobile password generator. +reset-actions-email=Reset Actions Email +send-email=Send email +credentials.reset-actions-email.tooltip=Sends an email to user with an embedded link. Clicking the link enables the user to execute the reset actions without first logging in. For example, set the action to update password, click this button, and the user can change the password without logging in. +add-user=Add user +created-at=Created At +user-enabled=User Enabled +user-enabled.tooltip=A disabled user cannot login. +user-temporarily-locked=User Temporarily Locked +user-temporarily-locked.tooltip=The user may be locked due to multiple failed attempts to log in. +unlock-user=Unlock user +federation-link=Federation Link +email-verified=Email Verified +email-verified.tooltip=Has the user's email been verified? +groups-joining=Groups +groups-joining.tooltip=Groups the user will be joining. To add a group, search for any existing one and select it. +groups-joining-select.placeholder=Select existing group +groups-joining-no-selected=No group selected +groups-joining-path=Path +required-user-actions=Required User Actions +required-user-actions.tooltip=Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure OTP' requires setup of a mobile password generator. +locale=Locale +select-one.placeholder=Select one... +impersonate=Impersonate +impersonate-user=Impersonate user +impersonate-user.tooltip=Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user. +identity-provider-alias=Identity Provider Alias +provider-user-id=Provider User ID +provider-username=Provider Username +no-identity-provider-links-available=No identity provider links available +group-membership=Group Membership +leave=Leave +group-membership.tooltip=Groups where the user has membership. To leave a group, select it and click Leave. +membership.available-groups.tooltip=Groups a user can join. Select a group and click Join. +table-of-realm-users=Table of Realm Users +view-all-users=View all users +view-all-groups=View all groups +view-all-roles=View all roles +unlock-users=Unlock users +no-users-available=No users available +users.instruction=Please enter a search, or click on view all users +clients.instruction=Please enter a search +consents=Consents +started=Started +logout-all-sessions=Log out all sessions +logout=Logout +new-name=New Name +new-description=New Description +ok=Ok +attributes=Attributes +role-mappings=Role Mappings +members=Members +details=Details +identity-provider-links=Identity Provider Links +register-required-action=Register required action +gender=Gender +address=Address +phone=Phone +profile-url=Profile URL +picture-url=Picture URL +website=Website +import-keys-and-cert=Import keys and cert +import-keys-and-cert.tooltip=Upload the client's key pair and cert. +upload-keys=Upload Keys +download-keys-and-cert=Download keys and cert +no-value-assigned.placeholder=No value assigned +remove=Remove +no-group-members=No group members +no-role-members=No role members +temporary=Temporary +join=Join +event-type=Event Type +events-config=Events Config +event-listeners=Event Listeners +login-events-settings=Login Events Settings +clear-events=Clear events +saved-types=Saved Types +clear-admin-events=Clear admin events +clear-changes=Clear changes +error=Error +# Authz +# Authz Common +authz-authorization=Authorization +authz-owner=Owner +authz-uri=URI +authz-uris=URIS +authz-scopes=Scopes +authz-resource=Resource +authz-resource-type=Resource Type +authz-resources=Resources +authz-scope=Scope +authz-authz-scopes=Authorization Scopes +authz-policies=Policies +authz-policy=Policy +authz-permissions=Permissions +authz-users=Users in Role +authz-evaluate=Evaluate +authz-icon-uri=Icon URI +authz-icon-uri.tooltip=An URI pointing to an icon. +authz-select-scope=Select a scope +authz-select-resource=Select a resource +authz-associated-policies=Associated Policies +authz-any-resource=Any resource +authz-any-scope=Any scope +authz-any-role=Any role +authz-policy-evaluation=Policy Evaluation +authz-select-user=Select a user +authz-select-client=Select a client +authz-entitlements=Entitlements +authz-no-resources=No resources +authz-result=Result +authz-authorization-services-enabled=Authorization Enabled +authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client +authz-required=Required +authz-show-details=Show Details +authz-hide-details=Hide Details +authz-associated-permissions=Associated Permissions +authz-no-permission-associated=No permissions associated +# Authz Settings +authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server. +authz-policy-enforcement-mode=Policy Enforcement Mode +authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allows access to any resource. +authz-policy-enforcement-mode-enforcing=Enforcing +authz-policy-enforcement-mode-permissive=Permissive +authz-policy-enforcement-mode-disabled=Disabled +authz-remote-resource-management=Remote Resource Management +authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can be managed only from this admin console. +authz-export-settings=Export Settings +authz-export-settings.tooltip=Export and download all authorization settings for this resource server. +authz-server-decision-strategy.tooltip=The decision strategy dictates how permissions are evaluated and how a final decision is obtained. 'Affirmative' means that at least one permission must evaluate to a positive decision in order to grant access to a resource and its scopes. 'Unanimous' means that all permissions must evaluate to a positive decision in order for the final decision to be also positive. +# Authz Resource List +authz-no-resources-available=No resources available. +authz-no-scopes-assigned=No scopes assigned. +authz-no-type-defined=No type defined. +authz-no-uri-defined=No URI defined. +authz-no-permission-assigned=No permission assigned. +authz-no-policy-assigned=No policy assigned. +authz-create-permission=Create Permission +# Authz Resource Detail +authz-add-resource=Add Resource +authz-resource-name.tooltip=A unique name for this resource. The name can be used to uniquely identify a resource, useful when querying for a specific resource. +authz-resource-owner.tooltip=The owner of this resource. +authz-resource-type.tooltip=The type of this resource. It can be used to group different resource instances with the same type. +authz-resource-uri.tooltip=Set of URIs which are protected by resource. +authz-resource-scopes.tooltip=The scopes associated with this resource. +authz-resource-attributes=Resource Attributes +authz-resource-attributes.tooltip=The attributes associated wth the resource. +authz-resource-user-managed-access-enabled=User-Managed Access Enabled +authz-resource-user-managed-access-enabled.tooltip=If enabled, the access to this resource can be managed by the resource owner. + +# Authz Scope List +authz-add-scope=Add Scope +authz-no-scopes-available=No scopes available. +# Authz Scope Detail +authz-scope-name.tooltip=A unique name for this scope. The name can be used to uniquely identify a scope, useful when querying for a specific scope. +# Authz Policy List +authz-all-types=All types +authz-create-policy=Create Policy +authz-no-policies-available=No policies available. +# Authz Policy Detail +authz-policy-name.tooltip=The name of this policy. +authz-policy-description.tooltip=A description for this policy. +authz-policy-logic=Logic +authz-policy-logic-positive=Positive +authz-policy-logic-negative=Negative +authz-policy-logic.tooltip=The logic dictates how the policy decision should be made. If 'Positive', the resulting effect (permit or deny) obtained during the evaluation of this policy will be used to perform a decision. If 'Negative', the resulting effect will be negated, in other words, a permit becomes a deny and vice-versa. +authz-policy-apply-policy=Apply Policy +authz-policy-apply-policy.tooltip=Specifies all the policies that must be applied to the scopes defined by this policy or permission. +authz-policy-decision-strategy=Decision Strategy +authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order for the final decision to be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order for the final decision to be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative. +authz-policy-decision-strategy-affirmative=Affirmative +authz-policy-decision-strategy-unanimous=Unanimous +authz-policy-decision-strategy-consensus=Consensus +authz-select-a-policy=Select existing policy +authz-no-policies-assigned=No policies assigned. +# Authz Role Policy Detail +authz-add-role-policy=Add Role Policy +authz-no-roles-assigned=No roles assigned. +authz-policy-role-realm-roles.tooltip=Specifies the *realm* roles allowed by this policy. +authz-policy-role-clients.tooltip=Selects a client in order to filter the client roles that can be applied to this policy. +authz-policy-role-client-roles.tooltip=Specifies the client roles allowed by this policy. +# Authz User Policy Detail +authz-add-user-policy=Add User Policy +authz-no-users-assigned=No users assigned. +authz-policy-user-users.tooltip=Specifies which user(s) are allowed by this policy. +# Authz Client Policy Detail +authz-add-client-policy=Add Client Policy +authz-no-clients-assigned=No clients assigned. +authz-policy-client-clients.tooltip=Specifies which client(s) are allowed by this policy. +# Authz Time Policy Detail +authz-add-time-policy=Add Time Policy +authz-policy-time-not-before.tooltip=Defines the time before which the policy MUST NOT be granted. Only granted if current date/time is after or equal to this value. +authz-policy-time-not-on-after=Not On or After +authz-policy-time-not-on-after.tooltip=Defines the time after which the policy MUST NOT be granted. Only granted if current date/time is before or equal to this value. +authz-policy-time-day-month=Day of Month +authz-policy-time-day-month.tooltip=Defines the day of month when the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current day of month is between or equal to the two values you provided. +authz-policy-time-month=Month +authz-policy-time-month.tooltip=Defines the month which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current month is between or equal to the two values you provided. +authz-policy-time-year=Year +authz-policy-time-year.tooltip=Defines the year when the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current year is between or equal to the two values you provided. +authz-policy-time-hour=Hour +authz-policy-time-hour.tooltip=Defines the hour when the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current hour is between or equal to the two values you provided. +authz-policy-time-minute=Minute +authz-policy-time-minute.tooltip=Defines the minute when the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current minute is between or equal to the two values you provided. +# Authz JS Policy Detail +authz-add-js-policy=Add JavaScript Policy +authz-policy-js-code=Code +authz-policy-js-code.tooltip=The JavaScript code providing the conditions for this policy. +# Authz Aggregated Policy Detail +authz-aggregated=Aggregated +authz-add-aggregated-policy=Add Aggregated Policy +# Authz Group Policy Detail +authz-add-group-policy=Add Group Policy +authz-no-groups-assigned=No groups assigned. +authz-policy-group-claim=Groups Claim +authz-policy-group-claim.tooltip=If defined, the policy will fetch user's groups from the given claim within an access token or ID token representing the identity asking permissions. If not defined, user's groups are obtained from your realm configuration. +authz-policy-group-groups.tooltip=Specifies the groups allowed by this policy. +# Authz Client Scope Policy Detail +authz-add-client-scope-policy=Add Client Scope Policy +authz-no-client-scopes-assigned=No client scopes assigned. +authz-policy-client-scope-client-scopes.tooltip=Specifies which client scope(s) are allowed by this policy. +select-a-client-scope=Select a client scope + +# Authz Permission List +authz-no-permissions-available=No permissions available. + +# Authz Permission Detail +authz-permission-name.tooltip=The name of this permission. +authz-permission-description.tooltip=A description for this permission. + +# Authz Resource Permission Detail +authz-add-resource-permission=Add Resource Permission +authz-permission-resource-apply-to-resource-type=Apply to Resource Type +authz-permission-resource-apply-to-resource-type.tooltip=Specifies if this permission should be applied to all resources with a given type. In this case, this permission will be evaluated for all instances of a given resource type. +authz-permission-resource-resource.tooltip=Specifies that this permission must be applied to a specific resource instance. +authz-permission-resource-type.tooltip=Specifies that this permission must be applied to all resources instances of a given type. + +# Authz Scope Permission Detail +authz-add-scope-permission=Add Scope Permission +authz-permission-scope-resource.tooltip=Restrict the scopes to those associated with the selected resource. If not selected all scopes would be available. +authz-permission-scope-scope.tooltip=Specifies that this permission must be applied to one or more scopes. + +# Authz Evaluation +authz-evaluation-identity-information=Identity Information +authz-evaluation-identity-information.tooltip=The available options to configure the identity information that will be used when evaluating policies. +authz-evaluation-client.tooltip=Select the client making this authorization request. If not provided, authorization requests would be done based on the client you are in. +authz-evaluation-user.tooltip=Select a user whose identity is going to be used to query permissions from the server. +authz-evaluation-role.tooltip=Select the roles you want to associate with the selected user. +authz-evaluation-new=New Evaluation +authz-evaluation-re-evaluate=Re-Evaluate +authz-evaluation-previous=Previous Evaluation +authz-evaluation-contextual-info=Contextual Information +authz-evaluation-contextual-info.tooltip=The available options to configure any contextual information that will be used when evaluating policies. +authz-evaluation-contextual-attributes=Contextual Attributes +authz-evaluation-contextual-attributes.tooltip=Any attribute provided by a running environment or execution context. +authz-evaluation-permissions.tooltip=The available options to configure the permissions to which policies will be applied. +authz-evaluation-evaluate=Evaluate +authz-evaluation-any-resource-with-scopes=Any resource with scope(s) +authz-evaluation-no-result=Could not obtain any result for the given authorization request. Check if the provided resource(s) or scope(s) are associated with any policy. +authz-evaluation-no-policies-resource=No policies were found for this resource. +authz-evaluation-result.tooltip=The overall result for this permission request. +authz-evaluation-scopes.tooltip=The list of allowed scopes. +authz-evaluation-policies.tooltip=Details about which policies were evaluated and their decisions. +authz-evaluation-authorization-data=Response +authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request. +authz-show-authorization-data=Show Authorization Data + +keys=Keys +status=Status +keystore=Keystore +keystores=Keystores +add-keystore=Add Keystore +add-keystore.placeholder=Add keystore... +view=View +active=Active +passive=Passive +disabled=Disabled +algorithm=Algorithm +providerHelpText=Provider description + +Sunday=Sunday +Monday=Monday +Tuesday=Tuesday +Wednesday=Wednesday +Thursday=Thursday +Friday=Friday +Saturday=Saturday + +user-storage-cache-policy=Cache Settings +userStorage.cachePolicy=Cache Policy +userStorage.cachePolicy.option.DEFAULT=DEFAULT +userStorage.cachePolicy.option.EVICT_WEEKLY=EVICT_WEEKLY +userStorage.cachePolicy.option.EVICT_DAILY=EVICT_DAILY +userStorage.cachePolicy.option.MAX_LIFESPAN=MAX_LIFESPAN +userStorage.cachePolicy.option.NO_CACHE=NO_CACHE +userStorage.cachePolicy.tooltip=Cache Policy for this storage provider. 'DEFAULT' is whatever the default settings are for the global cache. 'EVICT_DAILY' is a time of day every day that the cache will be invalidated. 'EVICT_WEEKLY' is a day of the week and time the cache will be invalidated. 'MAX-LIFESPAN' is the time in milliseconds that will be the lifespan of a cache entry. +userStorage.cachePolicy.evictionDay=Eviction Day +userStorage.cachePolicy.evictionDay.tooltip=Day of the week the entry will become invalid on +userStorage.cachePolicy.evictionHour=Eviction Hour +userStorage.cachePolicy.evictionHour.tooltip=Hour of day the entry will become invalid on. +userStorage.cachePolicy.evictionMinute=Eviction Minute +userStorage.cachePolicy.evictionMinute.tooltip=Minute of day the entry will become invalid on. +userStorage.cachePolicy.maxLifespan=Max Lifespan +userStorage.cachePolicy.maxLifespan.tooltip=Max lifespan of cache entry in milliseconds. +user-origin-link=Storage Origin +user-origin.tooltip=UserStorageProvider the user was loaded from +user-link.tooltip=UserStorageProvider this locally stored user was imported from. +client-origin-link=Storage Origin +client-origin.tooltip=Provider the client was loaded from + +client-storage-cache-policy=Cache Settings +clientStorage.cachePolicy=Cache Policy +clientStorage.cachePolicy.option.DEFAULT=DEFAULT +clientStorage.cachePolicy.option.EVICT_WEEKLY=EVICT_WEEKLY +clientStorage.cachePolicy.option.EVICT_DAILY=EVICT_DAILY +clientStorage.cachePolicy.option.MAX_LIFESPAN=MAX_LIFESPAN +clientStorage.cachePolicy.option.NO_CACHE=NO_CACHE +clientStorage.cachePolicy.tooltip=Cache Policy for this storage provider. 'DEFAULT' is whatever the default settings are for the global cache. 'EVICT_DAILY' is a time of day every day that the cache will be invalidated. 'EVICT_WEEKLY' is a day of the week and time the cache will be invalidated. 'MAX-LIFESPAN' is the time in milliseconds that will be the lifespan of a cache entry. +clientStorage.cachePolicy.evictionDay=Eviction Day +clientStorage.cachePolicy.evictionDay.tooltip=Day of the week the entry will become invalid on +clientStorage.cachePolicy.evictionHour=Eviction Hour +clientStorage.cachePolicy.evictionHour.tooltip=Hour of day the entry will become invalid on. +clientStorage.cachePolicy.evictionMinute=Eviction Minute +clientStorage.cachePolicy.evictionMinute.tooltip=Minute of day the entry will become invalid on. +clientStorage.cachePolicy.maxLifespan=Max Lifespan +clientStorage.cachePolicy.maxLifespan.tooltip=Max lifespan of cache entry in milliseconds. + +client-storage-list-no-entries=Keycloak can federate external client databases. By default, we support Openshift OAuth clients and service accounts. To get started, select a provider from the dropdown below: + + +disable=Disable +disableable-credential-types=Disableable Types +credentials.disableable.tooltip=List of credential types that you can disable +disable-credential-types=Disable Credential Types +credentials.disable.tooltip=Click button to disable selected credential types +credential-types=Credential Types +manage-user-password=Manage Password +supported-user-storage-credential-types=Supported User Storage Credential Types +supported-user-storage-credential-types.tooltip=Credential types, which are provided by User Storage Provider and which are configured for this user. Validation and eventually update of the credentials of those types can be delegated to the User Storage Provider based on the configuration and implementation of the particular provider. +provided-by=Provided By +manage-credentials=Manage Credentials +manage-credentials.tooltip=Credentials, which are not provided by the user storage. They are saved in the local database. +disable-credentials=Disable Credentials +credential-reset-actions=Credential Reset +credential-reset-actions-timeout=Expires In +credential-reset-actions-timeout.tooltip=Maximum time before the action permit expires. +ldap-mappers=LDAP Mappers +create-ldap-mapper=Create LDAP mapper +map-role-mgmt-scope-description=Policies that decide if an administrator can map this role to a user or group +manage-authz-users-scope-description=Policies that decide if an administrator can manage all users in the realm +view-authz-users-scope-description=Policies that decide if an administrator can view all users in realm +permissions-enabled-role=Permissions Enabled +permissions-enabled-role.tooltip=Determines if fine grained permissions are enabled for managing this role. Disabling will delete all current permissions that have been set up. +manage-permissions-role.tooltip=Fine grained permissions for managing roles. For example, you can define different policies for who is allowed to map a role. +lookup=Lookup +manage-permissions-users.tooltip=Fine grained permissions for managing all users in realm. You can define different policies for who is allowed to manage users in the realm. +permissions-enabled-users=Permissions Enabled +permissions-enabled-users.tooltip=Determines if fined grain permissions are enabled for managing users. Disabling will delete all current permissions that have been set up. +manage-permissions-client.tooltip=Fine grained permissions for administrators that want to manage this client or apply roles defined by this client. +manage-permissions-group.tooltip=Fine grained permissions for administrators that want to manage this group or the members of this group. +manage-authz-group-scope-description=Policies that decide if an administrator can manage this group +view-authz-group-scope-description=Policies that decide if an administrator can view this group +view-members-authz-group-scope-description=Policies that decide if an administrator can view the members of this group +token-exchange-authz-client-scope-description=Policies that decide which clients are allowed exchange tokens for a token that is targeted to this client. +token-exchange-authz-idp-scope-description=Policies that decide which clients are allowed exchange tokens for an external token minted by this identity provider. +manage-authz-client-scope-description=Policies that decide if an administrator can manage this client +configure-authz-client-scope-description=Reduced management permissions for administrator. Cannot set scope, template, or protocol mappers. +view-authz-client-scope-description=Policies that decide if an administrator can view this client +map-roles-authz-client-scope-description=Policies that decide if an administrator can map roles defined by this client +map-roles-client-scope-authz-client-scope-description=Policies that decide if an administrator can apply roles defined by this client to the client scope of another client +map-roles-composite-authz-client-scope-description=Policies that decide if an administrator can apply roles defined by this client as a composite to another role +map-role-authz-role-scope-description=Policies that decide if an administrator can map this role to a user or group +map-role-client-scope-authz-role-scope-description=Policies that decide if an administrator can apply this role to the client scope of a client +map-role-composite-authz-role-scope-description=Policies that decide if an administrator can apply this role as a composite to another role +manage-group-membership-authz-users-scope-description=Policies that decide if an administrator can manage group membership for all users in the realm. This is used in conjunction with specific group policy +impersonate-authz-users-scope-description=Policies that decide if administrator can impersonate other users +map-roles-authz-users-scope-description=Policies that decide if administrator can map roles for all users +user-impersonated-authz-users-scope-description=Policies that decide which users can be impersonated. These policies are applied to the user being impersonated. +manage-membership-authz-group-scope-description=Policies that decide if an administrator can add or remove users from this group +manage-members-authz-group-scope-description=Policies that decide if an administrator can manage the members of this group + +# KEYCLOAK-6771 Certificate Bound Token +# https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3 +advanced-client-settings=Advanced Settings +advanced-client-settings.tooltip=Expand this section to configure advanced settings of this client +tls-client-certificate-bound-access-tokens=OAuth 2.0 Mutual TLS Certificate Bound Access Tokens Enabled +tls-client-certificate-bound-access-tokens.tooltip=This enables support for OAuth 2.0 Mutual TLS Certificate Bound Access Tokens, which means that keycloak bind an access token and a refresh token with a X.509 certificate of a token requesting client exchanged in mutual TLS between keycloak's Token Endpoint and this client. These tokens can be treated as Holder-of-Key tokens instead of bearer tokens. +subjectdn=Subject DN +subjectdn-tooltip=A regular expression for validating Subject DN in the Client Certificate. Use "(.*?)(?:$)" to match all kind of expressions. + +pkce-code-challenge-method=Proof Key for Code Exchange Code Challenge Method +pkce-code-challenge-method.tooltip=Choose which code challenge method for PKCE is used. If not specified, keycloak does not applies PKCE to a client unless the client sends an authorization request with appropriate code challenge and code exchange method. + +key-not-allowed-here=Key '{{character}}' is not allowed here. + +# KEYCLOAK-10927 Implement LDAPv3 Password Modify Extended Operation +advanced-ldap-settings=Advanced Settings +ldap-query-supported-extensions=Query Supported Extensions +ldap-query-supported-extensions.tooltip=This will query LDAP server for supported extensions, controls and features. Some advanced settings of the LDAP provider will be then automatically configured based on the capabilities/extensions/features supported by LDAP server. For example if LDAPv3 Password Modify extension is supported by LDAP server, corresponding switch will be enabled for LDAP provider. + +notifications.info.header=Info! +notifications.success.header=Success! +notifications.error.header=Error! +notifications.warn.header=Warning! + +dialogs.delete.title=Delete {{type}} +dialogs.delete.message=Are you sure you want to permanently delete the {{type}} {{name}}? +dialogs.delete.confirm=Delete +dialogs.cancel=Cancel +dialogs.ok=Ok diff --git a/base/admin/messages/messages_en.properties b/base/admin/messages/messages_en.properties new file mode 100644 index 0000000..a3e115d --- /dev/null +++ b/base/admin/messages/messages_en.properties @@ -0,0 +1,41 @@ +invalidPasswordMinLengthMessage=Invalid password: minimum length {0}. +invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters. +invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits. +invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters. +invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters. +invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username. +invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email. +invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s). +invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords. +invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted. +invalidPasswordGenericMessage=Invalid password: new password does not match password policies. + +ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")". +ldapErrorConnectionTimeoutNotNumber=Connection Timeout must be a number +ldapErrorReadTimeoutNotNumber=Read Timeout must be a number +ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Roles Mapping is not used. +ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together. +ldapErrorCantWriteOnlyForReadOnlyLdap=Can not set write only when LDAP provider mode is not WRITABLE +ldapErrorCantWriteOnlyAndReadOnly=Can not set write-only and read-only together +ldapErrorCantEnableStartTlsAndConnectionPooling=Can not enable both StartTLS and connection pooling. +ldapErrorCantEnableUnsyncedAndImportOff=Can not disable Importing users when LDAP provider mode is UNSYNCED +ldapErrorMissingGroupsPathGroup=Groups path group does not exist - please create the group on specified path first + +clientRedirectURIsFragmentError=Redirect URIs must not contain an URI fragment +clientRootURLFragmentError=Root URL must not contain an URL fragment +clientRootURLIllegalSchemeError=Root URL uses an illegal scheme +clientBaseURLIllegalSchemeError=Base URL uses an illegal scheme +backchannelLogoutUrlIllegalSchemeError=Backchannel logout URL uses an illegal scheme +clientRedirectURIsIllegalSchemeError=A redirect URI uses an illegal scheme +clientBaseURLInvalid=Base URL is not a valid URL +clientRootURLInvalid=Root URL is not a valid URL +clientRedirectURIsInvalid=A redirect URI is not a valid URI +backchannelLogoutUrlIsInvalid=Backchannel logout URL is not a valid URL + + +pairwiseMalformedClientRedirectURI=Client contained an invalid redirect URI. +pairwiseClientRedirectURIsMissingHost=Client redirect URIs must contain a valid host component. +pairwiseClientRedirectURIsMultipleHosts=Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components. +pairwiseMalformedSectorIdentifierURI=Malformed Sector Identifier URI. +pairwiseFailedToGetRedirectURIs=Failed to get redirect URIs from the Sector Identifier URI. +pairwiseRedirectURIsMismatch=Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI. diff --git a/base/admin/resources/js/app.js b/base/admin/resources/js/app.js new file mode 100755 index 0000000..81dc3d9 --- /dev/null +++ b/base/admin/resources/js/app.js @@ -0,0 +1,3495 @@ +'use strict'; + +var auth = {}; +var resourceBundle; +var locale = 'en'; + +var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload', 'angularTreeview', 'pascalprecht.translate', 'ngCookies', 'ngSanitize', 'ui.ace']); +var resourceRequests = 0; +var loadingTimer = -1; +var translateProvider = null; +var currentRealm = null; + +angular.element(document).ready(function () { + var keycloakAuth = new Keycloak(consoleBaseUrl + 'config'); + + function whoAmI(success, error) { + var req = new XMLHttpRequest(); + req.open('GET', consoleBaseUrl + 'whoami', true); + req.setRequestHeader('Accept', 'application/json'); + req.setRequestHeader('Authorization', 'bearer ' + keycloakAuth.token); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + var data = JSON.parse(req.responseText); + success(data); + } else { + error(); + } + } + } + + req.send(); + } + + function loadResourceBundle(success, error) { + var req = new XMLHttpRequest(); + req.open('GET', consoleBaseUrl + 'messages.json?lang=' + locale, true); + req.setRequestHeader('Accept', 'application/json'); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + var data = JSON.parse(req.responseText); + success && success(data); + } else { + error && error(); + } + } + } + + req.send(); + } + + function loadSelect2Localization() { + // 'en' is the built-in default and does not have to be loaded. + var supportedLocales = ['ar', 'az', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'eu', 'fa', 'fi', 'fr', + 'gl', 'he', 'hr', 'hu', 'id', 'is', 'it', 'ja', 'ka', 'ko', 'lt', 'lv', 'mk', 'ms', 'nl', 'no', 'pl', + 'pt-BR', 'pt-PT', 'ro', 'rs', 'ru', 'sk', 'sv', 'th', 'tr', 'ug-CN', 'uk', 'vi', 'zh-CN', 'zh-TW']; + if (supportedLocales.indexOf(locale) == -1) return; + var select2JsUrl; + var allScriptElements = document.getElementsByTagName('script'); + for (var i = 0, n = allScriptElements.length; i < n; i++) { + var src = allScriptElements[i].getAttribute('src'); + if (src && src.match(/\/select2\/select2\.js$/)) { + select2JsUrl = src; + break; + } + } + if (!select2JsUrl) return; + var scriptElement = document.createElement('script'); + scriptElement.src = select2JsUrl.replace(/\/select2\/select2\.js$/, '/select2/select2_locale_'+locale+'.js'); + scriptElement.type = 'text/javascript'; + document.getElementsByTagName('head')[0].appendChild(scriptElement); + } + + function hasAnyAccess(user) { + return user && user['realm_access']; + } + + keycloakAuth.onAuthLogout = function() { + location.reload(); + } + + auth.refreshPermissions = function(success, error) { + whoAmI(function(data) { + auth.user = data; + auth.loggedIn = true; + auth.hasAnyAccess = hasAnyAccess(data); + + success(); + }, function() { + error(); + }); + }; + + module.factory('Auth', function () { + return auth; + }); + + keycloakAuth.init({ onLoad: 'login-required', pkceMethod: 'S256' }).then(function () { + auth.authz = keycloakAuth; + + whoAmI(function(data) { + auth.user = data; + auth.loggedIn = true; + auth.hasAnyAccess = hasAnyAccess(data); + locale = auth.user.locale || locale; + + loadResourceBundle(function(data) { + resourceBundle = data; + + var injector = angular.bootstrap(document, ["keycloak"]); + + injector.get('$translate')('consoleTitle').then(function (consoleTitle) { + document.title = consoleTitle; + }); + }); + }); + + loadSelect2Localization(); + }).catch(function () { + window.location.reload(); + }); +}); + +module.factory('authInterceptor', function($q, Auth) { + return { + request: function (config) { + if (!config.url.match(/.html$/)) { + var deferred = $q.defer(); + if (Auth.authz.token) { + Auth.authz.updateToken(5).then(function () { + config.headers = config.headers || {}; + config.headers.Authorization = 'Bearer ' + Auth.authz.token; + + deferred.resolve(config); + }).catch(function () { + location.reload(); + }); + } + return deferred.promise; + } else { + return config; + } + } + }; +}); + +module.config(['$translateProvider', function($translateProvider) { + translateProvider = $translateProvider; + $translateProvider.useSanitizeValueStrategy('sanitizeParameters'); + $translateProvider.preferredLanguage(locale); + $translateProvider.translations(locale, resourceBundle); +}]); + +// Change for upgrade to AngularJS 1.6 +// See https://github.com/angular/angular.js/commit/aa077e81129c740041438688dff2e8d20c3d7b52 +module.config(['$locationProvider', function($locationProvider) { + $locationProvider.hashPrefix(''); +}]); + +module.config([ '$routeProvider', function($routeProvider) { + $routeProvider + .when('/create/realm', { + templateUrl : resourceUrl + '/partials/realm-create.html', + resolve : { + + }, + controller : 'RealmCreateCtrl' + }) + .when('/realms/:realm', { + templateUrl : resourceUrl + '/partials/realm-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmDetailCtrl' + }) + .when('/realms/:realm/localization', { + templateUrl : resourceUrl + '/partials/realm-localization.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + realmSpecificLocales : function(RealmSpecificLocalesLoader) { + return RealmSpecificLocalesLoader(); + } + }, + controller : 'RealmLocalizationCtrl' + }) + .when('/realms/:realm/localization/upload', { + templateUrl : resourceUrl + '/partials/realm-localization-upload.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmLocalizationUploadCtrl' + }) + .when('/realms/:realm/login-settings', { + templateUrl : resourceUrl + '/partials/realm-login-settings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfo) { + return ServerInfo.delay; + } + }, + controller : 'RealmLoginSettingsCtrl' + }) + .when('/realms/:realm/theme-settings', { + templateUrl : resourceUrl + '/partials/realm-theme-settings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmThemeCtrl' + }) + .when('/realms/:realm/cache-settings', { + templateUrl : resourceUrl + '/partials/realm-cache-settings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmCacheCtrl' + }) + .when('/realms', { + templateUrl : resourceUrl + '/partials/realm-list.html', + controller : 'RealmListCtrl' + }) + .when('/realms/:realm/token-settings', { + templateUrl : resourceUrl + '/partials/realm-tokens.html', + resolve : { + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RealmTokenDetailCtrl' + }) + .when('/realms/:realm/client-registration/client-initial-access', { + templateUrl : resourceUrl + '/partials/client-initial-access.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientInitialAccess : function(ClientInitialAccessLoader) { + return ClientInitialAccessLoader(); + } + }, + controller : 'ClientInitialAccessCtrl' + }) + .when('/realms/:realm/client-registration/client-initial-access/create', { + templateUrl : resourceUrl + '/partials/client-initial-access-create.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'ClientInitialAccessCreateCtrl' + }) + .when('/realms/:realm/client-registration/client-reg-policies', { + templateUrl : resourceUrl + '/partials/client-reg-policies.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + policies : function(ComponentsLoader) { + return ComponentsLoader.loadComponents(null, 'org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy'); + }, + clientRegistrationPolicyProviders : function(ClientRegistrationPolicyProvidersLoader) { + return ClientRegistrationPolicyProvidersLoader(); + } + }, + controller : 'ClientRegPoliciesCtrl' + }) + .when('/realms/:realm/client-registration/client-reg-policies/create/:componentType/:providerId', { + templateUrl : resourceUrl + '/partials/client-reg-policy-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function($route) { + return { + providerType: 'org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy', + subType: $route.current.params.componentType, + providerId: $route.current.params.providerId + }; + }, + clientRegistrationPolicyProviders : function(ClientRegistrationPolicyProvidersLoader) { + return ClientRegistrationPolicyProvidersLoader(); + } + }, + controller : 'ClientRegPolicyDetailCtrl' + }) + .when('/realms/:realm/client-registration/client-reg-policies/:provider/:componentId', { + templateUrl : resourceUrl + '/partials/client-reg-policy-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + clientRegistrationPolicyProviders : function(ClientRegistrationPolicyProvidersLoader) { + return ClientRegistrationPolicyProvidersLoader(); + } + }, + controller : 'ClientRegPolicyDetailCtrl' + }) + .when('/realms/:realm/keys', { + templateUrl : resourceUrl + '/partials/realm-keys.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + keys: function(RealmKeysLoader) { + return RealmKeysLoader(); + } + }, + controller : 'RealmKeysCtrl' + }) + .when('/realms/:realm/keys/passive', { + templateUrl : resourceUrl + '/partials/realm-keys-passive.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + keys: function(RealmKeysLoader) { + return RealmKeysLoader(); + } + }, + controller : 'RealmKeysCtrl' + }) + .when('/realms/:realm/keys/disabled', { + templateUrl : resourceUrl + '/partials/realm-keys-disabled.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + keys: function(RealmKeysLoader) { + return RealmKeysLoader(); + } + }, + controller : 'RealmKeysCtrl' + }) + .when('/realms/:realm/keys/providers', { + templateUrl : resourceUrl + '/partials/realm-keys-providers.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmKeysProvidersCtrl' + }) + .when('/create/keys/:realm/providers/:provider', { + templateUrl : resourceUrl + '/partials/realm-keys-generic.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function() { + return { + }; + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericKeystoreCtrl' + }) + .when('/realms/:realm/keys/providers/:provider/:componentId', { + templateUrl : resourceUrl + '/partials/realm-keys-generic.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericKeystoreCtrl' + }) + .when('/realms/:realm/identity-provider-settings', { + templateUrl : resourceUrl + '/partials/realm-identity-provider.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + instance : function(IdentityProviderLoader) { + return {}; + }, + providerFactory : function(IdentityProviderFactoryLoader) { + return {}; + }, + authFlows : function(AuthenticationFlowsLoader) { + return {}; + } + }, + controller : 'RealmIdentityProviderCtrl' + }) + .when('/create/identity-provider/:realm/:provider_id', { + templateUrl : function(params){ return resourceUrl + '/partials/realm-identity-provider-' + params.provider_id + '.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + instance : function(IdentityProviderLoader) { + return {}; + }, + providerFactory : function(IdentityProviderFactoryLoader) { + return new IdentityProviderFactoryLoader(); + }, + authFlows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + } + }, + controller : 'RealmIdentityProviderCtrl' + }) + .when('/realms/:realm/identity-provider-settings/provider/:provider_id/:alias', { + templateUrl : function(params){ return resourceUrl + '/partials/realm-identity-provider-' + params.provider_id + '.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + instance : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + providerFactory : function(IdentityProviderFactoryLoader) { + return IdentityProviderFactoryLoader(); + }, + authFlows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + } + }, + controller : 'RealmIdentityProviderCtrl' + }) + .when('/realms/:realm/identity-provider-settings/provider/:provider_id/:alias/export', { + templateUrl : resourceUrl + '/partials/realm-identity-provider-export.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + providerFactory : function(IdentityProviderFactoryLoader) { + return IdentityProviderFactoryLoader(); + } + }, + controller : 'RealmIdentityProviderExportCtrl' + }) + .when('/realms/:realm/identity-provider-mappers/:alias/mappers', { + templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mappers.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + mapperTypes : function(IdentityProviderMapperTypesLoader) { + return IdentityProviderMapperTypesLoader(); + }, + mappers : function(IdentityProviderMappersLoader) { + return IdentityProviderMappersLoader(); + } + }, + controller : 'IdentityProviderMapperListCtrl' + }) + .when('/realms/:realm/identity-provider-mappers/:alias/mappers/:mapperId', { + templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + mapperTypes : function(IdentityProviderMapperTypesLoader) { + return IdentityProviderMapperTypesLoader(); + }, + mapper : function(IdentityProviderMapperLoader) { + return IdentityProviderMapperLoader(); + } + }, + controller : 'IdentityProviderMapperCtrl' + }) + .when('/create/identity-provider-mappers/:realm/:alias', { + templateUrl : function(params){ return resourceUrl + '/partials/identity-provider-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + }, + mapperTypes : function(IdentityProviderMapperTypesLoader) { + return IdentityProviderMapperTypesLoader(); + } + }, + controller : 'IdentityProviderMapperCreateCtrl' + }) + + .when('/realms/:realm/default-roles', { + templateUrl : resourceUrl + '/partials/realm-default-roles.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + } + }, + controller : 'RealmDefaultRolesCtrl' + }) + .when('/realms/:realm/smtp-settings', { + templateUrl : resourceUrl + '/partials/realm-smtp.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RealmSMTPSettingsCtrl' + }) + .when('/realms/:realm/events', { + templateUrl : resourceUrl + '/partials/realm-events.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmEventsCtrl' + }) + .when('/realms/:realm/admin-events', { + templateUrl : resourceUrl + '/partials/realm-events-admin.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmAdminEventsCtrl' + }) + .when('/realms/:realm/events-settings', { + templateUrl : resourceUrl + '/partials/realm-events-config.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + eventsConfig : function(RealmEventsConfigLoader) { + return RealmEventsConfigLoader(); + } + }, + controller : 'RealmEventsConfigCtrl' + }) + .when('/realms/:realm/partial-import', { + templateUrl : resourceUrl + '/partials/partial-import.html', + resolve : { + resourceName : function() { return 'users'}, + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RealmImportCtrl' + }) + .when('/realms/:realm/partial-export', { + templateUrl : resourceUrl + '/partials/partial-export.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RealmExportCtrl' + }) + .when('/create/user/:realm', { + templateUrl : resourceUrl + '/partials/user-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function() { + return {}; + } + }, + controller : 'UserDetailCtrl' + }) + .when('/realms/:realm/users/:user', { + templateUrl : resourceUrl + '/partials/user-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + } + }, + controller : 'UserDetailCtrl' + }) + .when('/realms/:realm/users/:user/user-attributes', { + templateUrl : resourceUrl + '/partials/user-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + } + }, + controller : 'UserDetailCtrl' + }) + .when('/realms/:realm/users/:user/user-credentials', { + templateUrl : resourceUrl + '/partials/user-credentials.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + } + }, + controller : 'UserCredentialsCtrl' + }) + .when('/realms/:realm/users/:user/role-mappings', { + templateUrl : resourceUrl + '/partials/role-mappings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + client : function() { + return {}; + } + }, + controller : 'UserRoleMappingCtrl' + }) + .when('/realms/:realm/users/:user/groups', { + templateUrl : resourceUrl + '/partials/user-group-membership.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + } + }, + controller : 'UserGroupMembershipCtrl' + }) + .when('/realms/:realm/users/:user/sessions', { + templateUrl : resourceUrl + '/partials/user-sessions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + sessions : function(UserSessionsLoader) { + return UserSessionsLoader(); + } + }, + controller : 'UserSessionsCtrl' + }) + .when('/realms/:realm/users/:user/federated-identity', { + templateUrl : resourceUrl + '/partials/user-federated-identity-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + federatedIdentities : function(UserFederatedIdentityLoader) { + return UserFederatedIdentityLoader(); + } + }, + controller : 'UserFederatedIdentityCtrl' + }) + .when('/create/federated-identity/:realm/:user', { + templateUrl : resourceUrl + '/partials/user-federated-identity-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + federatedIdentities : function(UserFederatedIdentityLoader) { + return UserFederatedIdentityLoader(); + } + }, + controller : 'UserFederatedIdentityAddCtrl' + }) + .when('/realms/:realm/users/:user/consents', { + templateUrl : resourceUrl + '/partials/user-consents.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + userConsents : function(UserConsentsLoader) { + return UserConsentsLoader(); + } + }, + controller : 'UserConsentsCtrl' + }) + .when('/realms/:realm/users/:user/offline-sessions/:client', { + templateUrl : resourceUrl + '/partials/user-offline-sessions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + offlineSessions : function(UserOfflineSessionsLoader) { + return UserOfflineSessionsLoader(); + } + }, + controller : 'UserOfflineSessionsCtrl' + }) + .when('/realms/:realm/users', { + templateUrl : resourceUrl + '/partials/user-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'UserListCtrl' + }) + + .when('/create/role/:realm', { + templateUrl : resourceUrl + '/partials/role-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function() { + return {}; + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'RoleDetailCtrl' + }) + .when('/realms/:realm/roles/:role', { + templateUrl : resourceUrl + '/partials/role-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'RoleDetailCtrl' + }) + .when('/realms/:realm/roles/:role/role-attributes', { + templateUrl : resourceUrl + '/partials/role-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'RoleDetailCtrl' + }) + .when('/realms/:realm/roles/:role/users', { + templateUrl : resourceUrl + '/partials/realm-role-users.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + } + }, + controller : 'RoleMembersCtrl' + }) + .when('/realms/:realm/roles', { + templateUrl : resourceUrl + '/partials/role-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RoleListCtrl' + }) + .when('/realms/:realm/groups', { + templateUrl : resourceUrl + '/partials/group-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'GroupListCtrl' + }) + .when('/create/group/:realm/parent/:parentId', { + templateUrl : resourceUrl + '/partials/create-group.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + parentId : function($route) { + return $route.current.params.parentId; + } + }, + controller : 'GroupCreateCtrl' + }) + .when('/realms/:realm/groups/:group', { + templateUrl : resourceUrl + '/partials/group-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupDetailCtrl' + }) + .when('/realms/:realm/groups/:group/attributes', { + templateUrl : resourceUrl + '/partials/group-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupDetailCtrl' + }) + .when('/realms/:realm/groups/:group/members', { + templateUrl : resourceUrl + '/partials/group-members.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupMembersCtrl' + }) + .when('/realms/:realm/groups/:group/role-mappings', { + templateUrl : resourceUrl + '/partials/group-role-mappings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + client : function() { + return {}; + } + }, + controller : 'GroupRoleMappingCtrl' + }) + .when('/realms/:realm/default-groups', { + templateUrl : resourceUrl + '/partials/default-groups.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'DefaultGroupsCtrl' + }) + + + .when('/create/role/:realm/clients/:client', { + templateUrl : resourceUrl + '/partials/client-role-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function() { + return {}; + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientRoleDetailCtrl' + }) + .when('/realms/:realm/clients/:client/roles/:role', { + templateUrl : resourceUrl + '/partials/client-role-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function(ClientRoleLoader) { + return ClientRoleLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientRoleDetailCtrl' + }) + .when('/realms/:realm/clients/:client/roles/:role/role-attributes', { + templateUrl : resourceUrl + '/partials/client-role-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function(ClientRoleLoader) { + return ClientRoleLoader(); + }, + roles : function(RoleListLoader) { + return RoleListLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientRoleDetailCtrl' + }) + .when('/realms/:realm/clients/:client/roles/:role/users', { + templateUrl : resourceUrl + '/partials/client-role-users.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function(ClientRoleLoader) { + return ClientRoleLoader(); + } + }, + controller : 'ClientRoleMembersCtrl' + }) + .when('/realms/:realm/clients/:client/mappers', { + templateUrl : resourceUrl + '/partials/client-mappers.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientProtocolMapperListCtrl' + }) + .when('/realms/:realm/clients/:client/add-mappers', { + templateUrl : resourceUrl + '/partials/client-mappers-add.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'AddBuiltinProtocolMapperCtrl' + }) + .when('/realms/:realm/clients/:client/mappers/:id', { + templateUrl : resourceUrl + '/partials/client-protocol-mapper-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + mapper : function(ClientProtocolMapperLoader) { + return ClientProtocolMapperLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + + }, + controller : 'ClientProtocolMapperCtrl' + }) + .when('/create/client/:realm/:client/mappers', { + templateUrl : resourceUrl + '/partials/client-protocol-mapper-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientProtocolMapperCreateCtrl' + }) + .when('/realms/:realm/clients/:client/client-scopes/setup-scopes', { + templateUrl : resourceUrl + '/partials/client-scopes-setup.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clientScopes : function(ClientScopeListLoader) { + return ClientScopeListLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + clientDefaultClientScopes : function(ClientDefaultClientScopesLoader) { + return ClientDefaultClientScopesLoader(); + }, + clientOptionalClientScopes : function(ClientOptionalClientScopesLoader) { + return ClientOptionalClientScopesLoader(); + } + }, + controller : 'ClientClientScopesSetupCtrl' + }) + .when('/realms/:realm/clients/:client/client-scopes/evaluate-scopes', { + templateUrl : resourceUrl + '/partials/client-scopes-evaluate.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + clientScopes : function(ClientScopeListLoader) { + return ClientScopeListLoader(); + }, + clientDefaultClientScopes : function(ClientDefaultClientScopesLoader) { + return ClientDefaultClientScopesLoader(); + }, + clientOptionalClientScopes : function(ClientOptionalClientScopesLoader) { + return ClientOptionalClientScopesLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientClientScopesEvaluateCtrl' + }) + .when('/realms/:realm/client-scopes/:clientScope/mappers', { + templateUrl : resourceUrl + '/partials/client-scope-mappers.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScope : function(ClientScopeLoader) { + return ClientScopeLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientScopeProtocolMapperListCtrl' + }) + .when('/realms/:realm/client-scopes/:clientScope/add-mappers', { + templateUrl : resourceUrl + '/partials/client-scope-mappers-add.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScope : function(ClientScopeLoader) { + return ClientScopeLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientScopeAddBuiltinProtocolMapperCtrl' + }) + .when('/realms/:realm/client-scopes/:clientScope/mappers/:id', { + templateUrl : resourceUrl + '/partials/client-scope-protocol-mapper-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScope : function(ClientScopeLoader) { + return ClientScopeLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + mapper : function(ClientScopeProtocolMapperLoader) { + return ClientScopeProtocolMapperLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + + }, + controller : 'ClientScopeProtocolMapperCtrl' + }) + .when('/create/client-scope/:realm/:clientScope/mappers', { + templateUrl : resourceUrl + '/partials/client-scope-protocol-mapper-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + clientScope : function(ClientScopeLoader) { + return ClientScopeLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientScopeProtocolMapperCreateCtrl' + }) + .when('/realms/:realm/clients/:client/sessions', { + templateUrl : resourceUrl + '/partials/client-sessions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + sessionCount : function(ClientSessionCountLoader) { + return ClientSessionCountLoader(); + } + }, + controller : 'ClientSessionsCtrl' + }) + .when('/realms/:realm/clients/:client/offline-access', { + templateUrl : resourceUrl + '/partials/client-offline-sessions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + offlineSessionCount : function(ClientOfflineSessionCountLoader) { + return ClientOfflineSessionCountLoader(); + } + }, + controller : 'ClientOfflineSessionsCtrl' + }) + .when('/realms/:realm/clients/:client/credentials', { + templateUrl : resourceUrl + '/partials/client-credentials.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clientAuthenticatorProviders : function(ClientAuthenticatorProvidersLoader) { + return ClientAuthenticatorProvidersLoader(); + }, + clientConfigProperties: function(PerClientAuthenticationConfigDescriptionLoader) { + return PerClientAuthenticationConfigDescriptionLoader(); + } + }, + controller : 'ClientCredentialsCtrl' + }) + .when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/import/:attribute', { + templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-import.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + callingContext : function() { + return "jwt-credentials"; + } + }, + controller : 'ClientCertificateImportCtrl' + }) + .when('/realms/:realm/clients/:client/credentials/client-jwt/:keyType/export/:attribute', { + templateUrl : resourceUrl + '/partials/client-credentials-jwt-key-export.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + callingContext : function() { + return "jwt-credentials"; + } + }, + controller : 'ClientCertificateExportCtrl' + }) + .when('/realms/:realm/clients/:client/identity-provider', { + templateUrl : resourceUrl + '/partials/client-identity-provider.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientIdentityProviderCtrl' + }) + .when('/realms/:realm/clients/:client/clustering', { + templateUrl : resourceUrl + '/partials/client-clustering.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientClusteringCtrl' + }) + .when('/register-node/realms/:realm/clients/:client/clustering', { + templateUrl : resourceUrl + '/partials/client-clustering-node.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientClusteringNodeCtrl' + }) + .when('/realms/:realm/clients/:client/clustering/:node', { + templateUrl : resourceUrl + '/partials/client-clustering-node.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientClusteringNodeCtrl' + }) + .when('/realms/:realm/clients/:client/saml/keys', { + templateUrl : resourceUrl + '/partials/client-saml-keys.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientSamlKeyCtrl' + }) + .when('/realms/:realm/clients/:client/saml/:keyType/import/:attribute', { + templateUrl : resourceUrl + '/partials/client-saml-key-import.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + callingContext : function() { + return "saml"; + } + }, + controller : 'ClientCertificateImportCtrl' + }) + .when('/realms/:realm/clients/:client/saml/:keyType/export/:attribute', { + templateUrl : resourceUrl + '/partials/client-saml-key-export.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + callingContext : function() { + return "saml"; + } + }, + controller : 'ClientCertificateExportCtrl' + }) + .when('/realms/:realm/clients/:client/roles', { + templateUrl : resourceUrl + '/partials/client-role-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientRoleListCtrl' + }) + .when('/realms/:realm/clients/:client/revocation', { + templateUrl : resourceUrl + '/partials/client-revocation.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientRevocationCtrl' + }) + .when('/realms/:realm/clients/:client/scope-mappings', { + templateUrl : resourceUrl + '/partials/client-scope-mappings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientScopeMappingCtrl' + }) + .when('/realms/:realm/clients/:client/installation', { + templateUrl : resourceUrl + '/partials/client-installation.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientInstallationCtrl' + }) + .when('/realms/:realm/clients/:client/service-account-roles', { + templateUrl : resourceUrl + '/partials/client-service-account-roles.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(ClientServiceAccountUserLoader) { + return ClientServiceAccountUserLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'UserRoleMappingCtrl' + }) + .when('/create/client/:realm', { + templateUrl : resourceUrl + '/partials/create-client.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + client : function() { + return {}; + }, + flows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'CreateClientCtrl' + }) + .when('/realms/:realm/clients/:client', { + templateUrl : resourceUrl + '/partials/client-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + flows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientDetailCtrl' + }) + .when('/create/client-scope/:realm', { + templateUrl : resourceUrl + '/partials/client-scope-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScope : function() { + return {}; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientScopeDetailCtrl' + }) + .when('/realms/:realm/client-scopes/:clientScope', { + templateUrl : resourceUrl + '/partials/client-scope-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScope : function(ClientScopeLoader) { + return ClientScopeLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientScopeDetailCtrl' + }) + .when('/realms/:realm/client-scopes/:clientScope/scope-mappings', { + templateUrl : resourceUrl + '/partials/client-scope-scope-mappings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScope : function(ClientScopeLoader) { + return ClientScopeLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientScopeScopeMappingCtrl' + }) + .when('/realms/:realm/clients', { + templateUrl : resourceUrl + '/partials/client-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + + }, + controller : 'ClientListCtrl' + }) + .when('/realms/:realm/client-scopes', { + templateUrl : resourceUrl + '/partials/client-scope-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScopes : function(ClientScopeListLoader) { + return ClientScopeListLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + + }, + controller : 'ClientScopeListCtrl' + }) + .when('/realms/:realm/default-client-scopes', { + templateUrl : resourceUrl + '/partials/client-scopes-realm-default.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + clientScopes : function(ClientScopeListLoader) { + return ClientScopeListLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + realmDefaultClientScopes : function(RealmDefaultClientScopesLoader) { + return RealmDefaultClientScopesLoader(); + }, + realmOptionalClientScopes : function(RealmOptionalClientScopesLoader) { + return RealmOptionalClientScopesLoader(); + } + }, + controller : 'ClientScopesRealmDefaultCtrl' + }) + .when('/import/client/:realm', { + templateUrl : resourceUrl + '/partials/client-import.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientImportCtrl' + }) + .when('/realms/:realm/client-stores', { + templateUrl : resourceUrl + '/partials/client-storage-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ClientStoresCtrl' + }) + .when('/realms/:realm/client-storage/providers/:provider/:componentId', { + templateUrl : resourceUrl + '/partials/client-storage-generic.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericClientStorageCtrl' + }) + .when('/create/client-storage/:realm/providers/:provider', { + templateUrl : resourceUrl + '/partials/client-storage-generic.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function() { + return { + + }; + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericClientStorageCtrl' + }) + .when('/', { + templateUrl : resourceUrl + '/partials/home.html', + controller : 'HomeCtrl' + }) + .when('/mocks/:realm', { + templateUrl : resourceUrl + '/partials/realm-detail_mock.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmDetailCtrl' + }) + .when('/realms/:realm/sessions/revocation', { + templateUrl : resourceUrl + '/partials/session-revocation.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RealmRevocationCtrl' + }) + .when('/realms/:realm/sessions/realm', { + templateUrl : resourceUrl + '/partials/session-realm.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + stats : function(RealmClientSessionStatsLoader) { + return RealmClientSessionStatsLoader(); + } + }, + controller : 'RealmSessionStatsCtrl' + }) + .when('/create/user-storage/:realm/providers/ldap', { + templateUrl : resourceUrl + '/partials/user-storage-ldap.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function() { + return { + + }; + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'LDAPUserStorageCtrl' + }) + .when('/create/user-storage/:realm/providers/kerberos', { + templateUrl : resourceUrl + '/partials/user-storage-kerberos.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function() { + return { + + }; + }, + providerId : function($route) { + return "kerberos"; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericUserStorageCtrl' + }) + .when('/create/user-storage/:realm/providers/:provider', { + templateUrl : resourceUrl + '/partials/user-storage-generic.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function() { + return { + + }; + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericUserStorageCtrl' + }) + .when('/realms/:realm/user-storage/providers/ldap/:componentId', { + templateUrl : resourceUrl + '/partials/user-storage-ldap.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'LDAPUserStorageCtrl' + }) + .when('/realms/:realm/user-storage/providers/kerberos/:componentId', { + templateUrl : resourceUrl + '/partials/user-storage-kerberos.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + providerId : function($route) { + return "kerberos"; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericUserStorageCtrl' + }) + .when('/realms/:realm/user-storage/providers/:provider/:componentId', { + templateUrl : resourceUrl + '/partials/user-storage-generic.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + instance : function(ComponentLoader) { + return ComponentLoader(); + }, + providerId : function($route) { + return $route.current.params.provider; + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'GenericUserStorageCtrl' + }) + .when('/realms/:realm/ldap-mappers/:componentId', { + templateUrl : function(params){ return resourceUrl + '/partials/user-storage-ldap-mappers.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + provider : function(ComponentLoader) { + return ComponentLoader(); + }, + mappers : function(ComponentsLoader, $route) { + return ComponentsLoader.loadComponents($route.current.params.componentId, 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'); + } + }, + controller : 'LDAPMapperListCtrl' + }) + .when('/create/ldap-mappers/:realm/:componentId', { + templateUrl : function(params){ return resourceUrl + '/partials/user-storage-ldap-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + provider : function(ComponentLoader) { + return ComponentLoader(); + }, + mapperTypes : function(SubComponentTypesLoader, $route) { + return SubComponentTypesLoader.loadComponents($route.current.params.componentId, 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'LDAPMapperCreateCtrl' + }) + .when('/realms/:realm/ldap-mappers/:componentId/mappers/:mapperId', { + templateUrl : function(params){ return resourceUrl + '/partials/user-storage-ldap-mapper-detail.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + provider : function(ComponentLoader) { + return ComponentLoader(); + }, + mapperTypes : function(SubComponentTypesLoader, $route) { + return SubComponentTypesLoader.loadComponents($route.current.params.componentId, 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'); + }, + mapper : function(LDAPMapperLoader) { + return LDAPMapperLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'LDAPMapperCtrl' + }) + .when('/realms/:realm/user-federation', { + templateUrl : resourceUrl + '/partials/user-federation.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'UserFederationCtrl' + }) + .when('/realms/:realm/defense/headers', { + templateUrl : resourceUrl + '/partials/defense-headers.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + + }, + controller : 'DefenseHeadersCtrl' + }) + .when('/realms/:realm/defense/brute-force', { + templateUrl : resourceUrl + '/partials/brute-force.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RealmBruteForceCtrl' + }) + .when('/realms/:realm/protocols', { + templateUrl : resourceUrl + '/partials/protocol-list.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + + }, + controller : 'ProtocolListCtrl' + }) + .when('/realms/:realm/authentication/flows', { + templateUrl : resourceUrl + '/partials/authentication-flows.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + flows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + }, + selectedFlow : function() { + return null; + } + }, + controller : 'AuthenticationFlowsCtrl' + }) + .when('/realms/:realm/authentication/flow-bindings', { + templateUrl : resourceUrl + '/partials/authentication-flow-bindings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + flows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmFlowBindingCtrl' + }) + .when('/realms/:realm/authentication/flows/:flow', { + templateUrl : resourceUrl + '/partials/authentication-flows.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + flows : function(AuthenticationFlowsLoader) { + return AuthenticationFlowsLoader(); + }, + selectedFlow : function($route) { + return $route.current.params.flow; + } + }, + controller : 'AuthenticationFlowsCtrl' + }) + .when('/realms/:realm/authentication/flows/:flow/create/execution/:topFlow', { + templateUrl : resourceUrl + '/partials/create-execution.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + topFlow: function($route) { + return $route.current.params.topFlow; + }, + parentFlow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, + formActionProviders : function(AuthenticationFormActionProvidersLoader) { + return AuthenticationFormActionProvidersLoader(); + }, + authenticatorProviders : function(AuthenticatorProvidersLoader) { + return AuthenticatorProvidersLoader(); + }, + clientAuthenticatorProviders : function(ClientAuthenticatorProvidersLoader) { + return ClientAuthenticatorProvidersLoader(); + } + }, + controller : 'CreateExecutionCtrl' + }) + .when('/realms/:realm/authentication/flows/:flow/create/flow/execution/:topFlow', { + templateUrl : resourceUrl + '/partials/create-flow-execution.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + topFlow: function($route) { + return $route.current.params.topFlow; + }, + parentFlow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, + formProviders : function(AuthenticationFormProvidersLoader) { + return AuthenticationFormProvidersLoader(); + } + }, + controller : 'CreateExecutionFlowCtrl' + }) + .when('/realms/:realm/authentication/create/flow', { + templateUrl : resourceUrl + '/partials/create-flow.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'CreateFlowCtrl' + }) + .when('/realms/:realm/authentication/required-actions', { + templateUrl : resourceUrl + '/partials/required-actions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + unregisteredRequiredActions : function(UnregisteredRequiredActionsListLoader) { + return UnregisteredRequiredActionsListLoader(); + } + }, + controller : 'RequiredActionsCtrl' + }) + .when('/realms/:realm/authentication/password-policy', { + templateUrl : resourceUrl + '/partials/password-policy.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmPasswordPolicyCtrl' + }) + .when('/realms/:realm/authentication/otp-policy', { + templateUrl : resourceUrl + '/partials/otp-policy.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmOtpPolicyCtrl' + }) + .when('/realms/:realm/authentication/webauthn-policy', { + templateUrl : resourceUrl + '/partials/webauthn-policy.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmWebAuthnPolicyCtrl' + }) + .when('/realms/:realm/authentication/webauthn-policy-passwordless', { + templateUrl : resourceUrl + '/partials/webauthn-policy-passwordless.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmWebAuthnPasswordlessPolicyCtrl' + }) + .when('/realms/:realm/authentication/ciba-policy', { + templateUrl : resourceUrl + '/partials/ciba-policy.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'RealmCibaPolicyCtrl' + }) + .when('/realms/:realm/authentication/flows/:flow/config/:provider/:config', { + templateUrl : resourceUrl + '/partials/authenticator-config.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + flow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, + configType : function(AuthenticationConfigDescriptionLoader) { + return AuthenticationConfigDescriptionLoader(); + }, + config : function(AuthenticationConfigLoader) { + return AuthenticationConfigLoader(); + } + }, + controller : 'AuthenticationConfigCtrl' + }) + .when('/create/authentication/:realm/flows/:flow/execution/:executionId/provider/:provider', { + templateUrl : resourceUrl + '/partials/authenticator-config.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + flow : function(AuthenticationFlowLoader) { + return AuthenticationFlowLoader(); + }, + configType : function(AuthenticationConfigDescriptionLoader) { + return AuthenticationConfigDescriptionLoader(); + }, + execution : function(ExecutionIdLoader) { + return ExecutionIdLoader(); + } + }, + controller : 'AuthenticationConfigCreateCtrl' + }) + .when('/create/localization/:realm/:locale', { + templateUrl : resourceUrl + '/partials/realm-localization-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + locale: function($route) { + return $route.current.params.locale; + }, + key: function() { + return null + }, + localizationText : function() { + return null; + } + }, + controller : 'RealmLocalizationDetailCtrl' + }) + .when('/realms/:realm/localization/:locale/:key', { + templateUrl : resourceUrl + '/partials/realm-localization-detail.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + locale: function($route) { + return $route.current.params.locale; + }, + key: function($route) { + return $route.current.params.key; + }, + localizationText : function(RealmSpecificlocalizationTextLoader) { + return RealmSpecificlocalizationTextLoader(); + } + }, + controller : 'RealmLocalizationDetailCtrl' + }) + .when('/server-info', { + templateUrl : resourceUrl + '/partials/server-info.html', + resolve : { + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ServerInfoCtrl' + }) + .when('/server-info/providers', { + templateUrl : resourceUrl + '/partials/server-info-providers.html', + resolve : { + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller : 'ServerInfoCtrl' + }) + .when('/logout', { + templateUrl : resourceUrl + '/partials/home.html', + controller : 'LogoutCtrl' + }) + .when('/notfound', { + templateUrl : resourceUrl + '/partials/notfound.html' + }) + .when('/forbidden', { + templateUrl : resourceUrl + '/partials/forbidden.html' + }) + .otherwise({ + templateUrl : resourceUrl + '/partials/pagenotfound.html' + }); +} ]); + +module.config(function($httpProvider) { + $httpProvider.interceptors.push('errorInterceptor'); + + var spinnerFunction = function(data, headersGetter) { + if (resourceRequests == 0) { + loadingTimer = window.setTimeout(function() { + $('#loading').show(); + loadingTimer = -1; + }, 500); + } + resourceRequests++; + return data; + }; + $httpProvider.defaults.transformRequest.push(spinnerFunction); + + $httpProvider.interceptors.push('spinnerInterceptor'); + $httpProvider.interceptors.push('authInterceptor'); + +}); + +module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location) { + return { + response: function(response) { + resourceRequests--; + if (resourceRequests == 0) { + if(loadingTimer != -1) { + window.clearTimeout(loadingTimer); + loadingTimer = -1; + } + $('#loading').hide(); + } + return response; + }, + responseError: function(response) { + resourceRequests--; + if (resourceRequests == 0) { + if(loadingTimer != -1) { + window.clearTimeout(loadingTimer); + loadingTimer = -1; + } + $('#loading').hide(); + } + + return $q.reject(response); + } + }; +}); + +module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Notifications, Auth) { + return { + response: function(response) { + return response; + }, + responseError: function(response) { + if (response.status == 401) { + Auth.authz.logout(); + } else if (response.status == 403) { + $location.path('/forbidden'); + } else if (response.status == 404) { + $location.path('/notfound'); + } else if (response.status) { + if (response.data && response.data.errorMessage) { + Notifications.error(response.data.errorMessage); + } else if (response.data && response.data.error_description) { + Notifications.error(response.data.error_description); + } else { + Notifications.error("An unexpected server error has occurred"); + } + } else { + Notifications.error("No response from server."); + } + return $q.reject(response); + } + }; +}); + +// collapsable form fieldsets +module.directive('collapsable', function() { + return function(scope, element, attrs) { + element.click(function() { + $(this).toggleClass('collapsed'); + $(this).find('.toggle-icons').toggleClass('kc-icon-collapse').toggleClass('kc-icon-expand'); + $(this).find('.toggle-icons').text($(this).text() == "Icon: expand" ? "Icon: collapse" : "Icon: expand"); + $(this).parent().find('.form-group').toggleClass('hidden'); + }); + } +}); + +// collapsable form fieldsets +module.directive('uncollapsed', function() { + return function(scope, element, attrs) { + element.prepend(' '); + element.click(function() { + $(this).find('.toggle-class').toggleClass('fa-angle-down').toggleClass('fa-angle-right'); + $(this).parent().find('.form-group').toggleClass('hidden'); + }); + } +}); + +// collapsable form fieldsets +module.directive('collapsed', function() { + return function(scope, element, attrs) { + element.prepend(' '); + element.parent().find('.form-group').toggleClass('hidden'); + element.click(function() { + $(this).find('.toggle-class').toggleClass('fa-angle-down').toggleClass('fa-angle-right'); + $(this).parent().find('.form-group').toggleClass('hidden'); + }); + } +}); + +/** + * Directive for presenting an ON-OFF switch for checkbox. + * Usage: + */ +module.directive('onoffswitch', function() { + return { + restrict: "EA", + replace: true, + scope: { + name: '@', + id: '@', + ngModel: '=', + ngDisabled: '=', + kcOnText: '@onText', + kcOffText: '@offText' + }, + // TODO - The same code acts differently when put into the templateURL. Find why and move the code there. + //templateUrl: "templates/kc-switch.html", + template: "
", + compile: function(element, attrs) { + /* + We don't want to propagate basic attributes to the root element of directive. Id should be passed to the + input element only to achieve proper label binding (and validity). + */ + element.removeAttr('name'); + element.removeAttr('id'); + + if (!attrs.onText) { attrs.onText = "ON"; } + if (!attrs.offText) { attrs.offText = "OFF"; } + + element.bind('keydown', function(e){ + var code = e.keyCode || e.which; + if (code === 32 || code === 13) { + e.stopImmediatePropagation(); + e.preventDefault(); + $(e.target).find('input').click(); + } + }); + } + } +}); + +/** + * Directive for presenting an ON-OFF switch for checkbox. The directive expects the value to be string 'true' or 'false', not boolean true/false + * This directive provides some additional capabilities to the default onoffswitch such as: + * + * - Dynamic values for id and name attributes. Useful if you need to use this directive inside a ng-repeat + * - Specific scope to specify the value. Instead of just true or false. + * + * Usage: + */ +module.directive('onoffswitchstring', function() { + return { + restrict: "EA", + replace: true, + scope: { + name: '=', + id: '=', + value: '=', + ngModel: '=', + ngDisabled: '=', + kcOnText: '@onText', + kcOffText: '@offText' + }, + // TODO - The same code acts differently when put into the templateURL. Find why and move the code there. + //templateUrl: "templates/kc-switch.html", + template: '
', + compile: function(element, attrs) { + + if (!attrs.onText) { attrs.onText = "ON"; } + if (!attrs.offText) { attrs.offText = "OFF"; } + + element.bind('keydown click', function(e){ + var code = e.keyCode || e.which; + if (code === 32 || code === 13) { + e.stopImmediatePropagation(); + e.preventDefault(); + $(e.target).find('input').click(); + } + }); + } + } +}); + +/** + * Directive for presenting an ON-OFF switch for checkbox. The directive expects the true-value or false-value to be string like 'true' or 'false', not boolean true/false. + * This directive provides some additional capabilities to the default onoffswitch such as: + * + * - Specific scope to specify the value. Instead of just 'true' or 'false' you can use any other values. For example: true-value="'foo'" false-value="'bar'" . + * But 'true'/'false' are defaults if true-value and false-value are not specified + * + * Usage: + */ +module.directive('onoffswitchvalue', function() { + return { + restrict: "EA", + replace: true, + scope: { + name: '@', + id: '@', + trueValue: '@', + falseValue: '@', + ngModel: '=', + ngDisabled: '=', + kcOnText: '@onText', + kcOffText: '@offText' + }, + // TODO - The same code acts differently when put into the templateURL. Find why and move the code there. + //templateUrl: "templates/kc-switch.html", + template: "
", + compile: function(element, attrs) { + /* + We don't want to propagate basic attributes to the root element of directive. Id should be passed to the + input element only to achieve proper label binding (and validity). + */ + element.removeAttr('name'); + element.removeAttr('id'); + + if (!attrs.trueValue) { attrs.trueValue = "'true'"; } + if (!attrs.falseValue) { attrs.falseValue = "'false'"; } + + if (!attrs.onText) { attrs.onText = "ON"; } + if (!attrs.offText) { attrs.offText = "OFF"; } + + element.bind('keydown', function(e){ + var code = e.keyCode || e.which; + if (code === 32 || code === 13) { + e.stopImmediatePropagation(); + e.preventDefault(); + $(e.target).find('input').click(); + } + }); + } + } +}); + +module.directive('kcInput', function() { + var d = { + scope : true, + replace : false, + link : function(scope, element, attrs) { + var form = element.children('form'); + var label = element.children('label'); + var input = element.children('input'); + + var id = form.attr('name') + '.' + input.attr('name'); + + element.attr('class', 'control-group'); + + label.attr('class', 'control-label'); + label.attr('for', id); + + input.wrap('
'); + input.attr('id', id); + + if (!input.attr('placeHolder')) { + input.attr('placeHolder', label.text()); + } + + if (input.attr('required')) { + label.append(' *'); + } + } + }; + return d; +}); + +module.directive('kcEnter', function() { + return function(scope, element, attrs) { + element.bind("keydown keypress", function(event) { + if (event.which === 13) { + scope.$apply(function() { + scope.$eval(attrs.kcEnter); + }); + + event.preventDefault(); + } + }); + }; +}); + +// Don't allow URI reserved characters +module.directive('kcNoReservedChars', function (Notifications, $translate) { + return function($scope, element) { + element.bind("keypress", function(event) { + var keyPressed = String.fromCharCode(event.which || event.keyCode || 0); + + // ] and ' can not be used inside a character set on POSIX and GNU + if (keyPressed.match('[:/?#[@!$&()*+,;=]') || keyPressed === ']' || keyPressed === '\'') { + event.preventDefault(); + $scope.$apply(function() { + Notifications.warn($translate.instant('key-not-allowed-here', {character: keyPressed})); + }); + } + }); + }; +}); + +module.directive('kcSave', function ($compile, $timeout, Notifications) { + var clickDelay = 500; // 500 ms + + return { + restrict: 'A', + link: function ($scope, elem, attr, ctrl) { + elem.addClass("btn btn-primary"); + elem.attr("type","submit"); + + var disabled = false; + elem.on('click', function(evt) { + if ($scope.hasOwnProperty("changed") && !$scope.changed) return; + + // KEYCLOAK-4121: Prevent double form submission + if (disabled) { + evt.preventDefault(); + evt.stopImmediatePropagation(); + return; + } else { + disabled = true; + $timeout(function () { disabled = false; }, clickDelay, false); + } + + $scope.$apply(function() { + var form = elem.closest('form'); + if (form && form.attr('name')) { + var ngValid = form.find('.ng-valid'); + if ($scope[form.attr('name')].$valid) { + //ngValid.removeClass('error'); + ngValid.parent().removeClass('has-error'); + $scope['save'](); + } else { + Notifications.error("Missing or invalid field(s). Please verify the fields in red.") + //ngValid.removeClass('error'); + ngValid.parent().removeClass('has-error'); + + var ngInvalid = form.find('.ng-invalid'); + //ngInvalid.addClass('error'); + ngInvalid.parent().addClass('has-error'); + } + } + }); + }) + } + } +}); + +module.directive('kcReset', function ($compile, Notifications) { + return { + restrict: 'A', + link: function ($scope, elem, attr, ctrl) { + elem.addClass("btn btn-default"); + elem.attr("type","submit"); + elem.bind('click', function() { + $scope.$apply(function() { + var form = elem.closest('form'); + if (form && form.attr('name')) { + form.find('.ng-valid').removeClass('error'); + form.find('.ng-invalid').removeClass('error'); + $scope['reset'](); + } + }) + }) + } + } +}); + +module.directive('kcCancel', function ($compile, Notifications) { + return { + restrict: 'A', + link: function ($scope, elem, attr, ctrl) { + elem.addClass("btn btn-default"); + elem.attr("type","submit"); + } + } +}); + +module.directive('kcDelete', function ($compile, Notifications) { + return { + restrict: 'A', + link: function ($scope, elem, attr, ctrl) { + elem.addClass("btn btn-danger"); + elem.attr("type","submit"); + } + } +}); + + +module.directive('kcDropdown', function ($compile, Notifications) { + return { + scope: { + kcOptions: '=', + kcModel: '=', + id: "=", + kcPlaceholder: '@' + }, + restrict: 'EA', + replace: true, + templateUrl: resourceUrl + '/templates/kc-select.html', + link: function(scope, element, attr) { + scope.updateModel = function(item) { + scope.kcModel = item; + }; + } + } +}); + +module.directive('kcReadOnly', function() { + var disabled = {}; + + var d = { + replace : false, + link : function(scope, element, attrs) { + var disable = function(i, e) { + if (!e.disabled) { + disabled[e.tagName + i] = true; + e.disabled = true; + } + } + + var enable = function(i, e) { + if (disabled[e.tagName + i]) { + e.disabled = false; + delete disabled[i]; + } + } + + var filterIgnored = function(i, e){ + return !e.attributes['kc-read-only-ignore']; + } + + scope.$watch(attrs.kcReadOnly, function(readOnly) { + if (readOnly) { + element.find('input').filter(filterIgnored).each(disable); + element.find('button').filter(filterIgnored).each(disable); + element.find('select').filter(filterIgnored).each(disable); + element.find('textarea').filter(filterIgnored).each(disable); + } else { + element.find('input').filter(filterIgnored).each(enable); + element.find('input').filter(filterIgnored).each(enable); + element.find('button').filter(filterIgnored).each(enable); + element.find('select').filter(filterIgnored).each(enable); + element.find('textarea').filter(filterIgnored).each(enable); + } + }); + } + }; + return d; +}); + +module.directive('kcMenu', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-menu.html' + } +}); + +module.directive('kcTabsRealm', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-realm.html' + } +}); + +module.directive('kcTabsAuthentication', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-authentication.html' + } +}); + +module.directive('kcTabsRole', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-role.html' + } +}); + +module.directive('kcTabsClientRole', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-client-role.html' + } +}); + +module.directive('kcTabsUser', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-user.html' + } +}); + +module.directive('kcTabsUsers', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-users.html' + } +}); + +module.directive('kcTabsClients', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-clients.html' + } +}); + +module.directive('kcTabsGroup', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-group.html' + } +}); + +module.directive('kcTabsGroupList', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-group-list.html' + } +}); + +module.directive('kcTabsClient', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-client.html' + } +}); + +module.directive('kcTabsClientScope', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-client-scope.html' + } +}); + +module.directive('kcNavigationUser', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-navigation-user.html' + } +}); + +module.directive('kcTabsIdentityProvider', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-identity-provider.html' + } +}); + +module.directive('kcTabsUserFederation', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-user-federation.html' + } +}); + +module.directive('kcTabsLdap', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-ldap.html' + } +}); + +module.controller('RoleSelectorModalCtrl', function($scope, realm, config, configName, RealmRoles, Client, ClientRole, $modalInstance) { + $scope.selectedRealmRole = { + role: undefined + }; + $scope.selectedClientRole = { + role: undefined + }; + $scope.client = { + selected: undefined + }; + + $scope.selectRealmRole = function() { + config[configName] = $scope.selectedRealmRole.role.name; + $modalInstance.close(); + } + + $scope.selectClientRole = function() { + config[configName] = $scope.selectedClient.clientId + "." + $scope.selectedClientRole.role.name; + $modalInstance.close(); + } + + $scope.cancel = function() { + $modalInstance.dismiss(); + } + + clientSelectControl($scope, realm.realm, Client); + + $scope.selectedClient = null; + + $scope.changeClient = function(client) { + $scope.selectedClient = client; + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } + if ($scope.selectedClient) { + ClientRole.query({realm: realm.realm, client: $scope.selectedClient.id}, function (data) { + $scope.clientRoles = data; + }); + } else { + console.log('selected client was null'); + $scope.clientRoles = null; + } + + $scope.selectedClient = client; + } + + RealmRoles.query({realm: realm.realm}, function(data) { + $scope.realmRoles = data; + }) +}); + +module.controller('ProviderConfigCtrl', function ($modal, $scope, $route, ComponentUtils, Client) { + clientSelectControl($scope, $route.current.params.realm, Client); + $scope.fileNames = {}; + $scope.newMapEntries = {}; + var cachedMaps = {}; + var cachedParsedMaps = {}; + var focusMapValueId = null; + + // KEYCLOAK-4463 + $scope.initEditor = function(editor){ + editor.$blockScrolling = Infinity; // suppress warning message + }; + + $scope.initSelectedClient = function(configName, config) { + if(config[configName]) { + $scope.selectedClient = null; + Client.query({realm: $route.current.params.realm, search: false, clientId: config[configName], max: 1}, function(data) { + if(data.length > 0) { + $scope.selectedClient = angular.copy(data[0]); + $scope.selectedClient.text = $scope.selectedClient.clientId; + } + }); + } + } + + $scope.openRoleSelector = function (configName, config) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/role-selector.html', + controller: 'RoleSelectorModalCtrl', + resolve: { + realm: function () { + return $scope.realm; + }, + config: function () { + return config; + }, + configName: function () { + return configName; + } + } + }) + } + + $scope.changeClient = function(configName, config, client, multivalued) { + if (!client || !client.id) { + config[configName] = null; + $scope.selectedClient = null; + return; + } + $scope.selectedClient = client; + if (multivalued) { + config[configName][0] = client.clientId; + } else { + config[configName] = client.clientId; + } + }; + + ComponentUtils.convertAllMultivaluedStringValuesToList($scope.properties, $scope.config); + + ComponentUtils.addLastEmptyValueToMultivaluedLists($scope.properties, $scope.config); + + $scope.addValueToMultivalued = function(optionName) { + var configProperty = $scope.config[optionName]; + var lastIndex = configProperty.length - 1; + var lastValue = configProperty[lastIndex]; + console.log("Option=" + optionName + ", lastIndex=" + lastIndex + ", lastValue=" + lastValue); + + if (lastValue.length > 0) { + configProperty.push(''); + } + } + + $scope.deleteValueFromMultivalued = function(optionName, index) { + $scope.config[optionName].splice(index, 1); + } + + $scope.uploadFile = function($files, optionName, config) { + var reader = new FileReader(); + reader.onload = function(e) { + $scope.$apply(function() { + config[optionName][0] = e.target.result; + }); + }; + reader.readAsText($files[0]); + $scope.fileNames[optionName] = $files[0].name; + } + + $scope.addMapEntry = function(optionName) { + $scope.removeMapEntry(optionName, $scope.newMapEntries[optionName].key) + + var parsedMap = JSON.parse($scope.config[optionName]); + parsedMap.push($scope.newMapEntries[optionName]); + $scope.config[optionName] = JSON.stringify(parsedMap); + + delete $scope.newMapEntries[optionName]; + } + + $scope.removeMapEntry = function(optionName, key) { + var parsedMap = JSON.parse($scope.config[optionName]); + + for(var i = parsedMap.length - 1; i >= 0; i--) { + if(parsedMap[i]['key'] === key) { + parsedMap.splice(i, 1); + } + } + + $scope.config[optionName] = JSON.stringify(parsedMap); + } + + $scope.updateMapEntry = function(optionName, key, value) { + var parsedMap = JSON.parse($scope.config[optionName]); + + for(var i = parsedMap.length - 1; i >= 0; i--) { + if(parsedMap[i]['key'] === key) { + parsedMap[i]['value'] = value; + } + } + $scope.config[optionName] = JSON.stringify(parsedMap); + + focusMapValueId = "mapValue-" + optionName + "-" + key; + } + + $scope.jsonParseMap = function(optionName) { + + if(cachedParsedMaps[optionName] === undefined) { + cachedMaps[optionName] = "[]"; + cachedParsedMaps[optionName] = []; + + if(!$scope.config.hasOwnProperty(optionName)){ + $scope.config[optionName]=cachedMaps[optionName]; + } else { + cachedMaps[optionName] = $scope.config[optionName]; + cachedParsedMaps[optionName] = JSON.parse(cachedMaps[optionName]); + } + } + + var mapChanged = $scope.config[optionName] !== cachedMaps[optionName]; + + if(mapChanged){ + cachedMaps[optionName] = $scope.config[optionName]; + cachedParsedMaps[optionName] = JSON.parse(cachedMaps[optionName]); + } + + if(!mapChanged && focusMapValueId !== null){ + document.getElementById(focusMapValueId).focus(); + focusMapValueId = null; + } + + return cachedParsedMaps[optionName]; + } +}); + +module.directive('kcProviderConfig', function ($modal) { + return { + scope: { + config: '=', + properties: '=', + realm: '=', + clients: '=', + configName: '=' + }, + restrict: 'E', + replace: true, + controller: 'ProviderConfigCtrl', + templateUrl: resourceUrl + '/templates/kc-provider-config.html' + } +}); + +module.controller('ComponentRoleSelectorModalCtrl', function($scope, realm, config, configName, RealmRoles, Client, ClientRole, $modalInstance) { + $scope.selectedRealmRole = { + role: undefined + }; + $scope.selectedClientRole = { + role: undefined + }; + $scope.client = { + selected: undefined + }; + + $scope.selectRealmRole = function() { + config[configName][0] = $scope.selectedRealmRole.role.name; + $modalInstance.close(); + } + + $scope.selectClientRole = function() { + config[configName][0] = $scope.client.selected.clientId + "." + $scope.selectedClientRole.role.name; + $modalInstance.close(); + } + + $scope.cancel = function() { + $modalInstance.dismiss(); + } + + $scope.changeClient = function() { + if ($scope.client.selected) { + ClientRole.query({realm: realm.realm, client: $scope.client.selected.id}, function (data) { + $scope.clientRoles = data; + }); + } else { + console.log('selected client was null'); + $scope.clientRoles = null; + } + + } + RealmRoles.query({realm: realm.realm}, function(data) { + $scope.realmRoles = data; + }) + Client.query({realm: realm.realm}, function(data) { + $scope.clients = data; + if (data.length > 0) { + $scope.client.selected = data[0]; + $scope.changeClient(); + } + }) +}); + +module.controller('ComponentConfigCtrl', function ($modal, $scope, $route, Client) { + + $scope.initSelectedClient = function(configName, config) { + if(config[configName]) { + $scope.selectedClient = null; + Client.query({realm: $route.current.params.realm, search: false, clientId: config[configName], max: 1}, function(data) { + if(data.length > 0) { + $scope.selectedClient = angular.copy(data[0]); + $scope.selectedClient.text = $scope.selectedClient.clientId; + } + }); + } + } + + $scope.changeClient = function(configName, config, client) { + if (!client || !client.id) { + config[configName] = null; + $scope.selectedClient = null; + return; + } + $scope.selectedClient = client; + config[configName] = client.clientId; + }; + + + $scope.openRoleSelector = function (configName, config) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/component-role-selector.html', + controller: 'ComponentRoleSelectorModalCtrl', + resolve: { + realm: function () { + return $scope.realm; + }, + config: function () { + return config; + }, + configName: function () { + return configName; + } + } + }) + } +}); +module.directive('kcComponentConfig', function ($modal) { + return { + scope: { + config: '=', + properties: '=', + realm: '=', + clients: '=', + configName: '=' + }, + restrict: 'E', + replace: true, + controller: 'ComponentConfigCtrl', + templateUrl: resourceUrl + '/templates/kc-component-config.html' + } +}); + +/* +* Used to select the element (invoke $(elem).select()) on specified action list. +* Usages kc-select-action="click mouseover" +* When used in the textarea element, this will select/highlight the textarea content on specified action (i.e. click). +*/ +module.directive('kcSelectAction', function ($compile, Notifications) { + return { + restrict: 'A', + compile: function (elem, attrs) { + + var events = attrs.kcSelectAction.split(" "); + + for(var i=0; i < events.length; i++){ + + elem.bind(events[i], function(){ + elem.select(); + }); + } + } + } +}); + +module.filter('remove', function() { + return function(input, remove, attribute) { + if (!input || !remove) { + return input; + } + + var out = []; + for ( var i = 0; i < input.length; i++) { + var e = input[i]; + + if (Array.isArray(remove)) { + for (var j = 0; j < remove.length; j++) { + if (attribute) { + if (remove[j][attribute] == e[attribute]) { + e = null; + break; + } + } else { + if (remove[j] == e) { + e = null; + break; + } + } + } + } else { + if (attribute) { + if (remove[attribute] == e[attribute]) { + e = null; + } + } else { + if (remove == e) { + e = null; + } + } + } + + if (e != null) { + out.push(e); + } + } + + return out; + }; +}); + +module.filter('capitalize', function() { + return function(input) { + if (!input) { + return; + } + var splittedWords = input.split(/\s+/); + for (var i=0; i'); + + $compile(label)(scope); + } + }; +}); + +module.directive( 'kcOpen', function ( $location ) { + return function ( scope, element, attrs ) { + var path; + + attrs.$observe( 'kcOpen', function (val) { + path = val; + }); + + element.bind( 'click', function () { + scope.$apply( function () { + $location.path(path); + }); + }); + }; +}); + +module.directive('kcOnReadFile', function ($parse) { + console.debug('kcOnReadFile'); + return { + restrict: 'A', + scope: false, + link: function(scope, element, attrs) { + var fn = $parse(attrs.kcOnReadFile); + + element.on('change', function(onChangeEvent) { + var reader = new FileReader(); + + reader.onload = function(onLoadEvent) { + scope.$apply(function() { + fn(scope, {$fileContent:onLoadEvent.target.result}); + }); + }; + + reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]); + }); + } + }; +}); + +module.controller('PagingCtrl', function ($scope) { + $scope.currentPageInput = 1; + + $scope.firstPage = function() { + if (!$scope.hasPrevious()) return; + $scope.currentPage = 1; + $scope.currentPageInput = 1; + }; + + $scope.lastPage = function() { + if (!$scope.hasNext()) return; + $scope.currentPage = $scope.numberOfPages; + $scope.currentPageInput = $scope.numberOfPages; + }; + + $scope.previousPage = function() { + if (!$scope.hasPrevious()) return; + $scope.currentPage--; + $scope.currentPageInput = $scope.currentPage; + }; + + $scope.nextPage = function() { + if (!$scope.hasNext()) return; + $scope.currentPage++; + $scope.currentPageInput = $scope.currentPage; + }; + + $scope.hasNext = function() { + return $scope.currentPage < $scope.numberOfPages; + }; + + $scope.hasPrevious = function() { + return $scope.currentPage > 1; + }; +}); + +// Provides a component for injection with utility methods for manipulating strings +module.factory('KcStrings', function () { + var instance = {}; + + // some IE versions do not support string.endsWith method, this method should be used as an alternative for cross-browser compatibility + instance.endsWith = function(source, suffix) { + return source.indexOf(suffix, source.length - suffix.length) !== -1; + }; + + return instance; +}); + +module.directive('kcPaging', function () { + return { + scope: { + currentPage: '=', + currentPageInput: '=', + numberOfPages: '=' + }, + restrict: 'E', + replace: true, + controller: 'PagingCtrl', + templateUrl: resourceUrl + '/templates/kc-paging.html' + } +}); + +// Tests the page number input from currentPageInput to see +// if it represents a valid page. If so, the current page is changed. +module.directive('kcValidPage', function() { + return { + require: 'ngModel', + link: function(scope, element, attrs, ctrl) { + ctrl.$validators.inRange = function(modelValue, viewValue) { + if (viewValue >= 1 && viewValue <= scope.numberOfPages) { + scope.currentPage = viewValue; + } + + return true; + } + } + } +}); + +// Directive to parse/format strings into numbers +module.directive('stringToNumber', function() { + return { + require: 'ngModel', + link: function(scope, element, attrs, ngModel) { + ngModel.$parsers.push(function(value) { + return (typeof value === 'undefined' || value === null)? '' : '' + value; + }); + ngModel.$formatters.push(function(value) { + return parseFloat(value); + }); + } + }; +}); + +// filter used for paged tables +module.filter('startFrom', function () { + return function (input, start) { + if (input) { + start = +start; + return input.slice(start); + } + return []; + }; +}); + + +module.directive('kcPassword', function ($compile, Notifications) { + return { + restrict: 'A', + link: function ($scope, elem, attr, ctrl) { + function toggleMask(evt) { + if(elem.hasClass('password-conceal')) { + view(); + } else { + conceal(); + } + } + + function view() { + elem.removeClass('password-conceal'); + + var t = elem.next().children().first(); + t.addClass('fa-eye-slash'); + t.removeClass('fa-eye'); + } + + function conceal() { + elem.addClass('password-conceal'); + + var t = elem.next().children().first(); + t.removeClass('fa-eye-slash'); + t.addClass('fa-eye'); + } + + elem.addClass("password-conceal"); + elem.attr("type","text"); + elem.attr("autocomplete", "off"); + + var p = elem.parent(); + + var inputGroup = $('
'); + var eye = $('') + .on('click', toggleMask); + + $scope.$watch(attr.ngModel, function(v) { + if (v && v == '**********') { + elem.next().addClass('disabled') + } else if (v && v.indexOf('${v') == 0) { + elem.next().addClass('disabled') + view(); + } else { + elem.next().removeClass('disabled') + } + }) + + elem.detach().appendTo(inputGroup); + inputGroup.append(eye); + p.append(inputGroup); + } + } +}); + + +module.filter('resolveClientRootUrl', function() { + return function(input) { + if (!input) { + return; + } + return input.replace("${authBaseUrl}", authServerUrl).replace("${authAdminUrl}", authUrl); + }; +}); diff --git a/base/admin/resources/js/authz/authz-app.js b/base/admin/resources/js/authz/authz-app.js new file mode 100644 index 0000000..5c4e29b --- /dev/null +++ b/base/admin/resources/js/authz/authz-app.js @@ -0,0 +1,549 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2016 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.requires.push('ui.ace'); + +module.config(['$routeProvider', function ($routeProvider) { + $routeProvider + .when('/realms/:realm/authz', { + templateUrl: resourceUrl + '/partials/authz/resource-server-list.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + } + }, + controller: 'ResourceServerCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/create', { + templateUrl: resourceUrl + '/partials/authz/resource-server-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients: function (ClientListLoader) { + return ClientListLoader(); + } + }, + controller: 'ResourceServerDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server', { + templateUrl: resourceUrl + '/partials/authz/resource-server-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients: function (ClientListLoader) { + return ClientListLoader(); + }, + serverInfo: function (ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller: 'ResourceServerDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/export-settings', { + templateUrl: resourceUrl + '/partials/authz/resource-server-export-settings.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients: function (ClientListLoader) { + return ClientListLoader(); + }, + serverInfo: function (ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller: 'ResourceServerDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/evaluate', { + templateUrl: resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + clients: function (ClientListLoader) { + return ClientListLoader(); + }, + roles: function (RoleListLoader) { + return new RoleListLoader(); + } + }, + controller: 'PolicyEvaluateCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/evaluate/result', { + templateUrl: resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate-result.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + }, + controller: 'PolicyEvaluateCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/resource', { + templateUrl: resourceUrl + '/partials/authz/resource-server-resource-list.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerResourceCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/resource/create', { + templateUrl: resourceUrl + '/partials/authz/resource-server-resource-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerResourceDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid', { + templateUrl: resourceUrl + '/partials/authz/resource-server-resource-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerResourceDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/scope', { + templateUrl: resourceUrl + '/partials/authz/resource-server-scope-list.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerScopeCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/scope/create', { + templateUrl: resourceUrl + '/partials/authz/resource-server-scope-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerScopeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/scope/:id', { + templateUrl: resourceUrl + '/partials/authz/resource-server-scope-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerScopeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/permission', { + templateUrl: resourceUrl + '/partials/authz/permission/resource-server-permission-list.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPermissionCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy', { + templateUrl: resourceUrl + '/partials/authz/policy/resource-server-policy-list.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/permission/resource/create', { + templateUrl: resourceUrl + '/partials/authz/permission/provider/resource-server-policy-resource-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyResourceDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/permission/resource/:id', { + templateUrl: resourceUrl + '/partials/authz/permission/provider/resource-server-policy-resource-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyResourceDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/permission/scope/create', { + templateUrl: resourceUrl + '/partials/authz/permission/provider/resource-server-policy-scope-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyScopeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/permission/scope/:id', { + templateUrl: resourceUrl + '/partials/authz/permission/provider/resource-server-policy-scope-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyScopeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/user/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-user-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyUserDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/user/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-user-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyUserDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/client/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-client-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyClientDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/client/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-client-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyClientDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/role/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-role-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyRoleDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/role/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-role-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyRoleDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/group/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-group-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyGroupDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/group/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-group-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyGroupDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/js/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-js-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller: 'ResourceServerPolicyJSDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/js/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-js-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + } + }, + controller: 'ResourceServerPolicyJSDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/time/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-time-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyTimeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/time/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-time-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyTimeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/aggregate/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyAggregateDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/aggregate/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyAggregateDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/client-scope/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-client-scope-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyClientScopeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/client-scope/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-client-scope-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyClientScopeDetailCtrl' + }).when('/realms/:realm/roles/:role/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/realm-role-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + } + }, + controller : 'RealmRolePermissionsCtrl' + }).when('/realms/:realm/clients/:client/roles/:role/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/client-role-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + }, + role : function(RoleLoader) { + return RoleLoader(); + } + }, + controller : 'ClientRolePermissionsCtrl' + }).when('/realms/:realm/users-permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/users-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'UsersPermissionsCtrl' + }) + .when('/realms/:realm/clients/:client/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/client-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller : 'ClientPermissionsCtrl' + }) + .when('/realms/:realm/groups/:group/permissions', { + templateUrl : resourceUrl + '/partials/authz/mgmt/group-permissions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupPermissionsCtrl' + }) + .when('/realms/:realm/identity-provider-settings/provider/:provider_id/:alias/permissions', { + templateUrl : function(params){ return resourceUrl + '/partials/authz/mgmt/broker-permissions.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + identityProvider : function(IdentityProviderLoader) { + return IdentityProviderLoader(); + } + }, + controller : 'IdentityProviderPermissionCtrl' + }) + ; +}]); + +module.directive('kcTabsResourceServer', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/authz/kc-tabs-resource-server.html' + } +}); + +module.filter('unique', function () { + + return function (items, filterOn) { + + if (filterOn === false) { + return items; + } + + if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) { + var hashCheck = {}, newItems = []; + + var extractValueToCompare = function (item) { + if (angular.isObject(item) && angular.isString(filterOn)) { + return item[filterOn]; + } else { + return item; + } + }; + + angular.forEach(items, function (item) { + var valueToCheck, isDuplicate = false; + + for (var i = 0; i < newItems.length; i++) { + if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) { + isDuplicate = true; + break; + } + } + if (!isDuplicate) { + newItems.push(item); + } + + }); + items = newItems; + } + return items; + }; +}); + +module.filter('toCamelCase', function () { + return function (input) { + input = input || ''; + return input.replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + }; +}) \ No newline at end of file diff --git a/base/admin/resources/js/authz/authz-controller.js b/base/admin/resources/js/authz/authz-controller.js new file mode 100644 index 0000000..f4c533d --- /dev/null +++ b/base/admin/resources/js/authz/authz-controller.js @@ -0,0 +1,3013 @@ +module.controller('ResourceServerCtrl', function($scope, realm, ResourceServer) { + $scope.realm = realm; + + ResourceServer.query({realm : realm.realm}, function (data) { + $scope.servers = data; + }); +}); + +module.controller('ResourceServerDetailCtrl', function($scope, $http, $route, $location, $upload, $modal, realm, ResourceServer, client, AuthzDialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = angular.copy(data); + $scope.changed = false; + + $scope.$watch('server', function() { + if (!angular.equals($scope.server, data)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + ResourceServer.update({realm : realm.realm, client : $scope.server.clientId}, $scope.server, function() { + $route.reload(); + Notifications.success("The resource server has been created."); + }); + } + + $scope.reset = function() { + $route.reload(); + } + + $scope.export = function() { + $scope.exportSettings = true; + ResourceServer.settings({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + var tmp = angular.fromJson(data); + $scope.settings = angular.toJson(tmp, true); + }) + } + + $scope.downloadSettings = function() { + saveAs(new Blob([$scope.settings], { type: 'application/json' }), $scope.server.name + "-authz-config.json"); + } + + $scope.cancelExport = function() { + delete $scope.settings + } + + $scope.onFileSelect = function($fileContent) { + $scope.server = angular.copy(JSON.parse($fileContent)); + $scope.importing = true; + }; + + $scope.viewImportDetails = function() { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-object.html', + controller: 'ObjectModalCtrl', + resolve: { + object: function () { + return $scope.server; + } + } + }) + }; + + $scope.import = function () { + ResourceServer.import({realm : realm.realm, client : client.id}, $scope.server, function() { + $route.reload(); + Notifications.success("The resource server has been updated."); + }); + } + }); +}); + +var Resources = { + delete: function(ResourceServerResource, realm, client, $scope, AuthzDialog, $location, Notifications, $route) { + ResourceServerResource.permissions({ + realm : realm, + client : client.id, + rsrid : $scope.resource._id + }, function (permissions) { + var msg = ""; + + if (permissions.length > 0 && !$scope.deleteConsent) { + msg = "

This resource is referenced in some permissions:

"; + msg += "
    "; + for (i = 0; i < permissions.length; i++) { + msg+= "
  • " + permissions[i].name + "
  • "; + } + msg += "
"; + msg += "

If you remove this resource, the permissions above will be affected and will not be associated with this resource anymore.

"; + } + + AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() { + ResourceServerResource.delete({realm : realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() { + $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource"); + $route.reload(); + Notifications.success("The resource has been deleted."); + }); + }); + }); + } +} + +var Policies = { + delete: function(service, realm, client, $scope, AuthzDialog, $location, Notifications, $route, isPermission) { + var msg = ""; + + service.dependentPolicies({ + realm : realm, + client : client.id, + id : $scope.policy.id + }, function (dependentPolicies) { + if (dependentPolicies.length > 0 && !$scope.deleteConsent) { + msg = "

This policy is being used by other policies:

"; + msg += "
    "; + for (i = 0; i < dependentPolicies.length; i++) { + msg+= "
  • " + dependentPolicies[i].name + "
  • "; + } + msg += "
"; + msg += "

If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.

"; + } + + AuthzDialog.confirmDeleteWithMsg($scope.policy.name, isPermission ? "Permission" : "Policy", msg, function() { + service.delete({realm : realm, client : $scope.client.id, id : $scope.policy.id}, null, function() { + if (isPermission) { + $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/permission"); + Notifications.success("The permission has been deleted."); + } else { + $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/policy"); + Notifications.success("The policy has been deleted."); + } + $route.reload(); + }); + }); + }); + } +} + +module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerResource, client, AuthzDialog, Notifications, viewState) { + $scope.realm = realm; + $scope.client = client; + + $scope.query = { + realm: realm.realm, + client : client.id, + deep: false, + max : 20, + first : 0 + }; + + $scope.listSizes = [5, 10, 20]; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + + $scope.createPolicy = function(resource) { + viewState.state = {}; + viewState.state.previousUrl = '/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/resource'; + $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/resource/create').search({rsrid: resource._id}); + } + + $scope.searchQuery(); + }); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerResource.query($scope.query, function(response) { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + $scope.resources = response; + if ($scope.detailsFilter) { + $scope.showDetails(); + } + }); + }; + + $scope.loadDetails = function (resource) { + if (resource.details) { + resource.details.loaded = !resource.details.loaded; + return; + } + + resource.details = {loaded: false}; + + ResourceServerResource.scopes({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resource._id + }, function(response) { + resource.scopes = response; + ResourceServerResource.permissions({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resource._id + }, function(response) { + resource.policies = response; + resource.details.loaded = true; + }); + }); + } + + $scope.showDetails = function(item, event) { + if (event.target.localName == 'a' || event.target.localName == 'button') { + return; + } + + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.resources.length; i++) { + $scope.loadDetails($scope.resources[i]); + } + } + }; + + $scope.delete = function(resource) { + $scope.resource = resource; + Resources.delete(ResourceServerResource, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route); + }; +}); + +module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerResource, ResourceServerScope, AuthzDialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + return object.name; + }, + formatSelection: function(object, container, query) { + return object.name; + } + }; + + var $instance = this; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + + var resourceId = $route.current.params.rsrid; + + if (!resourceId) { + $scope.create = true; + $scope.changed = false; + + var resource = {}; + resource.scopes = []; + resource.attributes = {}; + resource.uris = []; + + $scope.resource = angular.copy(resource); + + $scope.$watch('resource', function() { + if (!angular.equals($scope.resource, resource)) { + $scope.changed = true; + } + }, true); + + $scope.$watch('newUri', function() { + if ($scope.newUri && $scope.newUri.length > 0) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + if ($scope.newUri && $scope.newUri.length > 0) { + $scope.addUri(); + } + + for (i = 0; i < $scope.resource.scopes.length; i++) { + delete $scope.resource.scopes[i].text; + } + $instance.checkNameAvailability(function () { + ResourceServerResource.save({realm : realm.realm, client : $scope.client.id}, $scope.resource, function(data) { + $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource/" + data._id); + Notifications.success("The resource has been created."); + }); + }); + } + + $scope.reset = function() { + $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource/"); + } + } else { + ResourceServerResource.get({ + realm : $route.current.params.realm, + client : client.id, + rsrid : $route.current.params.rsrid, + }, function(data) { + if (!data.scopes) { + data.scopes = []; + } + + if (!data.attributes) { + data.attributes = {}; + } + + $scope.resource = angular.copy(data); + $scope.changed = false; + + $scope.originalResource = angular.copy($scope.resource); + + $scope.$watch('resource', function() { + if (!angular.equals($scope.resource, data)) { + $scope.changed = true; + } + }, true); + + $scope.$watch('newUri', function() { + if ($scope.newUri && $scope.newUri.length > 0) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + if ($scope.newUri && $scope.newUri.length > 0) { + $scope.addUri(); + } + + for (i = 0; i < $scope.resource.scopes.length; i++) { + delete $scope.resource.scopes[i].text; + } + + var keys = Object.keys($scope.resource.attributes); + + for (var k = 0; k < keys.length; k++) { + var key = keys[k]; + var value = $scope.resource.attributes[key]; + var values = value.toString().split(','); + + $scope.resource.attributes[key] = []; + + for (j = 0; j < values.length; j++) { + $scope.resource.attributes[key].push(values[j]); + } + } + $instance.checkNameAvailability(function () { + ResourceServerResource.update({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, $scope.resource, function() { + $route.reload(); + Notifications.success("The resource has been updated."); + }); + }); + } + + $scope.remove = function() { + Resources.delete(ResourceServerResource, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route); + } + + $scope.reset = function() { + $route.reload(); + } + }); + } + }); + + $scope.checkNewNameAvailability = function () { + $instance.checkNameAvailability(function () {}); + } + + this.checkNameAvailability = function (onSuccess) { + if (!$scope.resource.name || $scope.resource.name.trim().length == 0) { + return; + } + ResourceServerResource.search({ + realm : $route.current.params.realm, + client : client.id, + rsrid : $route.current.params.rsrid, + name: $scope.resource.name + }, function(data) { + if (data && data._id && data._id != $scope.resource._id) { + Notifications.error("Name already in use by another resource, please choose another one."); + } else { + onSuccess(); + } + }); + } + + $scope.addAttribute = function() { + $scope.resource.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.resource.attributes[key]; + } + + $scope.addUri = function() { + $scope.resource.uris.push($scope.newUri); + $scope.newUri = ""; + } + + $scope.deleteUri = function(index) { + $scope.resource.uris.splice(index, 1); + } +}); + +var Scopes = { + delete: function(ResourceServerScope, realm, client, $scope, AuthzDialog, $location, Notifications, $route) { + ResourceServerScope.permissions({ + realm : realm, + client : client.id, + id : $scope.scope.id + }, function (permissions) { + var msg = ""; + + if (permissions.length > 0 && !$scope.deleteConsent) { + msg = "

This scope is referenced in some permissions:

"; + msg += "
    "; + for (i = 0; i < permissions.length; i++) { + msg+= "
  • " + permissions[i].name + "
  • "; + } + msg += "
"; + msg += "

If you remove this scope, the permissions above will be affected and will not be associated with this scope anymore.

"; + } + + AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() { + ResourceServerScope.delete({realm : realm, client : $scope.client.id, id : $scope.scope.id}, null, function() { + $location.url("/realms/" + realm + "/clients/" + $scope.client.id + "/authz/resource-server/scope"); + $route.reload(); + Notifications.success("The scope has been deleted."); + }); + }); + }); + } +} + +module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerScope,client, AuthzDialog, Notifications, viewState) { + $scope.realm = realm; + $scope.client = client; + + $scope.query = { + realm: realm.realm, + client : client.id, + deep: false, + max : 20, + first : 0 + }; + + $scope.listSizes = [5, 10, 20]; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + + $scope.createPolicy = function(scope) { + viewState.state = {}; + viewState.state.previousUrl = '/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/scope'; + $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id}); + } + + $scope.searchQuery(); + }); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function(detailsFilter) { + $scope.searchLoaded = false; + + ResourceServerScope.query($scope.query, function(response) { + $scope.scopes = response; + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } + }); + }; + + $scope.loadDetails = function (scope) { + if (scope.details) { + scope.details.loaded = !scope.details.loaded; + return; + } + + scope.details = {loaded: false}; + + ResourceServerScope.resources({ + realm : $route.current.params.realm, + client : client.id, + id : scope.id + }, function(response) { + scope.resources = response; + ResourceServerScope.permissions({ + realm : $route.current.params.realm, + client : client.id, + id : scope.id + }, function(response) { + scope.policies = response; + scope.details.loaded = true; + }); + }); + } + + $scope.showDetails = function(item, event) { + if (event.target.localName == 'a' || event.target.localName == 'button') { + return; + } + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.scopes.length; i++) { + $scope.loadDetails($scope.scopes[i]); + } + } + }; + + $scope.delete = function(scope) { + $scope.scope = scope; + Scopes.delete(ResourceServerScope, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route); + }; +}); + +module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerScope, AuthzDialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + + var $instance = this; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + + var scopeId = $route.current.params.id; + + if (!scopeId) { + $scope.create = true; + $scope.changed = false; + + var scope = {}; + + $scope.scope = angular.copy(scope); + + $scope.$watch('scope', function() { + if (!angular.equals($scope.scope, scope)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + $instance.checkNameAvailability(function () { + ResourceServerScope.save({realm : realm.realm, client : $scope.client.id}, $scope.scope, function(data) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/scope/" + data.id); + Notifications.success("The scope has been created."); + }); + }); + } + + $scope.reset = function() { + $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/scope/"); + } + } else { + ResourceServerScope.get({ + realm : $route.current.params.realm, + client : client.id, + id : $route.current.params.id, + }, function(data) { + $scope.scope = angular.copy(data); + $scope.changed = false; + + $scope.$watch('scope', function() { + if (!angular.equals($scope.scope, data)) { + $scope.changed = true; + } + }, true); + + $scope.originalScope = angular.copy($scope.scope); + + $scope.save = function() { + $instance.checkNameAvailability(function () { + ResourceServerScope.update({realm : realm.realm, client : $scope.client.id, id : $scope.scope.id}, $scope.scope, function() { + $scope.changed = false; + Notifications.success("The scope has been updated."); + }); + }); + } + + $scope.remove = function() { + Scopes.delete(ResourceServerScope, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route); + } + + $scope.reset = function() { + $route.reload(); + } + }); + } + }); + + $scope.checkNewNameAvailability = function () { + $instance.checkNameAvailability(function () {}); + } + + this.checkNameAvailability = function (onSuccess) { + if (!$scope.scope.name || $scope.scope.name.trim().length == 0) { + return; + } + ResourceServerScope.search({ + realm : $route.current.params.realm, + client : client.id, + name: $scope.scope.name + }, function(data) { + if (data && data.id && data.id != $scope.scope.id) { + Notifications.error("Name already in use by another scope, please choose another one."); + } else { + onSuccess(); + } + }); + } +}); + +module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client, AuthzDialog, Notifications, KcStrings) { + $scope.realm = realm; + $scope.client = client; + $scope.policyProviders = []; + + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + max: 20, + first : 0 + }; + + $scope.listSizes = [5, 10, 20]; + + PolicyProvider.query({ + realm : $route.current.params.realm, + client : client.id + }, function (data) { + for (i = 0; i < data.length; i++) { + if (data[i].type != 'resource' && data[i].type != 'scope') { + $scope.policyProviders.push(data[i]); + } + } + }); + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + $scope.searchQuery(); + }); + + $scope.addPolicy = function(policyType) { + if (KcStrings.endsWith(policyType.type, '.js')) { + ResourceServerPolicy.save({realm : realm.realm, client : client.id, type: policyType.type}, {name: policyType.name, type: policyType.type}, function(data) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/"); + Notifications.success("The policy has been created."); + }); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); + } + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerPolicy.query($scope.query, function(data) { + $scope.policies = data; + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } + }); + }; + + $scope.loadDetails = function (policy) { + if (policy.details) { + policy.details.loaded = !policy.details.loaded; + return; + } + + policy.details = {loaded: false}; + + ResourceServerPolicy.dependentPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(response) { + policy.dependentPolicies = response; + policy.details.loaded = true; + }); + } + + $scope.showDetails = function(item, event) { + if (event.target.localName == 'a' || event.target.localName == 'button') { + return; + } + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.policies.length; i++) { + $scope.loadDetails($scope.policies[i]); + } + } + }; + + $scope.delete = function(policy) { + $scope.policy = policy; + Policies.delete(ResourceServerPolicy, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route, false); + }; +}); + +module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPermission, PolicyProvider, client, AuthzDialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + $scope.policyProviders = []; + + $scope.query = { + realm: realm.realm, + client : client.id, + max : 20, + first : 0 + }; + + $scope.listSizes = [5, 10, 20]; + + PolicyProvider.query({ + realm : $route.current.params.realm, + client : client.id + }, function (data) { + for (i = 0; i < data.length; i++) { + if (data[i].type == 'resource' || data[i].type == 'scope') { + $scope.policyProviders.push(data[i]); + } + } + }); + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + $scope.searchQuery(); + }); + + $scope.addPolicy = function(policyType) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + policyType.type + "/create"); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerPermission.query($scope.query, function(data) { + $scope.policies = data; + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } + }); + }; + + $scope.loadDetails = function (policy) { + if (policy.details) { + policy.details.loaded = !policy.details.loaded; + return; + } + + policy.details = {loaded: false}; + + ResourceServerPermission.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(response) { + policy.associatedPolicies = response; + policy.details.loaded = true; + }); + } + + $scope.showDetails = function(item, event) { + if (event.target.localName == 'a' || event.target.localName == 'button') { + return; + } + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.policies.length; i++) { + $scope.loadDetails($scope.policies[i]); + } + } + }; + + $scope.delete = function(policy) { + $scope.policy = policy; + Policies.delete(ResourceServerPermission, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route, true); + }; +}); + +module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $route, $location, realm, client, PolicyController, ResourceServerPermission, ResourceServerResource, policyViewState) { + PolicyController.onInit({ + getPolicyType : function() { + return "resource"; + }, + + isPermission : function() { + return true; + }, + + onInit : function() { + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPermission.searchPolicies($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.applyToResourceType = function() { + if ($scope.applyToResourceTypeFlag) { + $scope.selectedResource = null; + } else { + $scope.policy.resourceType = null; + } + } + }, + + onInitUpdate : function(policy) { + if (!policy.resourceType) { + $scope.selectedResource = {}; + ResourceServerPermission.resources({ + realm: $route.current.params.realm, + client: client.id, + id: policy.id + }, function (resources) { + resources[0].text = resources[0].name; + $scope.selectedResource = resources[0]; + var copy = angular.copy($scope.selectedResource); + $scope.$watch('selectedResource', function() { + if (!angular.equals($scope.selectedResource, copy)) { + $scope.changed = true; + } + }, true); + }); + } else { + $scope.applyToResourceTypeFlag = true; + } + + ResourceServerPermission.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + $scope.selectedPolicies = []; + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.selectedPolicies.push(policies[i]); + } + var copy = angular.copy($scope.selectedPolicies); + $scope.$watch('selectedPolicies', function() { + if (!angular.equals($scope.selectedPolicies, copy)) { + $scope.changed = true; + } + }, true); + }); + }, + + onUpdate : function() { + if ($scope.selectedResource && $scope.selectedResource._id) { + $scope.policy.resources = []; + $scope.policy.resources.push($scope.selectedResource._id); + } else { + $scope.policy.resources = []; + } + + var policies = []; + + for (i = 0; i < $scope.selectedPolicies.length; i++) { + policies.push($scope.selectedPolicies[i].id); + } + + $scope.policy.policies = policies; + delete $scope.policy.config; + }, + + onInitCreate : function(newPolicy) { + policyViewState.state.previousPage.name = 'authz-add-resource-permission'; + $scope.selectedResource = null; + var copy = angular.copy($scope.selectedResource); + $scope.$watch('selectedResource', function() { + if (!angular.equals($scope.selectedResource, copy)) { + $scope.changed = true; + } + }, true); + + $scope.selectedPolicies = null; + var copy = angular.copy($scope.selectedPolicies); + $scope.$watch('selectedPolicies', function() { + if (!angular.equals($scope.selectedPolicies, copy)) { + $scope.changed = true; + } + }, true); + + var resourceId = $location.search()['rsrid']; + + if (resourceId) { + ResourceServerResource.get({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resourceId + }, function(data) { + data.text = data.name; + $scope.selectedResource = data; + }); + } + }, + + onCreate : function() { + if ($scope.selectedResource && $scope.selectedResource._id) { + $scope.policy.resources = []; + $scope.policy.resources.push($scope.selectedResource._id); + } else { + delete $scope.policy.resources + } + + var policies = []; + + if ($scope.selectedPolicies) { + for (i = 0; i < $scope.selectedPolicies.length; i++) { + policies.push($scope.selectedPolicies[i].id); + } + } + + $scope.policy.policies = policies; + delete $scope.policy.config; + }, + + onSaveState : function(policy) { + policyViewState.state.selectedResource = $scope.selectedResource; + policyViewState.state.applyToResourceTypeFlag = $scope.applyToResourceTypeFlag; + }, + + onRestoreState : function(policy) { + $scope.selectedResource = policyViewState.state.selectedResource; + $scope.applyToResourceTypeFlag = policyViewState.state.applyToResourceTypeFlag; + policy.resourceType = policyViewState.state.policy.resourceType; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route, $location, realm, client, PolicyController, ResourceServerPolicy, ResourceServerResource, ResourceServerScope, policyViewState) { + PolicyController.onInit({ + getPolicyType : function() { + return "scope"; + }, + + isPermission : function() { + return true; + }, + + onInit : function() { + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.selectResource = function() { + $scope.selectedScopes = null; + if ($scope.selectedResource) { + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: $scope.selectedResource._id + }, function (data) { + $scope.resourceScopes = data; + }); + } + } + }, + + onInitUpdate : function(policy) { + ResourceServerPolicy.resources({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(resources) { + if (resources.length > 0) { + for (i = 0; i < resources.length; i++) { + ResourceServerResource.get({ + realm: $route.current.params.realm, + client: client.id, + rsrid: resources[0]._id, + }, function (resource) { + ResourceServerResource.query({ + realm: $route.current.params.realm, + client: client.id, + _id: resource._id, + deep: false + }, function (resource) { + resource[0].text = resource[0].name; + $scope.selectedResource = resource[0]; + var copy = angular.copy($scope.selectedResource); + $scope.$watch('selectedResource', function() { + if (!angular.equals($scope.selectedResource, copy)) { + $scope.changed = true; + } + }, true); + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: resource[0]._id + }, function (scopes) { + $scope.resourceScopes = scopes; + }); + }); + }); + } + + ResourceServerPolicy.scopes({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(scopes) { + $scope.selectedScopes = []; + for (i = 0; i < scopes.length; i++) { + scopes[i].text = scopes[i].name; + $scope.selectedScopes.push(scopes[i].id); + } + var copy = angular.copy($scope.selectedScopes); + $scope.$watch('selectedScopes', function() { + if (!angular.equals($scope.selectedScopes, copy)) { + $scope.changed = true; + } + }, true); + }); + } else { + $scope.selectedResource = null; + var copy = angular.copy($scope.selectedResource); + $scope.$watch('selectedResource', function() { + if (!angular.equals($scope.selectedResource, copy)) { + $scope.changed = true; + } + }, true); + ResourceServerPolicy.scopes({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(scopes) { + $scope.selectedScopes = []; + for (i = 0; i < scopes.length; i++) { + scopes[i].text = scopes[i].name; + $scope.selectedScopes.push(scopes[i]); + } + var copy = angular.copy($scope.selectedScopes); + $scope.$watch('selectedScopes', function() { + if (!angular.equals($scope.selectedScopes, copy)) { + $scope.changed = true; + } + }, true); + }); + } + }); + + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + $scope.selectedPolicies = []; + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.selectedPolicies.push(policies[i]); + } + var copy = angular.copy($scope.selectedPolicies); + $scope.$watch('selectedPolicies', function() { + if (!angular.equals($scope.selectedPolicies, copy)) { + $scope.changed = true; + } + }, true); + }); + }, + + onUpdate : function() { + if ($scope.selectedResource != null) { + $scope.policy.resources = [$scope.selectedResource._id]; + } else { + $scope.policy.resources = []; + } + + var scopes = []; + + for (i = 0; i < $scope.selectedScopes.length; i++) { + if ($scope.selectedScopes[i].id) { + scopes.push($scope.selectedScopes[i].id); + } else { + scopes.push($scope.selectedScopes[i]); + } + } + + $scope.policy.scopes = scopes; + + var policies = []; + + if ($scope.selectedPolicies) { + for (i = 0; i < $scope.selectedPolicies.length; i++) { + policies.push($scope.selectedPolicies[i].id); + } + } + + $scope.policy.policies = policies; + delete $scope.policy.config; + }, + + onInitCreate : function(newPolicy) { + policyViewState.state.previousPage.name = 'authz-add-scope-permission'; + var scopeId = $location.search()['scpid']; + + if (scopeId) { + ResourceServerScope.get({ + realm: $route.current.params.realm, + client: client.id, + id: scopeId, + }, function (data) { + data.text = data.name; + if (!$scope.policy.scopes) { + $scope.selectedScopes = []; + } + $scope.selectedScopes.push(data); + }); + } + }, + + onCreate : function() { + if ($scope.selectedResource != null) { + $scope.policy.resources = [$scope.selectedResource._id]; + } + + var scopes = []; + + for (i = 0; i < $scope.selectedScopes.length; i++) { + if ($scope.selectedScopes[i].id) { + scopes.push($scope.selectedScopes[i].id); + } else { + scopes.push($scope.selectedScopes[i]); + } + } + + $scope.policy.scopes = scopes; + + var policies = []; + + if ($scope.selectedPolicies) { + for (i = 0; i < $scope.selectedPolicies.length; i++) { + policies.push($scope.selectedPolicies[i].id); + } + } + + $scope.policy.policies = policies; + delete $scope.policy.config; + }, + + onSaveState : function(policy) { + policyViewState.state.selectedScopes = $scope.selectedScopes; + policyViewState.state.selectedResource = $scope.selectedResource; + policyViewState.state.resourceScopes = $scope.resourceScopes; + }, + + onRestoreState : function(policy) { + $scope.selectedScopes = policyViewState.state.selectedScopes; + $scope.selectedResource = policyViewState.state.selectedResource; + $scope.resourceScopes = policyViewState.state.resourceScopes; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyUserDetailCtrl', function($scope, $route, realm, client, PolicyController, User) { + PolicyController.onInit({ + getPolicyType : function() { + return "user"; + }, + + onInit : function() { + $scope.usersUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + User.query({realm: $route.current.params.realm, search: query.term.trim(), max: 20}, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + return object.username; + } + }; + + $scope.selectedUsers = []; + + $scope.selectUser = function(user) { + if (!user || !user.id) { + return; + } + + $scope.selectedUser = null; + + for (i = 0; i < $scope.selectedUsers.length; i++) { + if ($scope.selectedUsers[i].id == user.id) { + return; + } + } + + $scope.selectedUsers.push(user); + } + + $scope.removeFromList = function(list, user) { + for (i = 0; i < angular.copy(list).length; i++) { + if (user == list[i]) { + list.splice(i, 1); + } + } + } + }, + + onInitUpdate : function(policy) { + var selectedUsers = []; + + if (policy.users) { + var users = policy.users; + + for (i = 0; i < users.length; i++) { + User.get({realm: $route.current.params.realm, userId: users[i]}, function(data) { + selectedUsers.push(data); + $scope.selectedUsers = angular.copy(selectedUsers); + }); + } + } + + $scope.$watch('selectedUsers', function() { + if (!angular.equals($scope.selectedUsers, selectedUsers)) { + $scope.changed = true; + } else { + $scope.changed = false; + } + }, true); + }, + + onUpdate : function() { + var users = []; + + for (i = 0; i < $scope.selectedUsers.length; i++) { + users.push($scope.selectedUsers[i].id); + } + + $scope.policy.users = users; + delete $scope.policy.config; + }, + + onCreate : function() { + var users = []; + + for (i = 0; i < $scope.selectedUsers.length; i++) { + users.push($scope.selectedUsers[i].id); + } + + $scope.policy.users = users; + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyClientDetailCtrl', function($scope, $route, realm, client, PolicyController, Client) { + PolicyController.onInit({ + getPolicyType : function() { + return "client"; + }, + + onInit : function() { + clientSelectControl($scope, $route.current.params.realm, Client); + + $scope.selectedClients = []; + + $scope.selectClient = function(client) { + if (!client || !client.id) { + return; + } + + $scope.selectedClient = null; + + for (var i = 0; i < $scope.selectedClients.length; i++) { + if ($scope.selectedClients[i].id == client.id) { + return; + } + } + + $scope.selectedClients.push(client); + } + + $scope.removeFromList = function(client) { + var index = $scope.selectedClients.indexOf(client); + if (index != -1) { + $scope.selectedClients.splice(index, 1); + } + } + }, + + onInitUpdate : function(policy) { + var selectedClients = []; + + if (policy.clients) { + var clients = policy.clients; + + for (var i = 0; i < clients.length; i++) { + Client.get({realm: $route.current.params.realm, client: clients[i]}, function(data) { + selectedClients.push(data); + $scope.selectedClients = angular.copy(selectedClients); + }); + } + } + + $scope.$watch('selectedClients', function() { + if (!angular.equals($scope.selectedClients, selectedClients)) { + $scope.changed = true; + } else { + $scope.changed = false; + } + }, true); + }, + + onUpdate : function() { + var clients = []; + + for (var i = 0; i < $scope.selectedClients.length; i++) { + clients.push($scope.selectedClients[i].id); + } + + $scope.policy.clients = clients; + delete $scope.policy.config; + }, + + onInitCreate : function() { + var selectedClients = []; + + $scope.$watch('selectedClients', function() { + if (!angular.equals($scope.selectedClients, selectedClients)) { + $scope.changed = true; + } + }, true); + }, + + onCreate : function() { + var clients = []; + + for (var i = 0; i < $scope.selectedClients.length; i++) { + clients.push($scope.selectedClients[i].id); + } + + $scope.policy.clients = clients; + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyRoleDetailCtrl', function($scope, $route, realm, client, Client, ClientRole, PolicyController, Role, RoleById) { + PolicyController.onInit({ + getPolicyType : function() { + return "role"; + }, + + onInit : function() { + Role.query({realm: $route.current.params.realm}, function(data) { + $scope.roles = data; + }); + + Client.query({realm: $route.current.params.realm}, function (data) { + $scope.clients = data; + }); + + $scope.selectedRoles = []; + + $scope.selectRole = function(role) { + if (!role || !role.id) { + return; + } + + $scope.selectedRole = null; + + for (i = 0; i < $scope.selectedRoles.length; i++) { + if ($scope.selectedRoles[i].id == role.id) { + return; + } + } + + $scope.selectedRoles.push(role); + + var clientRoles = []; + + if ($scope.clientRoles) { + for (i = 0; i < $scope.clientRoles.length; i++) { + if ($scope.clientRoles[i].id != role.id) { + clientRoles.push($scope.clientRoles[i]); + } + } + $scope.clientRoles = clientRoles; + } + } + + $scope.removeFromList = function(role) { + if ($scope.clientRoles && $scope.selectedClient && $scope.selectedClient.id == role.containerId) { + $scope.clientRoles.push(role); + } + var index = $scope.selectedRoles.indexOf(role); + if (index != -1) { + $scope.selectedRoles.splice(index, 1); + } + } + + $scope.selectClient = function() { + if (!$scope.selectedClient) { + $scope.clientRoles = []; + return; + } + ClientRole.query({realm: $route.current.params.realm, client: $scope.selectedClient.id}, function(data) { + var roles = []; + + for (j = 0; j < data.length; j++) { + var defined = false; + + for (i = 0; i < $scope.selectedRoles.length; i++) { + if ($scope.selectedRoles[i].id == data[j].id) { + defined = true; + break; + } + } + + if (!defined) { + data[j].container = {}; + data[j].container.name = $scope.selectedClient.clientId; + roles.push(data[j]); + } + } + $scope.clientRoles = roles; + }); + } + }, + + onInitUpdate : function(policy) { + var selectedRoles = []; + + if (policy.roles) { + var roles = policy.roles; + + for (i = 0; i < roles.length; i++) { + RoleById.get({realm: $route.current.params.realm, role: roles[i].id}, function(data) { + for (i = 0; i < roles.length; i++) { + if (roles[i].id == data.id) { + data.required = roles[i].required ? true : false; + } + } + for (i = 0; i < $scope.clients.length; i++) { + if ($scope.clients[i].id == data.containerId) { + data.container = {}; + data.container.name = $scope.clients[i].clientId; + } + } + selectedRoles.push(data); + $scope.selectedRoles = angular.copy(selectedRoles); + }); + } + } + + $scope.$watch('selectedRoles', function() { + if (!angular.equals($scope.selectedRoles, selectedRoles)) { + $scope.changed = true; + } else { + $scope.changed = false; + } + }, true); + }, + + onUpdate : function() { + var roles = []; + + for (i = 0; i < $scope.selectedRoles.length; i++) { + var role = {}; + role.id = $scope.selectedRoles[i].id; + if ($scope.selectedRoles[i].required) { + role.required = $scope.selectedRoles[i].required; + } + roles.push(role); + } + + $scope.policy.roles = roles; + delete $scope.policy.config; + }, + + onCreate : function() { + var roles = []; + + for (i = 0; i < $scope.selectedRoles.length; i++) { + var role = {}; + role.id = $scope.selectedRoles[i].id; + if ($scope.selectedRoles[i].required) { + role.required = $scope.selectedRoles[i].required; + } + roles.push(role); + } + + $scope.policy.roles = roles; + delete $scope.policy.config; + } + }, realm, client, $scope); + + $scope.hasRealmRole = function () { + for (i = 0; i < $scope.selectedRoles.length; i++) { + if (!$scope.selectedRoles[i].clientRole) { + return true; + } + } + return false; + } + + $scope.hasClientRole = function () { + for (i = 0; i < $scope.selectedRoles.length; i++) { + if ($scope.selectedRoles[i].clientRole) { + return true; + } + } + return false; + } +}); + +module.controller('ResourceServerPolicyGroupDetailCtrl', function($scope, $route, realm, client, Client, Groups, Group, PolicyController, Notifications, $translate) { + PolicyController.onInit({ + getPolicyType : function() { + return "group"; + }, + + onInit : function() { + $scope.tree = []; + + Groups.query({realm: $route.current.params.realm}, function(groups) { + $scope.groups = groups; + $scope.groupList = [ + {"id" : "realm", "name": $translate.instant('groups'), + "subGroups" : groups} + ]; + }); + + var isLeaf = function(node) { + return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); + } + + $scope.getGroupClass = function(node) { + if (node.id == "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + } + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + return 'cut'; + } + return undefined; + } + + $scope.selectGroup = function(group) { + for (i = 0; i < $scope.selectedGroups.length; i++) { + if ($scope.selectedGroups[i].id == group.id) { + return + } + } + if (group.id == "realm") { + Notifications.error("You must choose a group"); + return; + } + $scope.selectedGroups.push({id: group.id, path: group.path}); + $scope.changed = true; + } + + $scope.extendChildren = function(group) { + $scope.changed = true; + } + + $scope.removeFromList = function(group) { + var index = $scope.selectedGroups.indexOf(group); + if (index != -1) { + $scope.selectedGroups.splice(index, 1); + $scope.changed = true; + } + } + }, + + onInitCreate : function(policy) { + var selectedGroups = []; + + $scope.selectedGroups = angular.copy(selectedGroups); + + $scope.$watch('selectedGroups', function() { + if (!angular.equals($scope.selectedGroups, selectedGroups)) { + $scope.changed = true; + } else { + $scope.changed = PolicyController.isNewAssociatedPolicy(); + } + }, true); + }, + + onInitUpdate : function(policy) { + $scope.selectedGroups = policy.groups; + + angular.forEach($scope.selectedGroups, function(group, index){ + Group.get({realm: $route.current.params.realm, groupId: group.id}, function (existing) { + group.path = existing.path; + }); + }); + + $scope.$watch('selectedGroups', function() { + if (!$scope.changed) { + return; + } + if (!angular.equals($scope.selectedGroups, selectedGroups)) { + $scope.changed = true; + } else { + $scope.changed = false; + } + }, true); + }, + + onUpdate : function() { + $scope.policy.groups = $scope.selectedGroups; + delete $scope.policy.config; + }, + + onCreate : function() { + $scope.policy.groups = $scope.selectedGroups; + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyJSDetailCtrl', function($scope, $route, $location, realm, PolicyController, client, serverInfo) { + PolicyController.onInit({ + getPolicyType : function() { + return "js"; + }, + + onInit : function() { + $scope.readOnly = !serverInfo.featureEnabled('UPLOAD_SCRIPTS'); + $scope.initEditor = function(editor){ + editor.$blockScrolling = Infinity; + editor.setReadOnly($scope.readOnly); + var session = editor.getSession(); + session.setMode('ace/mode/javascript'); + }; + }, + + onInitUpdate : function(policy) { + + }, + + onUpdate : function() { + delete $scope.policy.config; + }, + + onInitCreate : function(newPolicy) { + }, + + onCreate : function() { + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyTimeDetailCtrl', function($scope, $route, $location, realm, PolicyController, client) { + + function clearEmptyStrings() { + if ($scope.policy.notBefore != undefined && $scope.policy.notBefore.trim() == '') { + $scope.policy.notBefore = null; + } + if ($scope.policy.notOnOrAfter != undefined && $scope.policy.notOnOrAfter.trim() == '') { + $scope.policy.notOnOrAfter = null; + } + } + + PolicyController.onInit({ + getPolicyType : function() { + return "time"; + }, + + onInit : function() { + + }, + + onInitUpdate : function(policy) { + if (policy.dayMonth) { + policy.dayMonth = parseInt(policy.dayMonth); + } + if (policy.dayMonthEnd) { + policy.dayMonthEnd = parseInt(policy.dayMonthEnd); + } + if (policy.month) { + policy.month = parseInt(policy.month); + } + if (policy.monthEnd) { + policy.monthEnd = parseInt(policy.monthEnd); + } + if (policy.year) { + policy.year = parseInt(policy.year); + } + if (policy.yearEnd) { + policy.yearEnd = parseInt(policy.yearEnd); + } + if (policy.hour) { + policy.hour = parseInt(policy.hour); + } + if (policy.hourEnd) { + policy.hourEnd = parseInt(policy.hourEnd); + } + if (policy.minute) { + policy.minute = parseInt(policy.minute); + } + if (policy.minuteEnd) { + policy.minuteEnd = parseInt(policy.minuteEnd); + } + }, + + onUpdate : function() { + clearEmptyStrings(); + delete $scope.policy.config; + }, + + onInitCreate : function(newPolicy) { + }, + + onCreate : function() { + clearEmptyStrings(); + delete $scope.policy.config; + } + }, realm, client, $scope); + + $scope.isRequired = function () { + var policy = $scope.policy; + + if (!policy) { + return true; + } + + if (policy.notOnOrAfter || policy.notBefore + || policy.dayMonth + || policy.month + || policy.year + || policy.hour + || policy.minute) { + return false; + } + return true; + } +}); + +module.controller('ResourceServerPolicyAggregateDetailCtrl', function($scope, $route, $location, realm, PolicyController, ResourceServerPolicy, client, PolicyProvider, policyViewState) { + PolicyController.onInit({ + getPolicyType : function() { + return "aggregate"; + }, + + onInit : function() { + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + }, + + onInitUpdate : function(policy) { + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + $scope.selectedPolicies = []; + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.selectedPolicies.push(policies[i]); + } + var copy = angular.copy($scope.selectedPolicies); + $scope.$watch('selectedPolicies', function() { + if (!angular.equals($scope.selectedPolicies, copy)) { + $scope.changed = true; + } + }, true); + }); + }, + + onUpdate : function() { + var policies = []; + + for (i = 0; i < $scope.selectedPolicies.length; i++) { + policies.push($scope.selectedPolicies[i].id); + } + + $scope.policy.policies = policies; + delete $scope.policy.config; + }, + + onInitCreate : function(newPolicy) { + policyViewState.state.previousPage.name = 'authz-add-aggregated-policy'; + }, + + onCreate : function() { + var policies = []; + + for (i = 0; i < $scope.selectedPolicies.length; i++) { + policies.push($scope.selectedPolicies[i].id); + } + + $scope.policy.policies = policies; + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + +module.controller('ResourceServerPolicyClientScopeDetailCtrl', function($scope, $route, realm, client, ClientScope, PolicyController) { + PolicyController.onInit({ + getPolicyType : function() { + return "client-scope"; + }, + + onInit : function() { + ClientScope.query({realm: $route.current.params.realm}, function(data) { + $scope.clientScopes = data; + }); + + $scope.selectedClientScopes = []; + + $scope.selectClientScope = function(clientScope) { + if (!clientScope || !clientScope.id) { + return; + } + + $scope.selectedClientScope = null; + + for (i = 0; i < $scope.selectedClientScopes.length; i++) { + if ($scope.selectedClientScopes[i].id == clientScope.id) { + return; + } + } + + $scope.selectedClientScopes.push(clientScope); + } + + $scope.removeFromList = function(clientScope) { + var index = $scope.selectedClientScopes.indexOf(clientScope); + if (index != -1) { + $scope.selectedClientScopes.splice(index, 1); + } + } + }, + + onInitUpdate : function(policy) { + var selectedClientScopes = []; + + if (policy.clientScopes) { + var clientScopes = policy.clientScopes; + + for (i = 0; i < clientScopes.length; i++) { + ClientScope.get({realm: $route.current.params.realm, clientScope: clientScopes[i].id}, function(data) { + for (i = 0; i < clientScopes.length; i++) { + if (clientScopes[i].id == data.id) { + data.required = clientScopes[i].required ? true : false; + } + } + selectedClientScopes.push(data); + $scope.selectedClientScopes = angular.copy(selectedClientScopes); + }); + } + } + + $scope.$watch('selectedClientScopes', function() { + if (!angular.equals($scope.selectedClientScopes, selectedClientScopes)) { + $scope.changed = true; + } else { + $scope.changed = false; + } + }, true); + }, + + onUpdate : function() { + var clientScopes = []; + + for (i = 0; i < $scope.selectedClientScopes.length; i++) { + var clientScope = {}; + clientScope.id = $scope.selectedClientScopes[i].id; + if ($scope.selectedClientScopes[i].required) { + clientScope.required = $scope.selectedClientScopes[i].required; + } + clientScopes.push(clientScope); + } + + $scope.policy.clientScopes = clientScopes; + delete $scope.policy.config; + }, + + onCreate : function() { + var clientScopes = []; + + for (i = 0; i < $scope.selectedClientScopes.length; i++) { + var clientScope = {}; + clientScope.id = $scope.selectedClientScopes[i].id; + if ($scope.selectedClientScopes[i].required) { + clientScope.required = $scope.selectedClientScopes[i].required; + } + clientScopes.push(clientScope); + } + + $scope.policy.clientScopes = clientScopes; + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + +module.service("PolicyController", function($http, $route, $location, ResourceServer, ResourceServerPolicy, ResourceServerPermission, AuthzDialog, Notifications, policyViewState, PolicyProvider, viewState) { + + var PolicyController = {}; + + PolicyController.isNewAssociatedPolicy = function() { + return $route.current.params['new_policy'] != null; + } + + PolicyController.isBackNewAssociatedPolicy = function() { + return $route.current.params['back'] != null; + } + + PolicyController.onInit = function(delegate, realm, client, $scope) { + $scope.policyProviders = []; + + PolicyProvider.query({ + realm : $route.current.params.realm, + client : client.id + }, function (data) { + for (i = 0; i < data.length; i++) { + if (data[i].type != 'resource' && data[i].type != 'scope') { + $scope.policyProviders.push(data[i]); + } + } + }); + + if ((!policyViewState.state || !PolicyController.isBackNewAssociatedPolicy()) && !PolicyController.isNewAssociatedPolicy()) { + policyViewState.state = {}; + } + + if (!policyViewState.state.previousPage) { + policyViewState.state.previousPage = {}; + } + + $scope.policyViewState = policyViewState; + + $scope.addPolicy = function(policyType) { + policyViewState.state.policy = $scope.policy; + + if (delegate.onSaveState) { + delegate.onSaveState($scope.policy); + } + + if ($scope.selectedPolicies) { + policyViewState.state.selectedPolicies = $scope.selectedPolicies; + } + var previousUrl = window.location.href.substring(window.location.href.indexOf('/realms')); + + if (previousUrl.indexOf('back=true') == -1) { + previousUrl = previousUrl + (previousUrl.indexOf('?') == -1 ? '?' : '&') + 'back=true'; + } + policyViewState.state.previousUrl = previousUrl; + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create?new_policy=true"); + } + + $scope.detailPolicy = function(policy) { + policyViewState.state.policy = $scope.policy; + if (delegate.onSaveState) { + delegate.onSaveState($scope.policy); + } + if ($scope.selectedPolicies) { + policyViewState.state.selectedPolicies = $scope.selectedPolicies; + } + var previousUrl = window.location.href.substring(window.location.href.indexOf('/realms')); + + if (previousUrl.indexOf('back=true') == -1) { + previousUrl = previousUrl + (previousUrl.indexOf('?') == -1 ? '?' : '&') + 'back=true'; + } + policyViewState.state.previousUrl = previousUrl; + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policy.type + "/" + policy.id + "?new_policy=true"); + } + + $scope.removePolicy = function(list, policy) { + for (i = 0; i < angular.copy(list).length; i++) { + if (policy.id == list[i].id) { + list.splice(i, 1); + } + } + } + + $scope.selectPolicy = function(policy) { + if (!policy || !policy.id) { + return; + } + + if (!$scope.selectedPolicies) { + $scope.selectedPolicies = []; + } + + $scope.selectedPolicy = null; + + for (i = 0; i < $scope.selectedPolicies.length; i++) { + if ($scope.selectedPolicies[i].id == policy.id) { + return; + } + } + + $scope.selectedPolicies.push(policy); + } + + $scope.createNewPolicy = function() { + $scope.showNewPolicy = true; + } + + $scope.cancelCreateNewPolicy = function() { + $scope.showNewPolicy = false; + } + + $scope.historyBackOnSaveOrCancel = PolicyController.isNewAssociatedPolicy(); + + if (!delegate.isPermission) { + delegate.isPermission = function () { + return false; + } + } + + var service = ResourceServerPolicy; + + if (delegate.isPermission()) { + service = ResourceServerPermission; + } + + $scope.realm = realm; + $scope.client = client; + + $scope.decisionStrategies = ['AFFIRMATIVE', 'UNANIMOUS', 'CONSENSUS']; + $scope.logics = ['POSITIVE', 'NEGATIVE']; + + delegate.onInit(); + + var $instance = this; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + + var policyId = $route.current.params.id; + + if (!policyId) { + $scope.create = true; + + var policy = {}; + + policy.type = delegate.getPolicyType(); + policy.config = {}; + policy.logic = 'POSITIVE'; + policy.decisionStrategy = 'UNANIMOUS'; + + $scope.changed = $scope.historyBackOnSaveOrCancel || PolicyController.isBackNewAssociatedPolicy(); + + if (viewState.state != null && viewState.state.previousUrl != null) { + $scope.previousUrl = viewState.state.previousUrl; + policyViewState.state.rootUrl = $scope.previousUrl; + viewState.state = {}; + } + + $scope.policy = angular.copy(policy); + + $scope.$watch('policy', function() { + if (!angular.equals($scope.policy, policy)) { + $scope.changed = true; + } + }, true); + + if (PolicyController.isBackNewAssociatedPolicy()) { + if (delegate.onRestoreState) { + delegate.onRestoreState($scope.policy); + } + $instance.restoreState($scope); + } else if (delegate.onInitCreate) { + delegate.onInitCreate(policy); + } + + $scope.save = function() { + $instance.checkNameAvailability(function () { + if (delegate.onCreate) { + delegate.onCreate(); + } + service.save({realm : realm.realm, client : client.id, type: $scope.policy.type}, $scope.policy, function(data) { + if (delegate.isPermission()) { + if ($scope.historyBackOnSaveOrCancel || policyViewState.state.rootUrl != null) { + if (policyViewState.state.rootUrl != null) { + $location.url(policyViewState.state.rootUrl); + } else { + policyViewState.state.newPolicyName = $scope.policy.name; + $location.url(policyViewState.state.previousUrl); + } + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + $scope.policy.type + "/" + data.id); + } + Notifications.success("The permission has been created."); + } else { + if ($scope.historyBackOnSaveOrCancel) { + policyViewState.state.newPolicyName = $scope.policy.name; + $location.url(policyViewState.state.previousUrl); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + $scope.policy.type + "/" + data.id); + } + Notifications.success("The policy has been created."); + } + }); + }); + } + + $scope.reset = function() { + if (delegate.isPermission()) { + if ($scope.historyBackOnSaveOrCancel || policyViewState.state.rootUrl != null) { + if (policyViewState.state.rootUrl != null) { + $location.url(policyViewState.state.rootUrl); + } else { + $location.url(policyViewState.state.previousUrl); + } + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/"); + } + } else { + if ($scope.historyBackOnSaveOrCancel) { + $location.url(policyViewState.state.previousUrl); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/"); + } + } + } + } else { + service.get({ + realm: realm.realm, + client : client.id, + type: delegate.getPolicyType(), + id: $route.current.params.id + }, function(data) { + $scope.originalPolicy = data; + var policy = angular.copy(data); + + $scope.changed = $scope.historyBackOnSaveOrCancel || PolicyController.isBackNewAssociatedPolicy(); + $scope.policy = angular.copy(policy); + + if (PolicyController.isBackNewAssociatedPolicy()) { + if (delegate.onRestoreState) { + delegate.onRestoreState($scope.policy); + } + $instance.restoreState($scope); + } else if (delegate.onInitUpdate) { + delegate.onInitUpdate($scope.policy); + } + + $scope.$watch('policy', function() { + if (!angular.equals($scope.policy, policy)) { + $scope.changed = true; + } + }, true); + + + $scope.save = function() { + $instance.checkNameAvailability(function () { + if (delegate.onUpdate) { + delegate.onUpdate(); + } + service.update({realm : realm.realm, client : client.id, type: $scope.policy.type, id : $scope.policy.id}, $scope.policy, function() { + if (delegate.isPermission()) { + if ($scope.historyBackOnSaveOrCancel) { + $location.url(policyViewState.state.previousUrl); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + $scope.policy.type + "/" + $scope.policy.id); + } + $route.reload(); + Notifications.success("The permission has been updated."); + } else { + if ($scope.historyBackOnSaveOrCancel) { + $location.url(policyViewState.state.previousUrl); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + $scope.policy.type + "/" + $scope.policy.id); + } + $route.reload(); + Notifications.success("The policy has been updated."); + } + }); + }); + } + + $scope.reset = function() { + if ($scope.historyBackOnSaveOrCancel) { + $location.url(policyViewState.state.previousUrl); + } else { + var freshPolicy = angular.copy(data); + + if (delegate.onInitUpdate) { + delegate.onInitUpdate(freshPolicy); + } + + $scope.policy = angular.copy(freshPolicy); + $scope.changed = false; + } + } + }); + + $scope.remove = function() { + Policies.delete(ResourceServerPolicy, $route.current.params.realm, client, $scope, AuthzDialog, $location, Notifications, $route, delegate.isPermission()); + } + } + }); + + $scope.checkNewNameAvailability = function () { + $instance.checkNameAvailability(function () {}); + } + + this.checkNameAvailability = function (onSuccess) { + if (!$scope.policy.name || $scope.policy.name.trim().length == 0) { + return; + } + ResourceServerPolicy.search({ + realm: $route.current.params.realm, + client: client.id, + name: $scope.policy.name + }, function(data) { + if (data && data.id && data.id != $scope.policy.id) { + Notifications.error("Name already in use by another policy or permission, please choose another one."); + } else { + onSuccess(); + } + }); + } + + this.restoreState = function($scope) { + $scope.policy.name = policyViewState.state.policy.name; + $scope.policy.description = policyViewState.state.policy.description; + $scope.policy.decisionStrategy = policyViewState.state.policy.decisionStrategy; + $scope.policy.logic = policyViewState.state.policy.logic; + $scope.selectedPolicies = policyViewState.state.selectedPolicies; + + if (!$scope.selectedPolicies) { + $scope.selectedPolicies = []; + } + + $scope.changed = true; + var previousPage = policyViewState.state.previousPage; + + if (policyViewState.state.newPolicyName) { + ResourceServerPolicy.query({ + realm: realm.realm, + client : client.id, + permission: false, + name: policyViewState.state.newPolicyName, + max : 20, + first : 0 + }, function(response) { + for (i = 0; i < response.length; i++) { + if (response[i].name == policyViewState.state.newPolicyName) { + response[i].text = response[i].name; + $scope.selectedPolicies.push(response[i]); + } + } + + var rootUrl = policyViewState.state.rootUrl; + policyViewState.state = {}; + policyViewState.state.previousPage = previousPage; + policyViewState.state.rootUrl = rootUrl; + }); + } else { + var rootUrl = policyViewState.state.rootUrl; + policyViewState.state = {}; + policyViewState.state.previousPage = previousPage; + policyViewState.state.rootUrl = rootUrl; + } + } + } + + return PolicyController; +}); + +module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $location, realm, clients, roles, ResourceServer, client, ResourceServerResource, ResourceServerScope, User, Notifications) { + $scope.realm = realm; + $scope.client = client; + $scope.clients = clients; + $scope.roles = roles; + $scope.authzRequest = {}; + $scope.authzRequest.resources = []; + $scope.authzRequest.context = {}; + $scope.authzRequest.context.attributes = {}; + $scope.authzRequest.roleIds = []; + $scope.resultUrl = resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate-result.html'; + + $scope.addContextAttribute = function() { + if (!$scope.newContextAttribute.value || $scope.newContextAttribute.value == '') { + Notifications.error("You must provide a value to a context attribute."); + return; + } + + $scope.authzRequest.context.attributes[$scope.newContextAttribute.key] = $scope.newContextAttribute.value; + delete $scope.newContextAttribute; + } + + $scope.removeContextAttribute = function(key) { + delete $scope.authzRequest.context.attributes[key]; + } + + $scope.getContextAttribute = function(key) { + for (i = 0; i < $scope.defaultContextAttributes.length; i++) { + if ($scope.defaultContextAttributes[i].key == key) { + return $scope.defaultContextAttributes[i]; + } + } + + return $scope.authzRequest.context.attributes[key]; + } + + $scope.getContextAttributeName = function(key) { + var attribute = $scope.getContextAttribute(key); + + if (!attribute.name) { + return key; + } + + return attribute.name; + } + + $scope.defaultContextAttributes = [ + { + key : "custom", + name : "Custom Attribute...", + custom: true + }, + { + key : "kc.identity.authc.method", + name : "Authentication Method", + values: [ + { + key : "pwd", + name : "Password" + }, + { + key : "otp", + name : "One-Time Password" + }, + { + key : "kbr", + name : "Kerberos" + } + ] + }, + { + key : "kc.realm.name", + name : "Realm" + }, + { + key : "kc.time.date_time", + name : "Date/Time (MM/dd/yyyy hh:mm:ss)" + }, + { + key : "kc.client.network.ip_address", + name : "Client IPv4 Address" + }, + { + key : "kc.client.network.host", + name : "Client Host" + }, + { + key : "kc.client.user_agent", + name : "Client/User Agent" + } + ]; + + $scope.isDefaultContextAttribute = function() { + if (!$scope.newContextAttribute) { + return true; + } + + if ($scope.newContextAttribute.custom) { + return false; + } + + if (!$scope.getContextAttribute($scope.newContextAttribute.key).custom) { + return true; + } + + return false; + } + + $scope.selectDefaultContextAttribute = function() { + $scope.newContextAttribute = angular.copy($scope.newContextAttribute); + } + + $scope.setApplyToResourceType = function() { + delete $scope.newResource; + $scope.authzRequest.resources = []; + } + + $scope.addResource = function() { + var resource = angular.copy($scope.newResource); + + if (!resource) { + resource = {}; + } + + delete resource.text; + + if (!$scope.newScopes || (resource._id != null && $scope.newScopes.length > 0 && $scope.newScopes[0].id)) { + $scope.newScopes = []; + } + + var scopes = []; + + for (i = 0; i < $scope.newScopes.length; i++) { + if ($scope.newScopes[i].name) { + scopes.push($scope.newScopes[i].name); + } else { + scopes.push($scope.newScopes[i]); + } + } + + resource.scopes = scopes; + + $scope.authzRequest.resources.push(resource); + + delete $scope.newResource; + delete $scope.newScopes; + } + + $scope.removeResource = function(index) { + $scope.authzRequest.resources.splice(index, 1); + } + + $scope.resolveScopes = function() { + if ($scope.newResource._id) { + $scope.newResource.scopes = []; + $scope.scopes = []; + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: $scope.newResource._id + }, function (data) { + $scope.scopes = data; + }); + } + } + + $scope.reevaluate = function() { + if ($scope.authzRequest.entitlements) { + $scope.entitlements(); + } else { + $scope.save(); + } + } + + $scope.showAuthzData = function() { + $scope.showRpt = true; + } + + $scope.save = function() { + $scope.authzRequest.entitlements = false; + if ($scope.applyResourceType) { + if (!$scope.newResource) { + $scope.newResource = {}; + } + if (!$scope.newScopes || ($scope.newResource._id != null && $scope.newScopes.length > 0 && $scope.newScopes[0].id)) { + $scope.newScopes = []; + } + + var scopes = angular.copy($scope.newScopes); + + for (i = 0; i < scopes.length; i++) { + delete scopes[i].text; + } + + $scope.authzRequest.resources[0].scopes = scopes; + } + + $http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/evaluate' + , $scope.authzRequest).then(function(response) { + $scope.evaluationResult = response.data; + $scope.showResultTab(); + }); + } + + $scope.entitlements = function() { + $scope.authzRequest.entitlements = true; + $http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/evaluate' + , $scope.authzRequest).then(function(response) { + $scope.evaluationResult = response.data; + $scope.showResultTab(); + }); + } + + $scope.showResultTab = function() { + $scope.showResult = true; + $scope.showRpt = false; + } + + $scope.showRequestTab = function() { + $scope.showResult = false; + $scope.showRpt = false; + } + + $scope.usersUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + User.query({realm: $route.current.params.realm, search: query.term.trim(), max: 20}, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.username; + return object.username; + } + }; + + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + ResourceServer.get({ + realm : $route.current.params.realm, + client : client.id + }, function(data) { + $scope.server = data; + }); + + $scope.selectUser = function(user) { + if (!user || !user.id) { + $scope.selectedUser = null; + $scope.authzRequest.userId = ''; + return; + } + + $scope.authzRequest.userId = user.id; + } + + $scope.reset = function() { + $scope.authzRequest = angular.copy(authzRequest); + $scope.changed = false; + } +}); + +getManageClientId = function(realm) { + if (realm.realm == masterRealm) { + return 'master-realm'; + } else { + return 'realm-management'; + } +} + +module.controller('RealmRolePermissionsCtrl', function($scope, $http, $route, $location, realm, role, RoleManagementPermissions, Client, Notifications, Dialog, RealmRoleRemover) { + console.log('RealmRolePermissionsCtrl'); + $scope.role = role; + $scope.realm = realm; + + $scope.remove = function() { + RealmRoleRemover.remove($scope.role, realm, Dialog, $location, Notifications); + }; + + RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { + $scope.permissions = data; + $scope.$watch('permissions.enabled', function(newVal, oldVal) { + if (newVal != oldVal) { + var param = {enabled: $scope.permissions.enabled}; + $scope.permissions= RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param); + } + }, true); + }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); +}); +module.controller('ClientRolePermissionsCtrl', function($scope, $http, $route, $location, realm, client, role, Client, RoleManagementPermissions, Client, Notifications) { + console.log('RealmRolePermissionsCtrl'); + $scope.client = client; + $scope.role = role; + $scope.realm = realm; + RoleManagementPermissions.get({realm: realm.realm, role: role.id}, function(data) { + $scope.permissions = data; + $scope.$watch('permissions.enabled', function(newVal, oldVal) { + if (newVal != oldVal) { + var param = {enabled: $scope.permissions.enabled}; + $scope.permissions = RoleManagementPermissions.update({realm: realm.realm, role:role.id}, param); + } + }, true); + }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); +}); + +module.controller('UsersPermissionsCtrl', function($scope, $http, $route, $location, realm, UsersManagementPermissions, Client, Notifications) { + console.log('UsersPermissionsCtrl'); + $scope.realm = realm; + var first = true; + UsersManagementPermissions.get({realm: realm.realm}, function(data) { + $scope.permissions = data; + $scope.$watch('permissions.enabled', function(newVal, oldVal) { + if (newVal != oldVal) { + var param = {enabled: $scope.permissions.enabled}; + $scope.permissions = UsersManagementPermissions.update({realm: realm.realm}, param); + + } + }, true); + }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); + + + + +}); + +module.controller('ClientPermissionsCtrl', function($scope, $http, $route, $location, realm, client, Client, ClientManagementPermissions, Notifications) { + $scope.client = client; + $scope.realm = realm; + ClientManagementPermissions.get({realm: realm.realm, client: client.id}, function(data) { + $scope.permissions = data; + $scope.$watch('permissions.enabled', function(newVal, oldVal) { + if (newVal != oldVal) { + var param = {enabled: $scope.permissions.enabled}; + $scope.permissions = ClientManagementPermissions.update({realm: realm.realm, client: client.id}, param); + } + }, true); + }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); +}); + +module.controller('IdentityProviderPermissionCtrl', function($scope, $http, $route, $location, realm, identityProvider, Client, IdentityProviderManagementPermissions, Notifications) { + $scope.identityProvider = identityProvider; + $scope.realm = realm; + IdentityProviderManagementPermissions.get({realm: realm.realm, alias: identityProvider.alias}, function(data) { + $scope.permissions = data; + $scope.$watch('permissions.enabled', function(newVal, oldVal) { + if (newVal != oldVal) { + var param = {enabled: $scope.permissions.enabled}; + $scope.permissions = IdentityProviderManagementPermissions.update({realm: realm.realm, alias: identityProvider.alias}, param); + } + }, true); + }); + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); +}); + +module.controller('GroupPermissionsCtrl', function($scope, $http, $route, $location, realm, group, GroupManagementPermissions, Client, Notifications) { + $scope.group = group; + $scope.realm = realm; + Client.query({realm: realm.realm, clientId: getManageClientId(realm)}, function(data) { + $scope.realmManagementClientId = data[0].id; + }); + GroupManagementPermissions.get({realm: realm.realm, group: group.id}, function(data) { + $scope.permissions = data; + $scope.$watch('permissions.enabled', function(newVal, oldVal) { + if (newVal != oldVal) { + var param = {enabled: $scope.permissions.enabled}; + $scope.permissions = GroupManagementPermissions.update({realm: realm.realm, group: group.id}, param); + } + }, true); + }); +}); \ No newline at end of file diff --git a/base/admin/resources/js/authz/authz-services.js b/base/admin/resources/js/authz/authz-services.js new file mode 100644 index 0000000..5e20472 --- /dev/null +++ b/base/admin/resources/js/authz/authz-services.js @@ -0,0 +1,218 @@ +module.factory('ResourceServer', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server', { + realm : '@realm', + client: '@client' + }, { + 'update' : {method : 'PUT'}, + 'import' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/import', method : 'POST'}, + 'settings' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/settings', method : 'GET'} + }); +}); + +module.factory('ResourceServerResource', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid', { + realm : '@realm', + client: '@client', + rsrid : '@rsrid' + }, { + 'update' : {method : 'PUT'}, + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/search', method : 'GET'}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid/scopes', method : 'GET', isArray: true}, + 'permissions' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid/permissions', method : 'GET', isArray: true} + }); +}); + +module.factory('ResourceServerScope', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id', { + realm : '@realm', + client: '@client', + id : '@id' + }, { + 'update' : {method : 'PUT'}, + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/search', method : 'GET'}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id/resources', method : 'GET', isArray: true}, + 'permissions' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id/permissions', method : 'GET', isArray: true}, + }); +}); + +module.factory('ResourceServerPolicy', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:type/:id', { + realm : '@realm', + client: '@client', + id : '@id', + type: '@type' + }, { + 'update' : {method : 'PUT'}, + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/search', method : 'GET'}, + 'associatedPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/associatedPolicies', method : 'GET', isArray: true}, + 'dependentPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/dependentPolicies', method : 'GET', isArray: true}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/scopes', method : 'GET', isArray: true}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/resources', method : 'GET', isArray: true} + }); +}); + +module.factory('ResourceServerPermission', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/permission/:type/:id', { + realm : '@realm', + client: '@client', + type: '@type', + id : '@id' + }, { + 'update' : {method : 'PUT'}, + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/permission/search', method : 'GET'}, + 'searchPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy', method : 'GET', isArray: true}, + 'associatedPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/associatedPolicies', method : 'GET', isArray: true}, + 'dependentPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/dependentPolicies', method : 'GET', isArray: true}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/permission/:id/scopes', method : 'GET', isArray: true}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/permission/:id/resources', method : 'GET', isArray: true} + }); +}); + +module.factory('PolicyProvider', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/providers', { + realm : '@realm', + client: '@client' + }); +}); + +module.service('AuthzDialog', function($modal) { + var dialog = {}; + + var openDialog = function(title, message, btns, template) { + var controller = function($scope, $modalInstance, $sce, title, message, btns) { + $scope.title = title; + $scope.message = $sce.trustAsHtml(message); + $scope.btns = btns; + + $scope.ok = function () { + $modalInstance.close(); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }; + + return $modal.open({ + templateUrl: resourceUrl + template, + controller: controller, + resolve: { + title: function() { + return title; + }, + message: function() { + return message; + }, + btns: function() { + return btns; + } + } + }).result; + } + + dialog.confirmDeleteWithMsg = function(name, type, msg, success) { + var title = 'Delete ' + type; + msg += 'Are you sure you want to permanently delete the ' + type + ' ' + name + ' ?'; + var btns = { + ok: { + label: 'Delete', + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + } + + openDialog(title, msg, btns, '/templates/authz/kc-authz-modal.html').then(success); + }; + + dialog.confirmDelete = function(name, type, success) { + var title = 'Delete ' + type; + var msg = 'Are you sure you want to permanently delete the ' + type + ' ' + name + ' ?'; + var btns = { + ok: { + label: 'Delete', + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + } + + openDialog(title, msg, btns, '/templates/authz/kc-authz-modal.html').then(success); + } + + return dialog; +}); + +module.factory('RoleManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/management/permissions', { + realm : '@realm', + role : '@role' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('UsersManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users-management-permissions', { + realm : '@realm' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('ClientManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/management/permissions', { + realm : '@realm', + client : '@client' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('IdentityProviderManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/management/permissions', { + realm : '@realm', + alias : '@alias' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('GroupManagementPermissions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:group/management/permissions', { + realm : '@realm', + group : '@group' + }, { + update: { + method: 'PUT' + } + }); +}); + +module.factory('policyViewState', [function () { + return { + model: { + state: {} + } + }; +}]); + +module.factory('viewState', [function () { + return { + model: { + state: {} + } + }; +}]); + diff --git a/base/admin/resources/js/controllers/clients.js b/base/admin/resources/js/controllers/clients.js new file mode 100755 index 0000000..04f68ea --- /dev/null +++ b/base/admin/resources/js/controllers/clients.js @@ -0,0 +1,3693 @@ +Array.prototype.remove = function(from, to) { + var rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); +}; + +module.controller('ClientTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeClient = function() { + Dialog.confirmDelete($scope.client.clientId, 'client', function() { + $scope.client.$remove({ + realm : Current.realm.realm, + client : $scope.client.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/clients"); + Notifications.success("The client has been deleted."); + }); + }); + }; +}); + +module.controller('ClientRoleListCtrl', function($scope, $route, realm, client, ClientRoleList, RoleById, Notifications, Dialog) { + $scope.realm = realm; + $scope.roles = []; + $scope.client = client; + + $scope.query = { + realm: realm.realm, + client: $scope.client.id, + search : null, + max : 20, + first : 0 + } + + $scope.$watch('query.search', function (newVal, oldVal) { + if($scope.query.search && $scope.query.search.length >= 3) { + $scope.firstPage(); + } + }, true); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.roles = ClientRoleList.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); + + $scope.removeRole = function(role) { + Dialog.confirmDelete(role.name, 'role', function() { + RoleById.remove({ + realm: realm.realm, + role: role.id + }, function () { + $route.reload(); + Notifications.success("The role has been deleted."); + }); + }); + }; +}); + +module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client, ClientRegistrationAccessToken, Notifications) { + $scope.realm = realm; + $scope.client = angular.copy(client); + $scope.clientAuthenticatorProviders = clientAuthenticatorProviders; + + var updateCurrentPartial = function(val) { + $scope.clientAuthenticatorConfigPartial; + switch(val) { + case 'client-secret': + $scope.clientAuthenticatorConfigPartial = 'client-credentials-secret.html'; + break; + case 'client-jwt': + $scope.clientAuthenticatorConfigPartial = 'client-credentials-jwt.html'; + break; + case 'client-secret-jwt': + $scope.clientAuthenticatorConfigPartial = 'client-credentials-secret-jwt.html'; + break; + case 'client-x509': + $scope.clientAuthenticatorConfigPartial = 'client-credentials-x509.html'; + break; + default: + $scope.currentAuthenticatorConfigProperties = clientConfigProperties[val]; + $scope.clientAuthenticatorConfigPartial = 'client-credentials-generic.html'; + break; + } + }; + + updateCurrentPartial(client.clientAuthenticatorType); + + $scope.$watch('client.clientAuthenticatorType', function() { + if (!angular.equals($scope.client.clientAuthenticatorType, client.clientAuthenticatorType)) { + + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.client, function() { + $scope.changed = false; + client = angular.copy($scope.client); + updateCurrentPartial(client.clientAuthenticatorType) + }); + + } + }, true); + + $scope.regenerateRegistrationAccessToken = function() { + var secret = ClientRegistrationAccessToken.update({ realm : $scope.realm.realm, client : $scope.client.id }, + function(data) { + Notifications.success('The registration access token has been updated.'); + $scope.client['registrationAccessToken'] = data.registrationAccessToken; + }, + function() { + Notifications.error('Failed to update the registration access token'); + } + ); + }; +}); + +module.controller('ClientSecretCtrl', function($scope, $location, Client, ClientSecret, Notifications) { + var secret = ClientSecret.get({ realm : $scope.realm.realm, client : $scope.client.id }, + function() { + $scope.secret = secret.value; + } + ); + + $scope.changePassword = function() { + var secret = ClientSecret.update({ realm : $scope.realm.realm, client : $scope.client.id }, + function() { + Notifications.success('The secret has been changed.'); + $scope.secret = secret.value; + }, + function() { + Notifications.error("The secret was not changed due to a problem."); + $scope.secret = "error"; + } + ); + }; + + $scope.tokenEndpointAuthSigningAlg = $scope.client.attributes['token.endpoint.auth.signing.alg']; + + $scope.switchChange = function() { + $scope.changed = true; + } + + $scope.save = function() { + $scope.client.attributes['token.endpoint.auth.signing.alg'] = $scope.tokenEndpointAuthSigningAlg; + + Client.update({ + realm : $scope.realm.realm, + client : $scope.client.id + }, $scope.client, function() { + $scope.changed = false; + $scope.clientCopy = angular.copy($scope.client); + Notifications.success("Client authentication configuration has been saved to the client."); + }); + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.cancel = function() { + $location.url("/realms/" + $scope.realm.realm + "/clients/" + $scope.client.id + "/credentials"); + }; +}); + +module.controller('ClientX509Ctrl', function($scope, $location, Client, Notifications) { + console.log('ClientX509Ctrl invoked'); + + $scope.clientCopy = angular.copy($scope.client); + $scope.changed = false; + + $scope.$watch('client', function() { + if (!angular.equals($scope.client, $scope.clientCopy)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + if (!$scope.client.attributes["x509.subjectdn"]) { + Notifications.error("The SubjectDN must not be empty."); + } else { + Client.update({ + realm : $scope.realm.realm, + client : $scope.client.id + }, $scope.client, function() { + $scope.changed = false; + $scope.clientCopy = angular.copy($scope.client); + Notifications.success("Client authentication configuration has been saved to the client."); + }, function() { + Notifications.error("The SubjectDN was not changed due to a problem."); + $scope.subjectdn = "error"; + }); + } + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.reset = function() { + $scope.client.attributes["x509.subjectdn"] = $scope.clientCopy.attributes["x509.subjectdn"]; + $location.url("/realms/" + $scope.realm.realm + "/clients/" + $scope.client.id + "/credentials"); + }; +}); + +module.controller('ClientSignedJWTCtrl', function($scope, $location, Client, ClientCertificate, Notifications, $route) { + var signingKeyInfo = ClientCertificate.get({ realm : $scope.realm.realm, client : $scope.client.id, attribute: 'jwt.credential' }, + function() { + $scope.signingKeyInfo = signingKeyInfo; + } + ); + + console.log('ClientSignedJWTCtrl invoked'); + + $scope.clientCopy = angular.copy($scope.client); + $scope.changed = false; + + $scope.$watch('client', function() { + if (!angular.equals($scope.client, $scope.clientCopy)) { + $scope.changed = true; + } + }, true); + + $scope.tokenEndpointAuthSigningAlg = $scope.client.attributes['token.endpoint.auth.signing.alg']; + + if ($scope.client.attributes["use.jwks.url"]) { + if ($scope.client.attributes["use.jwks.url"] == "true") { + $scope.useJwksUrl = true; + } else { + $scope.useJwksUrl = false; + } + } + + $scope.switchChange = function() { + $scope.changed = true; + } + + $scope.save = function() { + $scope.client.attributes['token.endpoint.auth.signing.alg'] = $scope.tokenEndpointAuthSigningAlg; + + if ($scope.useJwksUrl == true) { + $scope.client.attributes["use.jwks.url"] = "true"; + } else { + $scope.client.attributes["use.jwks.url"] = "false"; + } + + Client.update({ + realm : $scope.realm.realm, + client : $scope.client.id + }, $scope.client, function() { + $scope.changed = false; + $scope.clientCopy = angular.copy($scope.client); + Notifications.success("Client authentication configuration has been saved to the client."); + }); + }; + + $scope.importCertificate = function() { + $location.url("/realms/" + $scope.realm.realm + "/clients/" + $scope.client.id + "/credentials/client-jwt/Signing/import/jwt.credential"); + }; + + $scope.generateSigningKey = function() { + $location.url("/realms/" + $scope.realm.realm + "/clients/" + $scope.client.id + "/credentials/client-jwt/Signing/export/jwt.credential"); + }; + + $scope.reset = function() { + $route.reload(); + }; +}); + +module.controller('ClientGenericCredentialsCtrl', function($scope, $location, Client, Notifications) { + + console.log('ClientGenericCredentialsCtrl invoked'); + + $scope.clientCopy = angular.copy($scope.client); + $scope.changed = false; + + $scope.$watch('client', function() { + if (!angular.equals($scope.client, $scope.clientCopy)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + + Client.update({ + realm : $scope.realm.realm, + client : $scope.client.id + }, $scope.client, function() { + $scope.changed = false; + $scope.clientCopy = angular.copy($scope.client); + Notifications.success("Client authentication configuration has been saved to the client."); + }); + }; + + $scope.reset = function() { + $scope.client = angular.copy($scope.clientCopy); + $scope.changed = false; + }; +}); + +module.controller('ClientIdentityProviderCtrl', function($scope, $location, $route, realm, client, Client, $location, Notifications) { + $scope.realm = realm; + $scope.client = angular.copy(client); + var length = 0; + + if ($scope.client.identityProviders) { + length = $scope.client.identityProviders.length; + + for (i = 0; i < $scope.client.identityProviders.length; i++) { + var clientProvider = $scope.client.identityProviders[i]; + if (clientProvider.retrieveToken) { + clientProvider.retrieveToken = clientProvider.retrieveToken.toString(); + } + } + + } else { + $scope.client.identityProviders = []; + } + + $scope.identityProviders = []; + var providersMissingInClient = []; + + for (j = 0; j < realm.identityProviders.length; j++) { + var identityProvider = realm.identityProviders[j]; + var clientProvider = null; + + for (i = 0; i < $scope.client.identityProviders.length; i++) { + clientProvider = $scope.client.identityProviders[i]; + + if (clientProvider) { + + if (clientProvider.id == identityProvider.id) { + $scope.identityProviders[i] = {}; + $scope.identityProviders[i].identityProvider = identityProvider; + $scope.identityProviders[i].retrieveToken = clientProvider.retrieveToken; + break; + } + + clientProvider = null; + } + } + + if (clientProvider == null) { + providersMissingInClient.push(identityProvider); + } + } + + for (j = 0; j < providersMissingInClient.length; j++) { + var identityProvider = providersMissingInClient[j]; + + var currentProvider = {}; + currentProvider.identityProvider = identityProvider; + currentProvider.retrieveToken = "false"; + $scope.identityProviders.push(currentProvider); + + var currentClientProvider = {}; + currentClientProvider.id = identityProvider.id; + currentClientProvider.retrieveToken = "false"; + $scope.client.identityProviders.push(currentClientProvider); + } + + var oldCopy = angular.copy($scope.client); + + $scope.save = function() { + + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.client, function() { + $scope.changed = false; + $route.reload(); + Notifications.success("Your changes have been saved to the client."); + }); + }; + + $scope.reset = function() { + $scope.client = angular.copy(oldCopy); + $scope.changed = false; + }; + + $scope.$watch('client', function() { + if (!angular.equals($scope.client, oldCopy)) { + $scope.changed = true; + } + }, true); +}); + +module.controller('ClientSamlKeyCtrl', function($scope, $location, $http, $upload, realm, client, + ClientCertificate, ClientCertificateGenerate, + ClientCertificateDownload, Notifications) { + $scope.realm = realm; + $scope.client = client; + + var signingKeyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: 'saml.signing' }, + function() { + $scope.signingKeyInfo = signingKeyInfo; + } + ); + + $scope.generateSigningKey = function() { + var keyInfo = ClientCertificateGenerate.generate({ realm : realm.realm, client : client.id, attribute: 'saml.signing' }, + function() { + Notifications.success('Signing key has been regenerated.'); + $scope.signingKeyInfo = keyInfo; + }, + function() { + Notifications.error("Signing key was not regenerated."); + } + ); + }; + + $scope.importSigningKey = function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/saml/Signing/import/saml.signing"); + }; + + $scope.exportSigningKey = function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/saml/Signing/export/saml.signing"); + }; + + var encryptionKeyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: 'saml.encryption' }, + function() { + $scope.encryptionKeyInfo = encryptionKeyInfo; + } + ); + + $scope.generateEncryptionKey = function() { + var keyInfo = ClientCertificateGenerate.generate({ realm : realm.realm, client : client.id, attribute: 'saml.encryption' }, + function() { + Notifications.success('Encryption key has been regenerated.'); + $scope.encryptionKeyInfo = keyInfo; + }, + function() { + Notifications.error("Encryption key was not regenerated."); + } + ); + }; + + $scope.importEncryptionKey = function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/saml/Encryption/import/saml.encryption"); + }; + + $scope.exportEncryptionKey = function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/saml/Encryption/export/saml.encryption"); + }; + + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); +}); + +module.controller('ClientCertificateImportCtrl', function($scope, $location, $http, $upload, realm, client, callingContext, $routeParams, + ClientCertificate, ClientCertificateGenerate, + ClientCertificateDownload, Notifications) { + + console.log("callingContext: " + callingContext); + + var keyType = $routeParams.keyType; + var attribute = $routeParams.attribute; + $scope.realm = realm; + $scope.client = client; + $scope.keyType = keyType; + + if (callingContext == 'saml') { + var uploadUrl = authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/certificates/' + attribute + '/upload'; + var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/saml/keys"; + } else if (callingContext == 'jwt-credentials') { + var uploadUrl = authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/certificates/' + attribute + '/upload-certificate'; + var redirectLocation = "/realms/" + realm.realm + "/clients/" + client.id + "/credentials"; + } + + $scope.files = []; + + $scope.onFileSelect = function($files) { + $scope.files = $files; + }; + + $scope.cancel = function() { + $location.url(redirectLocation); + } + + $scope.keyFormats = [ + "JKS", + "PKCS12", + "Certificate PEM" + ]; + + if (callingContext == 'jwt-credentials') { + $scope.keyFormats.push('Public Key PEM'); + $scope.keyFormats.push('JSON Web Key Set'); + } + + $scope.hideKeystoreSettings = function() { + return $scope.uploadKeyFormat == 'Certificate PEM' || $scope.uploadKeyFormat == 'Public Key PEM' || $scope.uploadKeyFormat == 'JSON Web Key Set'; + } + + $scope.uploadKeyFormat = $scope.keyFormats[0]; + + $scope.uploadFile = function() { + //$files: an array of files selected, each file has name, size, and type. + for (var i = 0; i < $scope.files.length; i++) { + var $file = $scope.files[i]; + $scope.upload = $upload.upload({ + url: uploadUrl, + // method: POST or PUT, + // headers: {'headerKey': 'headerValue'}, withCredential: true, + data: {keystoreFormat: $scope.uploadKeyFormat, + keyAlias: $scope.uploadKeyAlias, + keyPassword: $scope.uploadKeyPassword, + storePassword: $scope.uploadStorePassword + }, + file: $file + /* set file formData name for 'Content-Desposition' header. Default: 'file' */ + //fileFormDataName: myFile, + /* customize how data is added to formData. See #40#issuecomment-28612000 for example */ + //formDataAppender: function(formData, key, val){} + }).then(function(data, status, headers) { + Notifications.success("Keystore uploaded successfully."); + $location.url(redirectLocation); + }) + //.then(success, error, progress); + } + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); +}); + +module.controller('ClientCertificateExportCtrl', function($scope, $location, $http, $upload, realm, client, callingContext, $routeParams, + ClientCertificate, ClientCertificateGenerate, + ClientCertificateDownload, Notifications) { + var keyType = $routeParams.keyType; + var attribute = $routeParams.attribute; + $scope.realm = realm; + $scope.client = client; + $scope.keyType = keyType; + + if (callingContext == 'saml') { + var downloadUrl = authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/certificates/' + attribute + '/download'; + var realmCertificate = true; + } else if (callingContext == 'jwt-credentials') { + var downloadUrl = authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/certificates/' + attribute + '/generate-and-download' + var realmCertificate = false; + } + + var jks = { + keyAlias: client.clientId, + realmAlias: realm.realm, + realmCertificate: realmCertificate + }; + + $scope.keyFormats = [ + "JKS", + "PKCS12" + ]; + + var keyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: attribute }, + function() { + $scope.keyInfo = keyInfo; + } + ); + $scope.jks = jks; + $scope.jks.format = $scope.keyFormats[0]; + + $scope.download = function() { + $http({ + url: downloadUrl, + method: 'POST', + responseType: 'arraybuffer', + data: $scope.jks, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/octet-stream' + } + }).then(function(response){ + var blob = new Blob([response.data], { + type: 'application/octet-stream' + }); + var ext = ".jks"; + if ($scope.jks.format == 'PKCS12') ext = ".p12"; + + if (callingContext == 'jwt-credentials') { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials"); + Notifications.success("New keypair and certificate generated successfully. Download keystore file") + } + + saveAs(blob, 'keystore' + ext); + }).catch(function(response) { + var errorMsg = 'Error downloading'; + try { + var error = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(response.data))); + errorMsg = error['error_description'] ? error['error_description'] : errorMsg; + } catch (err) { + } + Notifications.error(errorMsg); + }); + } + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials"); + } +}); + +module.controller('ClientSessionsCtrl', function($scope, realm, sessionCount, client, + ClientUserSessions) { + $scope.realm = realm; + $scope.count = sessionCount.count; + $scope.sessions = []; + $scope.client = client; + + $scope.page = 0; + + $scope.query = { + realm : realm.realm, + client: $scope.client.id, + max : 5, + first : 0 + } + + $scope.firstPage = function() { + $scope.query.first = 0; + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.loadUsers(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.loadUsers(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.loadUsers(); + } + + $scope.toDate = function(val) { + return new Date(val); + }; + + $scope.loadUsers = function() { + ClientUserSessions.query($scope.query, function(updated) { + $scope.sessions = updated; + }) + }; +}); + +module.controller('ClientOfflineSessionsCtrl', function($scope, realm, offlineSessionCount, client, + ClientOfflineSessions) { + $scope.realm = realm; + $scope.count = offlineSessionCount.count; + $scope.sessions = []; + $scope.client = client; + + $scope.page = 0; + + $scope.query = { + realm : realm.realm, + client: $scope.client.id, + max : 5, + first : 0 + } + + $scope.firstPage = function() { + $scope.query.first = 0; + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.loadUsers(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.loadUsers(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.loadUsers(); + } + + $scope.toDate = function(val) { + return new Date(val); + }; + + $scope.loadUsers = function() { + ClientOfflineSessions.query($scope.query, function(updated) { + $scope.sessions = updated; + }) + }; +}); + +module.controller('ClientRoleDetailCtrl', function($scope, $route, realm, client, role, roles, Client, + Role, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, + $http, $location, Dialog, Notifications, ComponentUtils) { + $scope.realm = realm; + $scope.client = client; + $scope.role = angular.copy(role); + $scope.create = !role.name; + + $scope.changed = $scope.create; + + $scope.save = function() { + convertAttributeValuesToLists(); + if ($scope.create) { + ClientRole.save({ + realm: realm.realm, + client : client.id + }, $scope.role, function (data, headers) { + $scope.changed = false; + convertAttributeValuesToString($scope.role); + role = angular.copy($scope.role); + + ClientRole.get({ realm: realm.realm, client : client.id, role: role.name }, function(role) { + var id = role.id; + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/roles/" + id); + Notifications.success("The role has been created."); + }); + }); + } else { + $scope.update(); + } + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.role.name, 'role', function() { + $scope.role.$remove({ + realm : realm.realm, + client : client.id, + role : $scope.role.id + }, function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/roles"); + Notifications.success("The role has been deleted."); + }); + }); + }; + + $scope.cancel = function () { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/roles"); + }; + + $scope.addAttribute = function() { + $scope.role.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.role.attributes[key]; + } + + function convertAttributeValuesToLists() { + var attrs = $scope.role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + var attrVals = attrs[attribute].split("##"); + attrs[attribute] = attrVals; + } + } + } + + function convertAttributeValuesToString(role) { + var attrs = role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + var attrVals = attrs[attribute].join("##"); + attrs[attribute] = attrVals; + } + } + } + + roleControl($scope, $route, realm, role, roles, Client, + ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, + $http, $location, Notifications, Dialog, ComponentUtils); + +}); + +module.controller('ClientRoleMembersCtrl', function($scope, realm, client, role, ClientRoleMembership, Dialog, Notifications, $location) { + $scope.realm = realm; + $scope.page = 0; + $scope.role = role; + $scope.client = client; + + $scope.query = { + realm: realm.realm, + role: role.name, + client: client.id, + max : 5, + first : 0 + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.users = ClientRoleMembership.query($scope.query, function() { + console.log('search loaded'); + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); +}); + +module.controller('ClientImportCtrl', function($scope, $location, $upload, realm, serverInfo, Notifications) { + + $scope.realm = realm; + + $scope.files = []; + + $scope.onFileSelect = function($files) { + $scope.files = $files; + }; + + $scope.clearFileSelect = function() { + $scope.files = null; + } + + $scope.uploadFile = function() { + //$files: an array of files selected, each file has name, size, and type. + for (var i = 0; i < $scope.files.length; i++) { + var $file = $scope.files[i]; + $scope.upload = $upload.upload({ + url: authUrl + '/admin/realms/' + realm.realm + '/client-importers/' + $scope.configFormat.id + '/upload', + // method: POST or PUT, + // headers: {'headerKey': 'headerValue'}, withCredential: true, + data: {myObj: ""}, + file: $file + /* set file formData name for 'Content-Desposition' header. Default: 'file' */ + //fileFormDataName: myFile, + /* customize how data is added to formData. See #40#issuecomment-28612000 for example */ + //formDataAppender: function(formData, key, val){} + }).success(function(data, status, headers) { + Notifications.success("Uploaded successfully."); + $location.url("/realms/" + realm.realm + "/clients"); + }) + .error(function() { + Notifications.error("The file can not be uploaded. Please verify the file."); + + }); + //.then(success, error, progress); + } + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); +}); + + +module.controller('ClientListCtrl', function($scope, realm, Client, ClientListSearchState, $route, Dialog, Notifications) { + $scope.init = function() { + $scope.realm = realm; + $scope.searchLoaded = true; + + ClientListSearchState.query.realm = realm.realm; + $scope.query = ClientListSearchState.query; + + if (!ClientListSearchState.isFirstSearch) { + $scope.searchQuery(); + } else { + $scope.query.clientId = null; + $scope.firstPage(); + } + }; + + $scope.searchQuery = function() { + console.log("query.search: ", $scope.query); + $scope.searchLoaded = false; + + $scope.clients = Client.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + ClientListSearchState.isFirstSearch = false; + }); + }; + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.removeClient = function(client) { + Dialog.confirmDelete(client.clientId, 'client', function() { + Client.remove({ + realm : realm.realm, + client : client.id + }, function() { + $route.reload(); + Notifications.success("The client has been deleted."); + }); + }); + }; + + $scope.exportClient = function(client) { + var clientCopy = angular.copy(client); + delete clientCopy.id; + + if (clientCopy.protocolMappers) { + for (var i = 0; i < clientCopy.protocolMappers.length; i++) { + delete clientCopy.protocolMappers[i].id; + } + } + + saveAs(new Blob([angular.toJson(clientCopy, 4)], { type: 'application/json' }), clientCopy.clientId + '.json'); + } +}); + +module.controller('ClientInstallationCtrl', function($scope, realm, client, serverInfo, ClientInstallation,$http, $routeParams) { + $scope.realm = realm; + $scope.client = client; + $scope.installation = null; + $scope.download = null; + $scope.configFormat = null; + $scope.filename = null; + + var protocol = client.protocol; + if (!protocol) protocol = 'openid-connect'; + $scope.configFormats = serverInfo.clientInstallations[protocol]; + console.log('configFormats.length: ' + $scope.configFormats.length); + + $scope.changeFormat = function() { + var url = ClientInstallation.url({ realm: $routeParams.realm, client: $routeParams.client, provider: $scope.configFormat.id }); + if ($scope.configFormat.mediaType == 'application/zip') { + $http({ + url: url, + method: 'GET', + responseType: 'arraybuffer', + cache: false + }).then(function(response) { + var installation = response.data; + $scope.installation = installation; + } + ); + } else { + $http.get(url).then(function (response) { + var installation = response.data; + if ($scope.configFormat.mediaType == 'application/json') { + installation = angular.fromJson(response.data); + installation = angular.toJson(installation, true); + } + $scope.installation = installation; + }); + } + + }; + $scope.download = function() { + saveAs(new Blob([$scope.installation], { type: $scope.configFormat.mediaType }), $scope.configFormat.filename); + } +}); + + +module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $route, serverInfo, Client, ClientDescriptionConverter, Components, ClientStorageOperations, $location, $modal, Dialog, Notifications, TimeUnit2) { + $scope.serverInfo = serverInfo; + $scope.flows = []; + $scope.clientFlows = []; + var emptyFlow = { + id: "", + alias: "" + } + for (var i=0 ; i 0) { + $scope.client.requestUris = $scope.client.attributes["request.uris"].split("##"); + } else { + $scope.client.requestUris = []; + } + } + + if (!$scope.create) { + $scope.client = client; + updateProperties(); + + $scope.clientEdit = angular.copy(client); + } + + + $scope.samlIdpInitiatedUrl = function(ssoName) { + return encodeURI($location.absUrl().replace(/\/admin.*/, "/realms/") + realm.realm + "/protocol/saml/clients/") + encodeURIComponent(ssoName) + } + + $scope.importFile = function(fileContent){ + console.debug(fileContent); + ClientDescriptionConverter.save({ + realm: realm.realm + }, fileContent, function (data) { + $scope.client = data; + updateProperties(); + $scope.importing = true; + + $scope.clientEdit = angular.copy(client); + }); + }; + + $scope.viewImportDetails = function() { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-object.html', + controller: 'ObjectModalCtrl', + resolve: { + object: function () { + return $scope.client; + } + } + }) + }; + + $scope.switchChange = function() { + $scope.changed = true; + } + + $scope.changeAccessType = function() { + if ($scope.accessType == "confidential") { + $scope.clientEdit.bearerOnly = false; + $scope.clientEdit.publicClient = false; + } else if ($scope.accessType == "public") { + $scope.clientEdit.bearerOnly = false; + $scope.clientEdit.publicClient = true; + } else if ($scope.accessType == "bearer-only") { + $scope.clientEdit.bearerOnly = true; + $scope.clientEdit.publicClient = false; + $scope.clientEdit.alwaysDisplayInConsole = false; + } + }; + + $scope.changeProtocol = function() { + if ($scope.protocol == "openid-connect") { + $scope.clientEdit.protocol = "openid-connect"; + } else if ($scope.protocol == "saml") { + $scope.clientEdit.protocol = "saml"; + } + }; + + $scope.changeAlgorithm = function() { + $scope.clientEdit.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm; + }; + + $scope.changeNameIdFormat = function() { + $scope.clientEdit.attributes['saml_name_id_format'] = $scope.nameIdFormat; + }; + + $scope.changeSamlSigKeyNameTranformer = function() { + $scope.clientEdit.attributes['saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer'] = $scope.samlXmlKeyNameTranformer; + }; + + $scope.changeAccessTokenSignedResponseAlg = function() { + $scope.clientEdit.attributes['access.token.signed.response.alg'] = $scope.accessTokenSignedResponseAlg; + }; + + $scope.changeIdTokenSignedResponseAlg = function() { + $scope.clientEdit.attributes['id.token.signed.response.alg'] = $scope.idTokenSignedResponseAlg; + }; + + $scope.changeIdTokenEncryptedResponseAlg = function() { + $scope.clientEdit.attributes['id.token.encrypted.response.alg'] = $scope.idTokenEncryptedResponseAlg; + }; + + $scope.changeIdTokenEncryptedResponseEnc = function() { + $scope.clientEdit.attributes['id.token.encrypted.response.enc'] = $scope.idTokenEncryptedResponseEnc; + }; + + $scope.changeUserInfoSignedResponseAlg = function() { + if ($scope.userInfoSignedResponseAlg === 'unsigned') { + $scope.clientEdit.attributes['user.info.response.signature.alg'] = null; + } else { + $scope.clientEdit.attributes['user.info.response.signature.alg'] = $scope.userInfoSignedResponseAlg; + } + }; + + $scope.changeRequestObjectSignatureAlg = function() { + if ($scope.requestObjectSignatureAlg === 'any') { + $scope.clientEdit.attributes['request.object.signature.alg'] = null; + } else { + $scope.clientEdit.attributes['request.object.signature.alg'] = $scope.requestObjectSignatureAlg; + } + }; + + $scope.changeRequestObjectRequired = function() { + if ($scope.requestObjectRequired === 'not required') { + $scope.clientEdit.attributes['request.object.required'] = null; + } else { + $scope.clientEdit.attributes['request.object.required'] = $scope.requestObjectRequired; + } + }; + + $scope.changePkceCodeChallengeMethod = function() { + $scope.clientEdit.attributes['pkce.code.challenge.method'] = $scope.pkceCodeChallengeMethod; + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + function isChanged() { + if (!angular.equals($scope.client, $scope.clientEdit)) { + return true; + } + if ($scope.newRedirectUri && $scope.newRedirectUri.length > 0) { + return true; + } + if ($scope.newWebOrigin && $scope.newWebOrigin.length > 0) { + return true; + } + if ($scope.newRequestUri && $scope.newRequestUri.length > 0) { + return true; + } + return false; + } + + $scope.updateTimeouts = function() { + if ($scope.accessTokenLifespan.time) { + if ($scope.accessTokenLifespan.time === -1) { + $scope.clientEdit.attributes['access.token.lifespan'] = -1; + } else { + $scope.clientEdit.attributes['access.token.lifespan'] = $scope.accessTokenLifespan.toSeconds(); + } + } else { + $scope.clientEdit.attributes['access.token.lifespan'] = null; + } + } + + $scope.updateAssertionLifespan = function() { + if ($scope.samlAssertionLifespan.time) { + $scope.clientEdit.attributes['saml.assertion.lifespan'] = $scope.samlAssertionLifespan.toSeconds(); + } else { + $scope.clientEdit.attributes['saml.assertion.lifespan'] = null; + } + } + + $scope.updateClientSessionIdleTimeout = function() { + if ($scope.clientSessionIdleTimeout.time) { + $scope.clientEdit.attributes['client.session.idle.timeout'] = $scope.clientSessionIdleTimeout.toSeconds(); + } else { + $scope.clientEdit.attributes['client.session.idle.timeout'] = null; + } + } + + $scope.updateClientSessionMaxLifespan = function() { + if ($scope.clientSessionMaxLifespan.time) { + $scope.clientEdit.attributes['client.session.max.lifespan'] = $scope.clientSessionMaxLifespan.toSeconds(); + } else { + $scope.clientEdit.attributes['client.session.max.lifespan'] = null; + } + } + + $scope.updateClientOfflineSessionIdleTimeout = function() { + if ($scope.clientOfflineSessionIdleTimeout.time) { + $scope.clientEdit.attributes['client.offline.session.idle.timeout'] = $scope.clientOfflineSessionIdleTimeout.toSeconds(); + } else { + $scope.clientEdit.attributes['client.offline.session.idle.timeout'] = null; + } + } + + $scope.updateClientOfflineSessionMaxLifespan = function() { + if ($scope.clientOfflineSessionMaxLifespan.time) { + $scope.clientEdit.attributes['client.offline.session.max.lifespan'] = $scope.clientOfflineSessionMaxLifespan.toSeconds(); + } else { + $scope.clientEdit.attributes['client.offline.session.max.lifespan'] = null; + } + } + + $scope.updateOauth2DeviceCodeLifespan = function() { + if ($scope.oauth2DeviceCodeLifespan.time) { + $scope.clientEdit.attributes['oauth2.device.code.lifespan'] = $scope.oauth2DeviceCodeLifespan.toSeconds(); + } else { + $scope.clientEdit.attributes['oauth2.device.code.lifespan'] = null; + } + } + + $scope.updateOauth2DevicePollingInterval = function() { + if ($scope.oauth2DevicePollingInterval) { + $scope.clientEdit.attributes['oauth2.device.polling.interval'] = $scope.oauth2DevicePollingInterval; + } else { + $scope.clientEdit.attributes['oauth2.device.polling.interval'] = null; + } + } + + function configureAuthorizationServices() { + if ($scope.clientEdit.authorizationServicesEnabled) { + if ($scope.accessType == 'public') { + $scope.accessType = 'confidential'; + } + $scope.clientEdit.publicClient = false; + $scope.clientEdit.serviceAccountsEnabled = true; + } else if ($scope.clientEdit.bearerOnly) { + $scope.clientEdit.serviceAccountsEnabled = false; + } + if ($scope.client.authorizationServicesEnabled && !$scope.clientEdit.authorizationServicesEnabled) { + Dialog.confirm("Disable Authorization Settings", "Are you sure you want to disable authorization ? Once you save your changes, all authorization settings associated with this client will be removed. This operation can not be reverted.", function () { + }, function () { + $scope.clientEdit.authorizationServicesEnabled = true; + }); + } + } + + $scope.$watch('clientEdit', function() { + $scope.changed = isChanged(); + configureAuthorizationServices(); + }, true); + + $scope.$watch('newRedirectUri', function() { + $scope.changed = isChanged(); + }, true); + + + $scope.$watch('newWebOrigin', function() { + $scope.changed = isChanged(); + }, true); + + $scope.$watch('newRequestUri', function() { + $scope.changed = isChanged(); + }, true); + + $scope.deleteWebOrigin = function(index) { + $scope.clientEdit.webOrigins.splice(index, 1); + } + $scope.addWebOrigin = function() { + $scope.clientEdit.webOrigins.push($scope.newWebOrigin); + $scope.newWebOrigin = ""; + } + $scope.deleteRequestUri = function(index) { + $scope.clientEdit.requestUris.splice(index, 1); + } + $scope.addRequestUri = function() { + $scope.clientEdit.requestUris.push($scope.newRequestUri); + $scope.newRequestUri = ""; + } + $scope.deleteRedirectUri = function(index) { + $scope.clientEdit.redirectUris.splice(index, 1); + } + + $scope.addRedirectUri = function() { + $scope.clientEdit.redirectUris.push($scope.newRedirectUri); + $scope.newRedirectUri = ""; + } + + $scope.save = function() { + if ($scope.newRedirectUri && $scope.newRedirectUri.length > 0) { + $scope.addRedirectUri(); + } + + if ($scope.newWebOrigin && $scope.newWebOrigin.length > 0) { + $scope.addWebOrigin(); + } + + if ($scope.newRequestUri && $scope.newRequestUri.length > 0) { + $scope.addRequestUri(); + } + if ($scope.clientEdit.requestUris && $scope.clientEdit.requestUris.length > 0) { + $scope.clientEdit.attributes["request.uris"] = $scope.clientEdit.requestUris.join("##"); + } else { + $scope.clientEdit.attributes["request.uris"] = null; + } + delete $scope.clientEdit.requestUris; + + if ($scope.samlArtifactBinding == true) { + $scope.clientEdit.attributes["saml.artifact.binding"] = "true"; + } else { + $scope.clientEdit.attributes["saml.artifact.binding"] = "false"; + } + if ($scope.samlServerSignature == true) { + $scope.clientEdit.attributes["saml.server.signature"] = "true"; + } else { + $scope.clientEdit.attributes["saml.server.signature"] = "false"; + } + if ($scope.samlServerSignatureEnableKeyInfoExtension == true) { + $scope.clientEdit.attributes["saml.server.signature.keyinfo.ext"] = "true"; + } else { + $scope.clientEdit.attributes["saml.server.signature.keyinfo.ext"] = "false"; + } + if ($scope.samlAssertionSignature == true) { + $scope.clientEdit.attributes["saml.assertion.signature"] = "true"; + } else { + $scope.clientEdit.attributes["saml.assertion.signature"] = "false"; + } + if ($scope.samlClientSignature == true) { + $scope.clientEdit.attributes["saml.client.signature"] = "true"; + } else { + $scope.clientEdit.attributes["saml.client.signature"] = "false"; + + } + if ($scope.samlEncrypt == true) { + $scope.clientEdit.attributes["saml.encrypt"] = "true"; + } else { + $scope.clientEdit.attributes["saml.encrypt"] = "false"; + + } + if ($scope.samlAuthnStatement == true) { + $scope.clientEdit.attributes["saml.authnstatement"] = "true"; + } else { + $scope.clientEdit.attributes["saml.authnstatement"] = "false"; + + } + if ($scope.samlOneTimeUseCondition == true) { + $scope.clientEdit.attributes["saml.onetimeuse.condition"] = "true"; + } else { + $scope.clientEdit.attributes["saml.onetimeuse.condition"] = "false"; + + } + if ($scope.samlForceNameIdFormat == true) { + $scope.clientEdit.attributes["saml_force_name_id_format"] = "true"; + } else { + $scope.clientEdit.attributes["saml_force_name_id_format"] = "false"; + + } + if ($scope.samlMultiValuedRoles == true) { + $scope.clientEdit.attributes["saml.multivalued.roles"] = "true"; + } else { + $scope.clientEdit.attributes["saml.multivalued.roles"] = "false"; + + } + if ($scope.samlForcePostBinding == true) { + $scope.clientEdit.attributes["saml.force.post.binding"] = "true"; + } else { + $scope.clientEdit.attributes["saml.force.post.binding"] = "false"; + + } + + if ($scope.excludeSessionStateFromAuthResponse == true) { + $scope.clientEdit.attributes["exclude.session.state.from.auth.response"] = "true"; + } else { + $scope.clientEdit.attributes["exclude.session.state.from.auth.response"] = "false"; + + } + + if ($scope.oauth2DeviceAuthorizationGrantEnabled == true) { + $scope.clientEdit.attributes["oauth2.device.authorization.grant.enabled"] = "true"; + } else { + $scope.clientEdit.attributes["oauth2.device.authorization.grant.enabled"] = "false"; + } + + if ($scope.oidcCibaGrantEnabled == true) { + $scope.clientEdit.attributes["oidc.ciba.grant.enabled"] = "true"; + } else { + $scope.clientEdit.attributes["oidc.ciba.grant.enabled"] = "false"; + } + + if ($scope.useRefreshTokens == true) { + $scope.clientEdit.attributes["use.refresh.tokens"] = "true"; + } else { + $scope.clientEdit.attributes["use.refresh.tokens"] = "false"; + } + + // KEYCLOAK-6771 Certificate Bound Token + // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-3 + if ($scope.tlsClientCertificateBoundAccessTokens == true) { + $scope.clientEdit.attributes["tls.client.certificate.bound.access.tokens"] = "true"; + } else { + $scope.clientEdit.attributes["tls.client.certificate.bound.access.tokens"] = "false"; + } + + // KEYCLOAK-9551 Client Credentials Grant generates refresh token + // https://tools.ietf.org/html/rfc6749#section-4.4.3 + if ($scope.useRefreshTokenForClientCredentialsGrant === true) { + $scope.clientEdit.attributes["client_credentials.use_refresh_token"] = "true"; + } else { + $scope.clientEdit.attributes["client_credentials.use_refresh_token"] = "false"; + } + + if ($scope.displayOnConsentScreen == true) { + $scope.clientEdit.attributes["display.on.consent.screen"] = "true"; + } else { + $scope.clientEdit.attributes["display.on.consent.screen"] = "false"; + } + + if ($scope.backchannelLogoutSessionRequired == true) { + $scope.clientEdit.attributes["backchannel.logout.session.required"] = "true"; + } else { + $scope.clientEdit.attributes["backchannel.logout.session.required"] = "false"; + } + + if ($scope.backchannelLogoutRevokeOfflineSessions == true) { + $scope.clientEdit.attributes["backchannel.logout.revoke.offline.tokens"] = "true"; + } else { + $scope.clientEdit.attributes["backchannel.logout.revoke.offline.tokens"] = "false"; + } + + $scope.clientEdit.protocol = $scope.protocol; + $scope.clientEdit.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm; + $scope.clientEdit.attributes['saml_name_id_format'] = $scope.nameIdFormat; + + if ($scope.clientEdit.protocol != 'saml' && !$scope.clientEdit.bearerOnly && ($scope.clientEdit.standardFlowEnabled || $scope.clientEdit.implicitFlowEnabled) && (!$scope.clientEdit.redirectUris || $scope.clientEdit.redirectUris.length == 0)) { + Notifications.error("You must specify at least one redirect uri"); + } else { + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.clientEdit, function() { + $route.reload(); + Notifications.success("Your changes have been saved to the client."); + }); + } + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/clients"); + }; +}); + +module.controller('CreateClientCtrl', function($scope, realm, client, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) { + $scope.protocols = serverInfo.listProviderIds('login-protocol'); + $scope.create = true; + + $scope.realm = realm; + + $scope.client = { + enabled: true, + attributes: {} + }; + $scope.client.redirectUris = []; + $scope.protocol = $scope.protocols[0]; + + + $scope.importFile = function(fileContent){ + console.debug(fileContent); + ClientDescriptionConverter.save({ + realm: realm.realm + }, fileContent, function (data) { + $scope.client = data; + if (data.protocol) { + $scope.protocol = data.protocol; + } + $scope.importing = true; + }); + }; + + $scope.viewImportDetails = function() { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-object.html', + controller: 'ObjectModalCtrl', + resolve: { + object: function () { + return $scope.client; + } + } + }) + }; + + $scope.switchChange = function() { + $scope.changed = true; + } + + $scope.changeProtocol = function() { + if ($scope.protocol == "openid-connect") { + $scope.client.protocol = "openid-connect"; + } else if ($scope.protocol == "saml") { + $scope.client.protocol = "saml"; + } + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + function isChanged() { + if (!angular.equals($scope.client, client)) { + return true; + } + return false; + } + + $scope.$watch('client', function() { + $scope.changed = isChanged(); + }, true); + + + $scope.save = function() { + $scope.client.protocol = $scope.protocol; + + Client.save({ + realm: realm.realm, + client: '' + }, $scope.client, function (data, headers) { + $scope.changed = false; + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + "/clients/" + id); + Notifications.success("The client has been created."); + }); + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/clients"); + }; +}); + +module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, $route, client, clients, Notifications, + Client, ClientScope, + ClientRealmScopeMapping, ClientClientScopeMapping, ClientRole, + ClientAvailableRealmScopeMapping, ClientAvailableClientScopeMapping, + ClientCompositeRealmScopeMapping, ClientCompositeClientScopeMapping) { + $scope.realm = realm; + $scope.client = angular.copy(client); + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.clients = clients; + $scope.clientRoles = []; + $scope.clientComposite = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + $scope.dummymodel = []; + + $scope.hideRoleSelector = function() { + return $scope.client.fullScopeAllowed; + } + + $scope.changeFlag = function() { + console.log('changeFlag'); + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.client, function() { + $scope.changed = false; + client = angular.copy($scope.client); + updateRealmRoles(); + Notifications.success("Scope mappings updated."); + }); + } + + + $scope.selectedClient = null; + + $scope.selectClient = function(client) { + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } + + $scope.selectedClient = client; + updateClientRoles(); + } + + function updateRealmRoles() { + $scope.realmRoles = ClientAvailableRealmScopeMapping.query({realm : realm.realm, client : client.id}); + $scope.realmMappings = ClientRealmScopeMapping.query({realm : realm.realm, client : client.id}); + $scope.realmComposite = ClientCompositeRealmScopeMapping.query({realm : realm.realm, client : client.id}); + } + + function updateClientRoles() { + if ($scope.selectedClient) { + $scope.clientRoles = ClientAvailableClientScopeMapping.query({realm : realm.realm, client : client.id, targetClient : $scope.selectedClient.id}); + $scope.clientMappings = ClientClientScopeMapping.query({realm : realm.realm, client : client.id, targetClient : $scope.selectedClient.id}); + $scope.clientComposite = ClientCompositeClientScopeMapping.query({realm : realm.realm, client : client.id, targetClient : $scope.selectedClient.id}); + } else { + $scope.clientRoles = null; + $scope.clientMappings = null; + $scope.clientComposite = null; + } + } + + $scope.addRealmRole = function() { + $scope.selectedRealmRolesToAdd = JSON.parse('[' + $scope.selectedRealmRoles + ']'); + $scope.selectedRealmRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/scope-mappings/realm', + $scope.selectedRealmRolesToAdd).then(function() { + updateRealmRoles(); + $scope.selectedRealmRolesToAdd = []; + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.deleteRealmRole = function() { + $scope.selectedRealmMappingsToRemove = JSON.parse('[' + $scope.selectedRealmMappings + ']'); + $scope.selectedRealmMappings = []; + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/scope-mappings/realm', + {data : $scope.selectedRealmMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function () { + updateRealmRoles(); + $scope.selectedRealmMappingsToRemove = []; + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.addClientRole = function() { + $scope.selectedClientRolesToAdd = JSON.parse('[' + $scope.selectedClientRoles + ']'); + $scope.selectedClientRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/scope-mappings/clients/' + $scope.selectedClient.id, + $scope.selectedClientRolesToAdd).then(function () { + updateClientRoles(); + $scope.selectedClientRolesToAdd = []; + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.deleteClientRole = function() { + $scope.selectedClientMappingsToRemove = JSON.parse('[' + $scope.selectedClientMappings + ']'); + $scope.selectedClientMappings = []; + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/scope-mappings/clients/' + $scope.selectedClient.id, + {data : $scope.selectedClientMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function () { + updateClientRoles(); + $scope.selectedClientMappingsToRemove = []; + Notifications.success("Scope mappings updated."); + }); + }; + + clientSelectControl($scope, $route.current.params.realm, Client); + updateRealmRoles(); +}); + +module.controller('ClientRevocationCtrl', function($scope, realm, client, Client, ClientPushRevocation, $location, Dialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + + var setNotBefore = function() { + if ($scope.client.notBefore == 0) { + $scope.notBefore = "None"; + } else { + $scope.notBefore = new Date($scope.client.notBefore * 1000); + } + }; + + setNotBefore(); + + var refresh = function() { + Client.get({ realm : realm.realm, client: $scope.client.id }, function(updated) { + $scope.client = updated; + setNotBefore(); + }) + + }; + + $scope.clear = function() { + $scope.client.notBefore = 0; + Client.update({ realm : realm.realm, client: client.id}, $scope.client, function () { + $scope.notBefore = "None"; + Notifications.success('Not Before cleared for client.'); + refresh(); + }); + } + $scope.setNotBeforeNow = function() { + $scope.client.notBefore = new Date().getTime()/1000; + Client.update({ realm : realm.realm, client: $scope.client.id}, $scope.client, function () { + Notifications.success('Not Before set for client.'); + refresh(); + }); + } + $scope.pushRevocation = function() { + ClientPushRevocation.save({realm : realm.realm, client: $scope.client.id}, function (globalReqResult) { + var successCount = globalReqResult.successRequests ? globalReqResult.successRequests.length : 0; + var failedCount = globalReqResult.failedRequests ? globalReqResult.failedRequests.length : 0; + + if (successCount==0 && failedCount==0) { + Notifications.warn('No push sent. No admin URI configured or no registered cluster nodes available'); + } else if (failedCount > 0) { + var msgStart = successCount>0 ? 'Successfully push notBefore to: ' + globalReqResult.successRequests + ' . ' : ''; + Notifications.error(msgStart + 'Failed to push notBefore to: ' + globalReqResult.failedRequests + '. Verify availability of failed hosts and try again'); + } else { + Notifications.success('Successfully push notBefore to: ' + globalReqResult.successRequests); + } + }); + } + +}); + +module.controller('ClientClusteringCtrl', function($scope, client, Client, ClientTestNodesAvailable, ClientClusterNode, realm, $location, $route, Dialog, Notifications, TimeUnit) { + $scope.client = client; + $scope.realm = realm; + + var oldCopy = angular.copy($scope.client); + $scope.changed = false; + + $scope.$watch('client', function() { + if (!angular.equals($scope.client, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.client.nodeReRegistrationTimeoutUnit = TimeUnit.autoUnit(client.nodeReRegistrationTimeout); + $scope.client.nodeReRegistrationTimeout = TimeUnit.toUnit(client.nodeReRegistrationTimeout, $scope.client.nodeReRegistrationTimeoutUnit); + + $scope.save = function() { + var clientCopy = angular.copy($scope.client); + delete clientCopy['nodeReRegistrationTimeoutUnit']; + clientCopy.nodeReRegistrationTimeout = TimeUnit.toSeconds($scope.client.nodeReRegistrationTimeout, $scope.client.nodeReRegistrationTimeoutUnit) + Client.update({ realm : realm.realm, client : client.id }, clientCopy, function () { + $route.reload(); + Notifications.success('Your changes have been saved to the client.'); + }); + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.testNodesAvailable = function() { + ClientTestNodesAvailable.get({ realm : realm.realm, client : client.id }, function(globalReqResult) { + $route.reload(); + + var successCount = globalReqResult.successRequests ? globalReqResult.successRequests.length : 0; + var failedCount = globalReqResult.failedRequests ? globalReqResult.failedRequests.length : 0; + + if (successCount==0 && failedCount==0) { + Notifications.warn('No requests sent. No admin URI configured or no registered cluster nodes available'); + } else if (failedCount > 0) { + var msgStart = successCount>0 ? 'Successfully verify availability for ' + globalReqResult.successRequests + ' . ' : ''; + Notifications.error(msgStart + 'Failed to verify availability for: ' + globalReqResult.failedRequests + '. Fix or unregister failed cluster nodes and try again'); + } else { + Notifications.success('Successfully sent requests to: ' + globalReqResult.successRequests); + } + }); + }; + + if (client.registeredNodes) { + var nodeRegistrations = []; + for (node in client.registeredNodes) { + reg = { + host: node, + lastRegistration: new Date(client.registeredNodes[node] * 1000) + } + nodeRegistrations.push(reg); + } + + $scope.nodeRegistrations = nodeRegistrations; + }; + + $scope.removeNode = function(node) { + Dialog.confirmDelete(node.host, 'node', function() { + ClientClusterNode.remove({ realm : realm.realm, client : client.id , node: node.host }, function() { + Notifications.success('Node ' + node.host + ' unregistered successfully.'); + $route.reload(); + }); + }); + }; +}); + +module.controller('ClientClusteringNodeCtrl', function($scope, client, Client, ClientClusterNode, realm, + $location, $routeParams, Notifications, Dialog) { + $scope.client = client; + $scope.realm = realm; + $scope.create = !$routeParams.node; + + $scope.save = function() { + ClientClusterNode.save({ realm : realm.realm, client : client.id , node: $scope.node.host }, function() { + Notifications.success('Node ' + $scope.node.host + ' registered successfully.'); + $location.url('/realms/' + realm.realm + '/clients/' + client.id + '/clustering'); + }); + } + + $scope.unregisterNode = function() { + Dialog.confirmDelete($scope.node.host, 'node', function() { + ClientClusterNode.remove({ realm : realm.realm, client : client.id , node: $scope.node.host }, function() { + Notifications.success('Node ' + $scope.node.host + ' unregistered successfully.'); + $location.url('/realms/' + realm.realm + '/clients/' + client.id + '/clustering'); + }); + }); + } + + if ($scope.create) { + $scope.node = {} + $scope.registered = false; + } else { + var lastRegTime = client.registeredNodes[$routeParams.node]; + + if (lastRegTime) { + $scope.registered = true; + $scope.node = { + host: $routeParams.node, + lastRegistration: new Date(lastRegTime * 1000) + } + + } else { + $scope.registered = false; + $scope.node = { + host: $routeParams.node + } + } + } +}); + +module.controller('AddBuiltinProtocolMapperCtrl', function($scope, realm, client, serverInfo, + ClientProtocolMappersByProtocol, + $http, $location, Dialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + if (client.protocol == null) { + client.protocol = 'openid-connect'; + } + + var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i]; + } + $scope.mapperTypes = mapperTypes; + + + + + var updateMappers = function() { + var clientMappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol}, function() { + var builtinMappers = serverInfo.builtinProtocolMappers[client.protocol]; + for (var i = 0; i < clientMappers.length; i++) { + for (var j = 0; j < builtinMappers.length; j++) { + if (builtinMappers[j].name == clientMappers[i].name + && builtinMappers[j].protocolMapper == clientMappers[i].protocolMapper) { + builtinMappers.splice(j, 1); + break; + } + } + } + $scope.mappers = builtinMappers; + for (var i = 0; i < $scope.mappers.length; i++) { + $scope.mappers[i].isChecked = false; + } + + + }); + }; + + updateMappers(); + + $scope.add = function() { + var toAdd = []; + for (var i = 0; i < $scope.mappers.length; i++) { + if ($scope.mappers[i].isChecked) { + delete $scope.mappers[i].isChecked; + toAdd.push($scope.mappers[i]); + } + } + $http.post(authUrl + '/admin/realms/' + realm.realm + '/clients/' + client.id + '/protocol-mappers/add-models', + toAdd).then(function() { + Notifications.success("Mappers added"); + $location.url('/realms/' + realm.realm + '/clients/' + client.id + '/mappers'); + }).catch(function() { + Notifications.error("Error adding mappers"); + $location.url('/realms/' + realm.realm + '/clients/' + client.id + '/mappers'); + }); + }; + +}); + +module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client, serverInfo, + Client, + ClientProtocolMappersByProtocol, ClientProtocolMapper, + $route, Dialog, Notifications) { + $scope.realm = realm; + $scope.client = client; + if (client.protocol == null) { + client.protocol = 'openid-connect'; + } + + $scope.changeFlag = function() { + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.client, function() { + $scope.changed = false; + client = angular.copy($scope.client); + Notifications.success("Client updated."); + }); + } + + var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i]; + } + $scope.mapperTypes = mapperTypes; + + $scope.removeMapper = function(mapper) { + console.debug(mapper); + Dialog.confirmDelete(mapper.name, 'mapper', function() { + ClientProtocolMapper.remove({ realm: realm.realm, client: client.id, id : mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $route.reload(); + }); + }); + }; + + $scope.sortMappersByPriority = function(mapper) { + return $scope.mapperTypes[mapper.protocolMapper].priority; + } + + var updateMappers = function() { + $scope.mappers = ClientProtocolMappersByProtocol.query({realm : realm.realm, client : client.id, protocol : client.protocol}); + }; + + updateMappers(); +}); + +module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, clients, mapper, ClientProtocolMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.clients = clients; + + /* + $scope.client = client; + $scope.create = false; + $scope.protocol = client.protocol; + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + */ + + if (client.protocol == null) { + client.protocol = 'openid-connect'; + } + + $scope.model = { + realm: realm, + client: client, + create: false, + protocol: client.protocol, + mapper: angular.copy(mapper), + changed: false + }; + + var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; + for (var i = 0; i < protocolMappers.length; i++) { + if (protocolMappers[i].id === mapper.protocolMapper) { + $scope.model.mapperType = protocolMappers[i]; + } + } + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch('model.mapper', function() { + if (!angular.equals($scope.model.mapper, mapper)) { + $scope.model.changed = true; + } + }, true); + + $scope.save = function() { + ClientProtocolMapper.update({ + realm : realm.realm, + client: client.id, + id : $scope.model.mapper.id + }, $scope.model.mapper, function() { + $scope.model.changed = false; + mapper = angular.copy($scope.mapper); + $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id); + Notifications.success("Your changes have been saved."); + }); + }; + + $scope.reset = function() { + $scope.model.mapper = angular.copy(mapper); + $scope.model.changed = false; + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.model.mapper.name, 'mapper', function() { + ClientProtocolMapper.remove({ realm: realm.realm, client: client.id, id : $scope.model.mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers"); + }); + }); + }; + +}); + +module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, clients, ClientProtocolMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.clients = clients; + + if (client.protocol == null) { + client.protocol = 'openid-connect'; + } + var protocol = client.protocol; + /* + $scope.client = client; + $scope.create = true; + $scope.protocol = protocol; + $scope.mapper = { protocol : client.protocol, config: {}}; + $scope.mapperTypes = serverInfo.protocolMapperTypes[protocol]; + */ + $scope.model = { + realm: realm, + client: client, + create: true, + protocol: client.protocol, + mapper: { protocol : client.protocol, config: {}}, + changed: false, + mapperTypes: serverInfo.protocolMapperTypes[protocol] + }; + console.log("mapper types: ", $scope.model.mapperTypes); + + // apply default configurations on change for selected protocolmapper type. + $scope.$watch('model.mapperType', function() { + var currentMapperType = $scope.model.mapperType; + var defaultConfig = {}; + + if (currentMapperType && Array.isArray(currentMapperType.properties)) { + for (var i = 0; i < currentMapperType.properties.length; i++) { + var property = currentMapperType.properties[i]; + if (property && property.name && property.defaultValue) { + defaultConfig[property.name] = property.defaultValue; + } + } + } + + $scope.model.mapper.config = defaultConfig; + }, true); + + $scope.model.mapperType = $scope.model.mapperTypes[0]; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.save = function() { + $scope.model.mapper.protocolMapper = $scope.model.mapperType.id; + ClientProtocolMapper.save({ + realm : realm.realm, client: client.id + }, $scope.model.mapper, function(data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + id); + Notifications.success("Mapper has been created."); + }); + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + +}); + + +module.controller('ClientClientScopesSetupCtrl', function($scope, realm, Realm, client, clientScopes, serverInfo, + clientDefaultClientScopes, ClientDefaultClientScopes, clientOptionalClientScopes, ClientOptionalClientScopes, $route, Notifications, $location) { + console.log('ClientClientScopesSetupCtrl'); + + $scope.realm = realm; + $scope.client = client; + + $scope.clientDefaultClientScopes = clientDefaultClientScopes; + $scope.clientOptionalClientScopes = clientOptionalClientScopes; + + $scope.availableClientScopes = []; + $scope.selectedDefaultClientScopes = []; + $scope.selectedDefDefaultClientScopes = []; + + $scope.selectedOptionalClientScopes = []; + $scope.selectedDefOptionalClientScopes = []; + + // Populate available client scopes. Available client scopes are neither already assigned to 'default' or 'optional' + for (var i = 0; i < clientScopes.length; i++) { + var clientScope = clientScopes[i]; + var scopeName = clientScopes[i].name; + + var available = true; + if (clientScope.protocol != client.protocol) { + available = false; + } + + for (var j = 0; j < $scope.clientDefaultClientScopes.length; j++) { + if (scopeName === $scope.clientDefaultClientScopes[j].name) { + available = false; + } + } + for (var j = 0; j < $scope.clientOptionalClientScopes.length; j++) { + if (scopeName === $scope.clientOptionalClientScopes[j].name) { + available = false; + } + } + + if (available) { + $scope.availableClientScopes.push(clientScope); + } + } + + $scope.addDefaultClientScope = function () { + $scope.selectedDefaultClientScopesToAdd = JSON.parse('[' + $scope.selectedDefaultClientScopes + ']'); + toAdd = $scope.selectedDefaultClientScopesToAdd.length; + + for (var i = 0; i < $scope.selectedDefaultClientScopesToAdd.length; i++) { + var currentScope = $scope.selectedDefaultClientScopesToAdd[i]; + + ClientDefaultClientScopes.update({ + realm : realm.realm, + client : client.id, + clientScopeId : currentScope.id + }, function () { + toAdd = toAdd - 1; + if (toAdd === 0) { + $route.reload(); + Notifications.success("Default scopes updated."); + } + }); + } + $scope.selectedDefaultClientScopesToAdd = []; + }; + + $scope.deleteDefaultClientScope = function () { + $scope.selectedDefDefaultClientScopesToRemove = JSON.parse('[' + $scope.selectedDefDefaultClientScopes + ']'); + toRemove = $scope.selectedDefDefaultClientScopesToRemove.length; + + for (var i = 0; i < $scope.selectedDefDefaultClientScopesToRemove.length; i++) { + var currentScope = $scope.selectedDefDefaultClientScopesToRemove[i]; + + ClientDefaultClientScopes.remove({ + realm : realm.realm, + client : client.id, + clientScopeId : currentScope.id + }, function () { + toRemove = toRemove - 1; + if (toRemove === 0) { + $route.reload(); + Notifications.success("Default scopes updated."); + } + }); + } + $scope.selectedDefDefaultClientScopesToRemove = []; + }; + + $scope.addOptionalClientScope = function () { + $scope.selectedOptionalClientScopesToAdd = JSON.parse('[' + $scope.selectedOptionalClientScopes + ']'); + toAdd = $scope.selectedOptionalClientScopesToAdd.length; + + for (var i = 0; i < $scope.selectedOptionalClientScopesToAdd.length; i++) { + var currentScope = $scope.selectedOptionalClientScopesToAdd[i]; + + ClientOptionalClientScopes.update({ + realm : realm.realm, + client : client.id, + clientScopeId : currentScope.id + }, function () { + toAdd = toAdd - 1; + if (toAdd === 0) { + $route.reload(); + Notifications.success("Optional scopes updated."); + } + }); + } + }; + + $scope.deleteOptionalClientScope = function () { + $scope.selectedDefOptionalClientScopesToRemove = JSON.parse('[' + $scope.selectedDefOptionalClientScopes + ']'); + toRemove = $scope.selectedDefOptionalClientScopesToRemove.length; + + for (var i = 0; i < $scope.selectedDefOptionalClientScopesToRemove.length; i++) { + var currentScope = $scope.selectedDefOptionalClientScopesToRemove[i]; + + ClientOptionalClientScopes.remove({ + realm : realm.realm, + client : client.id, + clientScopeId : currentScope.id + }, function () { + toRemove = toRemove - 1; + if (toRemove === 0) { + $route.reload(); + Notifications.success("Optional scopes updated."); + } + }); + } + $scope.selectedDefOptionalClientScopesToRemove = []; + }; + +}); + +module.controller('ClientClientScopesEvaluateCtrl', function($scope, Realm, User, ClientEvaluateProtocolMappers, ClientEvaluateGrantedRoles, + ClientEvaluateNotGrantedRoles, ClientEvaluateGenerateExampleAccessToken, ClientEvaluateGenerateExampleIDToken, + ClientEvaluateGenerateExampleUserInfo, realm, client, clients, clientScopes, serverInfo, ComponentUtils, + clientOptionalClientScopes, clientDefaultClientScopes, $route, $routeParams, $http, Notifications, $location, + Client) { + + console.log('ClientClientScopesEvaluateCtrl'); + + var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i]; + } + $scope.mapperTypes = mapperTypes; + + $scope.realm = realm; + $scope.client = client; + $scope.clients = clients; + $scope.userId = null; + + $scope.availableClientScopes = []; + $scope.assignedClientScopes = []; + $scope.selectedClientScopes = []; + $scope.selectedDefClientScopes = []; + $scope.effectiveClientScopes = []; + + // Populate available client scopes. Available client scopes are neither already assigned to 'default' or 'optional' + for (var i = 0; i < clientOptionalClientScopes.length; i++) { + $scope.availableClientScopes.push(clientOptionalClientScopes[i]); + } + + function clearEvalResponse() { + $scope.protocolMappers = null; + $scope.grantedRealmRoles = null; + $scope.notGrantedRealmRoles = null; + $scope.grantedClientRoles = null; + $scope.notGrantedClientRoles = null; + $scope.targetClient = null; + $scope.oidcAccessToken = null; + $scope.oidcIDToken = null; + $scope.oidcUserInfo = null; + + $scope.selectedTab = 0; + } + + function updateState() { + // Compute scope parameter + $scope.scopeParam = 'openid'; + for (var i = 0; i < $scope.assignedClientScopes.length; i++) { + var currentScopeParam = $scope.assignedClientScopes[i].name; + $scope.scopeParam = $scope.scopeParam + ' ' + currentScopeParam; + } + + // Compute effective scopes + $scope.effectiveClientScopes = []; + + for (var i = 0; i < clientDefaultClientScopes.length; i++) { + var currentScope = clientDefaultClientScopes[i]; + $scope.effectiveClientScopes.push(currentScope); + } + for (var i = 0; i < $scope.assignedClientScopes.length; i++) { + var currentScope = $scope.assignedClientScopes[i]; + $scope.effectiveClientScopes.push(currentScope); + } + + // Clear the evaluation response + clearEvalResponse(); + } + + updateState(); + + + $scope.addAppliedClientScope = function () { + $scope.selectedClientScopesToAdd = JSON.parse('[' + $scope.selectedClientScopes + ']'); + for (var i = 0; i < $scope.selectedClientScopesToAdd.length; i++) { + var currentScope = $scope.selectedClientScopesToAdd[i]; + + $scope.assignedClientScopes.push(currentScope); + + var index = ComponentUtils.findIndexById($scope.availableClientScopes, currentScope.id); + if (index > -1) { + $scope.availableClientScopes.splice(index, 1); + } + } + + $scope.selectedClientScopes = []; + $scope.selectedClientScopesToAdd = []; + updateState(); + }; + + $scope.deleteAppliedClientScope = function () { + $scope.selectedDefClientScopesToRemove = JSON.parse('[' + $scope.selectedDefClientScopes + ']'); + for (var i = 0; i < $scope.selectedDefClientScopesToRemove.length; i++) { + var currentScope = $scope.selectedDefClientScopesToRemove[i]; + + $scope.availableClientScopes.push(currentScope); + + var index = ComponentUtils.findIndexById($scope.assignedClientScopes, currentScope.id); + if (index > -1) { + $scope.assignedClientScopes.splice(index, 1); + } + } + + $scope.selectedDefClientScopes = []; + $scope.selectedDefClientScopesToRemove = []; + + updateState(); + }; + + $scope.usersUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + User.query({realm: $route.current.params.realm, search: query.term.trim(), max: 20}, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.username; + return object.username; + } + }; + + $scope.selectedUser = null; + + $scope.selectUser = function(user) { + clearEvalResponse(); + + if (!user || !user.id) { + $scope.selectedUser = null; + $scope.userId = ''; + return; + } + + $scope.userId = user.id; + } + + clientSelectControl($scope, $route.current.params.realm, Client); + + $scope.selectedClient = null; + + $scope.selectClient = function(client) { + console.log("selected client: ", client); + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } + + $scope.selectedClient = client; + updateScopeClientRoles(); + } + + + $scope.sendEvaluationRequest = function () { + + // Send request for retrieve protocolMappers + $scope.protocolMappers = ClientEvaluateProtocolMappers.query({ + realm: realm.realm, + client: client.id, + scopeParam: $scope.scopeParam + }); + + // Send request for retrieve realmRoles + updateScopeRealmRoles(); + + // Send request for retrieve accessToken (in case user was selected) + if (client.protocol === 'openid-connect' && $scope.userId != null && $scope.userId !== '') { + var exampleRequestParams = { + realm: realm.realm, + client: client.id, + userId: $scope.userId, + scopeParam: $scope.scopeParam + }; + + var accessTokenUrl = ClientEvaluateGenerateExampleAccessToken.url(exampleRequestParams); + getPrettyJsonResponse(accessTokenUrl).then(function (result) { + $scope.oidcAccessToken = result; + }); + + var idTokenUrl = ClientEvaluateGenerateExampleIDToken.url(exampleRequestParams); + getPrettyJsonResponse(idTokenUrl).then(function (result) { + $scope.oidcIDToken = result; + }); + + var userInfoUrl = ClientEvaluateGenerateExampleUserInfo.url(exampleRequestParams); + getPrettyJsonResponse(userInfoUrl).then(function (result) { + $scope.oidcUserInfo = result; + }); + } + + $scope.showTab(1); + }; + + function getPrettyJsonResponse(url) { + return $http.get(url).then(function (response) { + if (response.data) { + var responseJson = angular.fromJson(response.data); + return angular.toJson(responseJson, true); + } else { + return null; + } + }); + } + + $scope.isResponseAvailable = function () { + return $scope.protocolMappers != null; + } + + $scope.isAccessTokenAvailable = function () { + return $scope.oidcAccessToken != null; + } + + $scope.isIDTokenAvailable = function () { + return $scope.oidcIDToken != null; + } + + $scope.isUserInfoAvailable = function () { + return $scope.oidcUserInfo != null; + } + + $scope.showTab = function (tab) { + $scope.selectedTab = tab; + + $scope.tabCss = { + tab1: getTabCssClass(1, tab), + tab2: getTabCssClass(2, tab), + tab3: getTabCssClass(3, tab), + tab4: getTabCssClass(4, tab), + tab5: getTabCssClass(5, tab) + } + } + + function getTabCssClass(tabNo, selectedTab) { + return (tabNo === selectedTab) ? 'active' : ''; + } + + $scope.protocolMappersShown = function () { + return $scope.selectedTab === 1; + } + + $scope.rolesShown = function () { + return $scope.selectedTab === 2; + } + + $scope.exampleTabInfo = function() { + switch ($scope.selectedTab) { + case 3: + return { isShown: true, value: $scope.oidcAccessToken} + case 4: + return { isShown: true, value: $scope.oidcIDToken} + case 5: + return { isShown: true, value: $scope.oidcUserInfo} + default: + return { isShown: false, value: null} + } + } + + $scope.sortMappersByPriority = function(mapper) { + return $scope.mapperTypes[mapper.protocolMapper].priority; + } + + + // Roles + + function updateScopeRealmRoles() { + $scope.grantedRealmRoles = ClientEvaluateGrantedRoles.query({ + realm: realm.realm, + client: client.id, + roleContainer: realm.realm, + scopeParam: $scope.scopeParam + }); + $scope.notGrantedRealmRoles = ClientEvaluateNotGrantedRoles.query({ + realm: realm.realm, + client: client.id, + roleContainer: realm.realm, + scopeParam: $scope.scopeParam + }); + } + + function updateScopeClientRoles() { + if ($scope.selectedClient) { + $scope.grantedClientRoles = ClientEvaluateGrantedRoles.query({ + realm: realm.realm, + client: client.id, + roleContainer: $scope.selectedClient.id, + scopeParam: $scope.scopeParam + }); + $scope.notGrantedClientRoles = ClientEvaluateNotGrantedRoles.query({ + realm: realm.realm, + client: client.id, + roleContainer: $scope.selectedClient.id, + scopeParam: $scope.scopeParam + }); + } else { + $scope.grantedClientRoles = null; + $scope.notGrantedClientRoles = null; + } + } +}); + + +module.controller('ClientScopeTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeClientScope = function() { + Dialog.confirmDelete($scope.clientScope.name, 'client scope', function() { + $scope.clientScope.$remove({ + realm : Current.realm.realm, + clientScope : $scope.clientScope.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/client-scopes"); + Notifications.success("The client scope has been deleted."); + }); + }); + }; +}); + + + +module.controller('ClientScopeListCtrl', function($scope, realm, clientScopes, ClientScope, serverInfo, $route, Dialog, Notifications, $location) { + $scope.realm = realm; + $scope.clientScopes = clientScopes; + + $scope.removeClientScope = function(clientScope) { + Dialog.confirmDelete(clientScope.name, 'client scope', function() { + ClientScope.remove({ + realm : realm.realm, + clientScope : clientScope.id + }, function() { + $route.reload(); + Notifications.success("The client scope been deleted."); + }); + }); + }; +}); + +module.controller('ClientScopesRealmDefaultCtrl', function($scope, realm, Realm, clientScopes, realmDefaultClientScopes, RealmDefaultClientScopes, + realmOptionalClientScopes, RealmOptionalClientScopes, serverInfo, $route, Dialog, Notifications, $location) { + + console.log('ClientScopesRealmDefaultCtrl'); + + $scope.realm = realm; + $scope.realmDefaultClientScopes = realmDefaultClientScopes; + $scope.realmOptionalClientScopes = realmOptionalClientScopes; + + $scope.availableClientScopes = []; + $scope.selectedDefaultClientScopes = []; + $scope.selectedDefDefaultClientScopes = []; + + $scope.selectedOptionalClientScopes = []; + $scope.selectedDefOptionalClientScopes = []; + + // Populate available client scopes. Available client scopes are neither already assigned to 'default' or 'optional' + for (var i = 0; i < clientScopes.length; i++) { + var scopeName = clientScopes[i].name; + + var available = true; + for (var j = 0; j < $scope.realmDefaultClientScopes.length; j++) { + if (scopeName === $scope.realmDefaultClientScopes[j].name) { + available = false; + } + } + for (var j = 0; j < $scope.realmOptionalClientScopes.length; j++) { + if (scopeName === $scope.realmOptionalClientScopes[j].name) { + available = false; + } + } + + if (available) { + $scope.availableClientScopes.push(clientScopes[i]); + } + } + + $scope.addDefaultClientScope = function () { + $scope.selectedDefaultClientScopesToAdd = JSON.parse('[' + $scope.selectedDefaultClientScopes + ']'); + toAdd = $scope.selectedDefaultClientScopesToAdd.length; + + for (var i = 0; i < $scope.selectedDefaultClientScopesToAdd.length; i++) { + var currentScope = $scope.selectedDefaultClientScopesToAdd[i]; + + RealmDefaultClientScopes.update({ + realm : realm.realm, + clientScopeId : currentScope.id + }, function () { + toAdd = toAdd - 1; + console.log('toAdd: ' + toAdd); + if (toAdd === 0) { + $route.reload(); + Notifications.success("Realm default scopes updated."); + } + }); + } + $scope.selectedDefaultClientScopesToAdd = []; + }; + + $scope.deleteDefaultClientScope = function () { + $scope.selectedDefDefaultClientScopesToRemove = JSON.parse('[' + $scope.selectedDefDefaultClientScopes + ']'); + toRemove = $scope.selectedDefDefaultClientScopesToRemove.length; + + for (var i = 0; i < $scope.selectedDefDefaultClientScopesToRemove.length; i++) { + var currentScope = $scope.selectedDefDefaultClientScopesToRemove[i]; + + RealmDefaultClientScopes.remove({ + realm : realm.realm, + clientScopeId : currentScope.id + }, function () { + toRemove = toRemove - 1; + if (toRemove === 0) { + $route.reload(); + Notifications.success("Realm default scopes updated."); + } + }); + } + $scope.selectedDefDefaultClientScopesToRemove = []; + }; + + $scope.addOptionalClientScope = function () { + $scope.selectedOptionalClientScopesToAdd = JSON.parse('[' + $scope.selectedOptionalClientScopes + ']'); + toAdd = $scope.selectedOptionalClientScopesToAdd.length; + + for (var i = 0; i < $scope.selectedOptionalClientScopesToAdd.length; i++) { + var currentScope = $scope.selectedOptionalClientScopesToAdd[i]; + + RealmOptionalClientScopes.update({ + realm : realm.realm, + clientScopeId : currentScope.id + }, function () { + toAdd = toAdd - 1; + console.log('toAdd: ' + toAdd); + if (toAdd === 0) { + $route.reload(); + Notifications.success("Realm optional scopes updated."); + } + }); + } + $scope.selectedOptionalClientScopesToAdd = []; + }; + + $scope.deleteOptionalClientScope = function () { + $scope.selectedDefOptionalClientScopesToRemove = JSON.parse('[' + $scope.selectedDefOptionalClientScopes + ']'); + toRemove = $scope.selectedDefOptionalClientScopesToRemove.length; + + for (var i = 0; i < $scope.selectedDefOptionalClientScopesToRemove.length; i++) { + var currentScope = $scope.selectedDefOptionalClientScopesToRemove[i]; + + RealmOptionalClientScopes.remove({ + realm : realm.realm, + clientScopeId : currentScope.id + }, function () { + toRemove = toRemove - 1; + if (toRemove === 0) { + $route.reload(); + Notifications.success("Realm optional scopes updated."); + } + }); + } + $scope.selectedDefOptionalClientScopesToRemove = []; + }; +}); + +module.controller('ClientScopeDetailCtrl', function($scope, realm, clientScope, $route, serverInfo, ClientScope, $location, $modal, Dialog, Notifications) { + $scope.protocols = serverInfo.listProviderIds('login-protocol'); + + $scope.realm = realm; + $scope.create = !clientScope.name; + + function updateProperties() { + if (!$scope.clientScope.attributes) { + $scope.clientScope.attributes = {}; + } + + if ($scope.clientScope.protocol) { + $scope.protocol = $scope.protocols[$scope.protocols.indexOf($scope.clientScope.protocol)]; + } else { + $scope.protocol = $scope.protocols[0]; + } + + if ($scope.clientScope.attributes["display.on.consent.screen"]) { + if ($scope.clientScope.attributes["display.on.consent.screen"] == "true") { + $scope.displayOnConsentScreen = true; + } else { + $scope.displayOnConsentScreen = false; + } + } else { + $scope.displayOnConsentScreen = true; + } + + if ($scope.clientScope.attributes["include.in.token.scope"]) { + if ($scope.clientScope.attributes["include.in.token.scope"] == "true") { + $scope.includeInTokenScope = true; + } else { + $scope.includeInTokenScope = false; + } + } else { + $scope.includeInTokenScope = true; + } + } + + if (!$scope.create) { + $scope.clientScope = angular.copy(clientScope); + } else { + $scope.clientScope = {}; + } + + updateProperties(); + + + $scope.switchChange = function() { + $scope.changed = true; + } + + $scope.changeProtocol = function() { + if ($scope.protocol == "openid-connect") { + $scope.clientScope.protocol = "openid-connect"; + } else if ($scope.protocol == "saml") { + $scope.clientScope.protocol = "saml"; + } + }; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + function isChanged() { + if (!angular.equals($scope.clientScope, clientScope)) { + return true; + } + return false; + } + + $scope.$watch('clientScope', function() { + $scope.changed = isChanged(); + }, true); + + $scope.save = function() { + $scope.clientScope.protocol = $scope.protocol; + + if ($scope.displayOnConsentScreen == true) { + $scope.clientScope.attributes["display.on.consent.screen"] = "true"; + } else { + $scope.clientScope.attributes["display.on.consent.screen"] = "false"; + } + + if ($scope.includeInTokenScope == true) { + $scope.clientScope.attributes["include.in.token.scope"] = "true"; + } else { + $scope.clientScope.attributes["include.in.token.scope"] = "false"; + } + + if ($scope.create) { + ClientScope.save({ + realm: realm.realm, + clientScope: '' + }, $scope.clientScope, function (data, headers) { + $scope.changed = false; + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + "/client-scopes/" + id); + Notifications.success("The client scope has been created."); + }); + } else { + ClientScope.update({ + realm : realm.realm, + clientScope : clientScope.id + }, $scope.clientScope, function() { + $scope.changed = false; + clientScope = angular.copy($scope.clientScope); + $location.url("/realms/" + realm.realm + "/client-scopes/" + clientScope.id); + Notifications.success("Your changes have been saved to the client scope."); + }); + } + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/client-scopes"); + }; +}); + +module.controller('ClientScopeProtocolMapperListCtrl', function($scope, realm, clientScope, serverInfo, + ClientScopeProtocolMappersByProtocol, ClientScopeProtocolMapper, + $route, Dialog, Notifications) { + $scope.realm = realm; + $scope.clientScope = clientScope; + if (clientScope.protocol == null) { + clientScope.protocol = 'openid-connect'; + } + + var protocolMappers = serverInfo.protocolMapperTypes[clientScope.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i]; + } + $scope.mapperTypes = mapperTypes; + + $scope.removeMapper = function(mapper) { + console.debug(mapper); + Dialog.confirmDelete(mapper.name, 'mapper', function() { + ClientScopeProtocolMapper.remove({ realm: realm.realm, clientScope: clientScope.id, id : mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $route.reload(); + }); + }); + }; + + $scope.sortMappersByPriority = function(mapper) { + return $scope.mapperTypes[mapper.protocolMapper].priority; + } + + var updateMappers = function() { + $scope.mappers = ClientScopeProtocolMappersByProtocol.query({realm : realm.realm, clientScope : clientScope.id, protocol : clientScope.protocol}); + }; + + updateMappers(); +}); + +module.controller('ClientScopeProtocolMapperCtrl', function($scope, realm, serverInfo, clientScope, mapper, clients, ClientScopeProtocolMapper, Notifications, Dialog, $location, $route) { + $scope.realm = realm; + $scope.clients = clients; + + if (clientScope.protocol == null) { + clientScope.protocol = 'openid-connect'; + } + + $scope.model = { + realm: realm, + clientScope: clientScope, + create: false, + protocol: clientScope.protocol, + mapper: angular.copy(mapper), + changed: false + } + + var protocolMappers = serverInfo.protocolMapperTypes[clientScope.protocol]; + for (var i = 0; i < protocolMappers.length; i++) { + if (protocolMappers[i].id == mapper.protocolMapper) { + $scope.model.mapperType = protocolMappers[i]; + } + } + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch('model.mapper', function() { + if (!angular.equals($scope.model.mapper, mapper)) { + $scope.model.changed = true; + } + }, true); + + $scope.save = function() { + ClientScopeProtocolMapper.update({ + realm : realm.realm, + clientScope: clientScope.id, + id : mapper.id + }, $scope.model.mapper, function() { + $route.reload(); + Notifications.success("Your changes have been saved."); + }); + }; + + $scope.reset = function() { + $scope.model.mapper = angular.copy(mapper); + $scope.model.changed = false; + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.model.mapper.name, 'mapper', function() { + ClientScopeProtocolMapper.remove({ realm: realm.realm, clientScope: clientScope.id, id : $scope.model.mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $location.url("/realms/" + realm.realm + '/client-scopes/' + clientScope.id + "/mappers"); + }); + }); + }; + +}); + +module.controller('ClientScopeProtocolMapperCreateCtrl', function($scope, realm, serverInfo, clientScope, clients, ClientScopeProtocolMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.clients = clients; + + if (clientScope.protocol == null) { + clientScope.protocol = 'openid-connect'; + } + var protocol = clientScope.protocol; + $scope.model = { + realm: realm, + clientScope: clientScope, + create: true, + protocol: clientScope.protocol, + mapper: { protocol : clientScope.protocol, config: {}}, + changed: false, + mapperTypes: serverInfo.protocolMapperTypes[protocol] + } + + // apply default configurations on change for selected protocolmapper type. + $scope.$watch('model.mapperType', function() { + var currentMapperType = $scope.model.mapperType; + var defaultConfig = {}; + + if (currentMapperType && Array.isArray(currentMapperType.properties)) { + for (var i = 0; i < currentMapperType.properties.length; i++) { + var property = currentMapperType.properties[i]; + if (property && property.name && property.defaultValue) { + defaultConfig[property.name] = property.defaultValue; + } + } + } + + $scope.model.mapper.config = defaultConfig; + }, true); + + $scope.model.mapperType = $scope.model.mapperTypes[0]; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.save = function() { + $scope.model.mapper.protocolMapper = $scope.model.mapperType.id; + ClientScopeProtocolMapper.save({ + realm : realm.realm, clientScope: clientScope.id + }, $scope.model.mapper, function(data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + '/client-scopes/' + clientScope.id + "/mappers/" + id); + Notifications.success("Mapper has been created."); + }); + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + +}); + +module.controller('ClientScopeAddBuiltinProtocolMapperCtrl', function($scope, realm, clientScope, serverInfo, + ClientScopeProtocolMappersByProtocol, + $http, $location, Dialog, Notifications) { + $scope.realm = realm; + $scope.clientScope = clientScope; + if (clientScope.protocol == null) { + clientScope.protocol = 'openid-connect'; + } + + var protocolMappers = serverInfo.protocolMapperTypes[clientScope.protocol]; + var mapperTypes = {}; + for (var i = 0; i < protocolMappers.length; i++) { + mapperTypes[protocolMappers[i].id] = protocolMappers[i]; + } + $scope.mapperTypes = mapperTypes; + + + + + var updateMappers = function() { + var clientMappers = ClientScopeProtocolMappersByProtocol.query({realm : realm.realm, clientScope : clientScope.id, protocol : clientScope.protocol}, function() { + var builtinMappers = serverInfo.builtinProtocolMappers[clientScope.protocol]; + for (var i = 0; i < clientMappers.length; i++) { + for (var j = 0; j < builtinMappers.length; j++) { + if (builtinMappers[j].name == clientMappers[i].name + && builtinMappers[j].protocolMapper == clientMappers[i].protocolMapper) { + builtinMappers.splice(j, 1); + break; + } + } + } + $scope.mappers = builtinMappers; + for (var i = 0; i < $scope.mappers.length; i++) { + $scope.mappers[i].isChecked = false; + } + + + }); + }; + + updateMappers(); + + $scope.add = function() { + var toAdd = []; + for (var i = 0; i < $scope.mappers.length; i++) { + if ($scope.mappers[i].isChecked) { + delete $scope.mappers[i].isChecked; + toAdd.push($scope.mappers[i]); + } + } + $http.post(authUrl + '/admin/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/protocol-mappers/add-models', + toAdd).then(function() { + Notifications.success("Mappers added"); + $location.url('/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/mappers'); + }).catch(function() { + Notifications.error("Error adding mappers"); + $location.url('/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/mappers'); + }); + }; + +}); + + +module.controller('ClientScopeScopeMappingCtrl', function($scope, $http, $route, realm, clientScope, Notifications, + ClientScope, Client, + ClientScopeRealmScopeMapping, ClientScopeClientScopeMapping, ClientRole, + ClientScopeAvailableRealmScopeMapping, ClientScopeAvailableClientScopeMapping, + ClientScopeCompositeRealmScopeMapping, ClientScopeCompositeClientScopeMapping) { + $scope.realm = realm; + $scope.clientScope = angular.copy(clientScope); + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.clientRoles = []; + $scope.clientComposite = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + $scope.dummymodel = []; + $scope.selectedClient = null; + + function updateScopeRealmRoles() { + $scope.realmRoles = ClientScopeAvailableRealmScopeMapping.query({realm : realm.realm, clientScope : clientScope.id}); + $scope.realmMappings = ClientScopeRealmScopeMapping.query({realm : realm.realm, clientScope : clientScope.id}); + $scope.realmComposite = ClientScopeCompositeRealmScopeMapping.query({realm : realm.realm, clientScope : clientScope.id}); + } + + function updateScopeClientRoles() { + if ($scope.selectedClient) { + $scope.clientRoles = ClientScopeAvailableClientScopeMapping.query({realm : realm.realm, clientScope : clientScope.id, targetClient : $scope.selectedClient.id}); + $scope.clientMappings = ClientScopeClientScopeMapping.query({realm : realm.realm, clientScope : clientScope.id, targetClient : $scope.selectedClient.id}); + $scope.clientComposite = ClientScopeCompositeClientScopeMapping.query({realm : realm.realm, clientScope : clientScope.id, targetClient : $scope.selectedClient.id}); + } else { + $scope.clientRoles = null; + $scope.clientMappings = null; + $scope.clientComposite = null; + } + } + + $scope.changeClient = function(client) { + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } + $scope.selectedClient = client; + updateScopeClientRoles(); + }; + + $scope.addRealmRole = function() { + $scope.selectedRealmRolesToAdd = JSON.parse('[' + $scope.selectedRealmRoles + ']'); + $scope.selectedRealmRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/scope-mappings/realm', + $scope.selectedRealmRolesToAdd).then(function() { + updateScopeRealmRoles(); + $scope.selectedRealmRolesToAdd = []; + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.deleteRealmRole = function() { + $scope.selectedRealmMappingsToRemove = JSON.parse('[' + $scope.selectedRealmMappings + ']'); + $scope.selectedRealmMappings = []; + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/scope-mappings/realm', + {data : $scope.selectedRealmMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function () { + updateScopeRealmRoles(); + $scope.selectedRealmMappingsToRemove = []; + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.addClientRole = function() { + $scope.selectedClientRolesToAdd = JSON.parse('[' + $scope.selectedClientRoles + ']'); + $scope.selectedClientRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/scope-mappings/clients/' + $scope.selectedClient.id, + $scope.selectedClientRolesToAdd).then(function () { + updateScopeClientRoles(); + $scope.selectedClientRolesToAdd = []; + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.deleteClientRole = function() { + $scope.selectedClientMappingsToRemove = JSON.parse('[' + $scope.selectedClientMappings + ']'); + $scope.selectedClientMappings = []; + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/client-scopes/' + clientScope.id + '/scope-mappings/clients/' + $scope.selectedClient.id, + {data : $scope.selectedClientMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function () { + updateScopeClientRoles(); + $scope.selectedClientMappingsToRemove = []; + Notifications.success("Scope mappings updated."); + }); + }; + + clientSelectControl($scope, $route.current.params.realm, Client); + updateScopeRealmRoles(); +}); + +module.controller('ClientStoresCtrl', function($scope, $location, $route, realm, serverInfo, Components, Notifications, Dialog) { + console.log('ClientStoresCtrl ++++****'); + $scope.realm = realm; + $scope.providers = serverInfo.componentTypes['org.keycloak.storage.client.ClientStorageProvider']; + $scope.clientStorageProviders = serverInfo.componentTypes['org.keycloak.storage.client.ClientStorageProvider']; + $scope.instancesLoaded = false; + + if (!$scope.providers) $scope.providers = []; + + $scope.addProvider = function(provider) { + console.log('Add provider: ' + provider.id); + $location.url("/create/client-storage/" + realm.realm + "/providers/" + provider.id); + }; + + $scope.getInstanceLink = function(instance) { + return "/realms/" + realm.realm + "/client-storage/providers/" + instance.providerId + "/" + instance.id; + } + + $scope.getInstanceName = function(instance) { + return instance.name; + } + $scope.getInstanceProvider = function(instance) { + return instance.providerId; + } + + $scope.isProviderEnabled = function(instance) { + return !instance.config['enabled'] || instance.config['enabled'][0] == 'true'; + } + + $scope.getInstancePriority = function(instance) { + if (!instance.config['priority']) { + return "0"; + } + return instance.config['priority'][0]; + } + + Components.query({realm: realm.realm, + parent: realm.id, + type: 'org.keycloak.storage.client.ClientStorageProvider' + }, function(data) { + $scope.instances = data; + $scope.instancesLoaded = true; + }); + + $scope.removeInstance = function(instance) { + Dialog.confirmDelete(instance.name, 'client storage provider', function() { + Components.remove({ + realm : realm.realm, + componentId : instance.id + }, function() { + $route.reload(); + Notifications.success("The provider has been deleted."); + }); + }); + }; +}); + +module.controller('GenericClientStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, + serverInfo, instance, providerId, Components) { + console.log('GenericClientStorageCtrl'); + console.log('providerId: ' + providerId); + $scope.create = !instance.providerId; + console.log('create: ' + $scope.create); + var providers = serverInfo.componentTypes['org.keycloak.storage.client.ClientStorageProvider']; + console.log('providers length ' + providers.length); + var providerFactory = null; + for (var i = 0; i < providers.length; i++) { + var p = providers[i]; + console.log('provider: ' + p.id); + if (p.id == providerId) { + $scope.providerFactory = p; + providerFactory = p; + break; + } + + } + $scope.changed = false; + + console.log("providerFactory: " + providerFactory.id); + + function initClientStorageSettings() { + if ($scope.create) { + $scope.changed = true; + instance.name = providerFactory.id; + instance.providerId = providerFactory.id; + instance.providerType = 'org.keycloak.storage.client.ClientStorageProvider'; + instance.parentId = realm.id; + instance.config = { + + }; + instance.config['priority'] = ["0"]; + instance.config['enabled'] = ["true"]; + + $scope.fullSyncEnabled = false; + $scope.changedSyncEnabled = false; + instance.config['cachePolicy'] = ['DEFAULT']; + instance.config['evictionDay'] = ['']; + instance.config['evictionHour'] = ['']; + instance.config['evictionMinute'] = ['']; + instance.config['maxLifespan'] = ['']; + if (providerFactory.properties) { + + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (configProperty.defaultValue) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } else { + instance.config[configProperty.name] = ['']; + } + + } + } + + } else { + $scope.changed = false; + if (!instance.config['enabled']) { + instance.config['enabled'] = ['true']; + } + if (!instance.config['cachePolicy']) { + instance.config['cachePolicy'] = ['DEFAULT']; + + } + if (!instance.config['evictionDay']) { + instance.config['evictionDay'] = ['']; + + } + if (!instance.config['evictionHour']) { + instance.config['evictionHour'] = ['']; + + } + if (!instance.config['evictionMinute']) { + instance.config['evictionMinute'] = ['']; + + } + if (!instance.config['maxLifespan']) { + instance.config['maxLifespan'] = ['']; + + } + if (!instance.config['priority']) { + instance.config['priority'] = ['0']; + } + + if (providerFactory.properties) { + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (!instance.config[configProperty.name]) { + instance.config[configProperty.name] = ['']; + } + } + } + + } + } + + initClientStorageSettings(); + $scope.instance = angular.copy(instance); + $scope.realm = realm; + + $scope.$watch('instance', function() { + if (!angular.equals($scope.instance, instance)) { + $scope.changed = true; + } + + }, true); + + $scope.save = function() { + console.log('save provider'); + $scope.changed = false; + if ($scope.create) { + console.log('saving new provider'); + Components.save({realm: realm.realm}, $scope.instance, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/client-storage/providers/" + $scope.instance.providerId + "/" + id); + Notifications.success("The provider has been created."); + }); + } else { + console.log('update existing provider'); + Components.update({realm: realm.realm, + componentId: instance.id + }, + $scope.instance, function () { + $route.reload(); + Notifications.success("The provider has been updated."); + }); + } + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.cancel = function() { + console.log('cancel'); + if ($scope.create) { + $location.url("/realms/" + realm.realm + "/client-stores"); + } else { + $route.reload(); + } + }; + + + +}); + + diff --git a/base/admin/resources/js/controllers/groups.js b/base/admin/resources/js/controllers/groups.js new file mode 100755 index 0000000..256bf89 --- /dev/null +++ b/base/admin/resources/js/controllers/groups.js @@ -0,0 +1,625 @@ +module.controller('GroupListCtrl', function($scope, $route, $q, realm, Groups, GroupsCount, Group, GroupChildren, Notifications, $location, Dialog, ComponentUtils, $translate) { + $scope.realm = realm; + $scope.groupList = [ + { + "id" : "realm", + "name": $translate.instant('groups'), + "subGroups" : [] + } + ]; + + $scope.searchCriteria = ''; + $scope.currentPage = 1; + $scope.currentPageInput = $scope.currentPage; + $scope.pageSize = 20; + $scope.numberOfPages = 1; + $scope.tree = []; + + var refreshGroups = function (search) { + console.log('refreshGroups'); + $scope.currentPageInput = $scope.currentPage; + + var first = ($scope.currentPage * $scope.pageSize) - $scope.pageSize; + console.log('first:' + first); + var queryParams = { + realm : realm.realm, + first : first, + max : $scope.pageSize + }; + var countParams = { + realm : realm.realm, + top : 'true' + }; + + if(angular.isDefined(search) && search !== '') { + queryParams.search = search; + countParams.search = search; + } + + var promiseGetGroups = $q.defer(); + Groups.query(queryParams, function(entry) { + promiseGetGroups.resolve(entry); + }, function() { + promiseGetGroups.reject($translate.instant('group.fetch.fail', {params: queryParams})); + }); + promiseGetGroups.promise.then(function(groups) { + $scope.groupList = [ + { + "id" : "realm", + "name": $translate.instant('groups'), + "subGroups": ComponentUtils.sortGroups('name', groups) + } + ]; + if (angular.isDefined(search) && search !== '') { + // Add highlight for concrete text match + setTimeout(function () { + document.querySelectorAll('span').forEach(function (element) { + if (element.textContent.indexOf(search) != -1) { + angular.element(element).addClass('highlight'); + } + }); + }, 500); + } + }, function (failed) { + Notifications.error(failed); + }); + + var promiseCount = $q.defer(); + console.log('countParams: realm[' + countParams.realm); + GroupsCount.query(countParams, function(entry) { + promiseCount.resolve(entry); + }, function() { + promiseCount.reject($translate.instant('group.fetch.fail', {params: countParams})); + }); + promiseCount.promise.then(function(entry) { + if(angular.isDefined(entry.count) && entry.count > $scope.pageSize) { + $scope.numberOfPages = Math.ceil(entry.count/$scope.pageSize); + } else { + $scope.numberOfPages = 1; + } + }, function (failed) { + Notifications.error(failed); + }); + }; + refreshGroups(); + + $scope.$watch('currentPage', function(newValue, oldValue) { + if(parseInt(newValue, 10) !== oldValue) { + refreshGroups($scope.searchCriteria); + } + }); + + $scope.clearSearch = function() { + $scope.searchCriteria = ''; + if (parseInt($scope.currentPage, 10) === 1) { + refreshGroups(); + } else { + $scope.currentPage = 1; + } + }; + + $scope.searchGroup = function() { + if (parseInt($scope.currentPage, 10) === 1) { + refreshGroups($scope.searchCriteria); + } else { + $scope.currentPage = 1; + } + }; + + $scope.edit = function(selected) { + if (selected.id === 'realm') return; + $location.url("/realms/" + realm.realm + "/groups/" + selected.id); + }; + + $scope.cut = function(selected) { + $scope.cutNode = selected; + }; + + $scope.isDisabled = function() { + if (!$scope.tree.currentNode) return true; + return $scope.tree.currentNode.id === 'realm'; + }; + + $scope.paste = function(selected) { + if (selected === null) return; + if ($scope.cutNode === null) return; + if (selected.id === $scope.cutNode.id) return; + if (selected.id === 'realm') { + Groups.save({realm: realm.realm}, {id:$scope.cutNode.id}, function() { + $route.reload(); + Notifications.success($translate.instant('group.move.success')); + + }); + + } else { + GroupChildren.save({realm: realm.realm, groupId: selected.id}, {id:$scope.cutNode.id}, function() { + $route.reload(); + Notifications.success($translate.instant('group.move.success')); + + }); + + } + + }; + + $scope.remove = function(selected) { + if (selected === null) return; + Dialog.confirmWithButtonText( + $translate.instant('group.remove.confirm.title', {name: selected.name}), + $translate.instant('group.remove.confirm.message', {name: selected.name}), + $translate.instant('dialogs.delete.confirm'), + function() { + Group.remove({ realm: realm.realm, groupId : selected.id }, function() { + $route.reload(); + Notifications.success($translate.instant('group.remove.success')); + }); + } + ); + }; + + $scope.createGroup = function(selected) { + var parent = 'realm'; + if (selected) { + parent = selected.id; + } + $location.url("/create/group/" + realm.realm + '/parent/' + parent); + + }; + var isLeaf = function(node) { + return node.id !== "realm" && (!node.subGroups || node.subGroups.length === 0); + }; + + $scope.getGroupClass = function(node) { + if (node.id === "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + }; + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id === node.id) { + return 'cut'; + } + return undefined; + } + +}); + +module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, Groups, Group, GroupChildren, Notifications, $location, $translate) { + $scope.realm = realm; + $scope.group = {}; + $scope.save = function() { + console.log('save!!!'); + if (parentId === 'realm') { + console.log('realm'); + Groups.save({realm: realm.realm}, $scope.group, function(data, headers) { + var l = headers().location; + + + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/groups/" + id); + Notifications.success($translate.instant('group.create.success')); + }) + + } else { + GroupChildren.save({realm: realm.realm, groupId: parentId}, $scope.group, function(data, headers) { + var l = headers().location; + + + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/groups/" + id); + Notifications.success($translate.instant('group.create.success')); + }) + + } + + }; + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/groups"); + }; +}); + +module.controller('GroupTabCtrl', function(Dialog, $scope, Current, Group, Notifications, $location, $translate) { + $scope.removeGroup = function() { + Dialog.confirmWithButtonText( + $translate.instant('group.remove.confirm.title', {name: $scope.group.name}), + $translate.instant('group.remove.confirm.message', {name: $scope.group.name}), + $translate.instant('dialogs.delete.confirm'), + function() { + Group.remove({ + realm : Current.realm.realm, + groupId : $scope.group.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/groups"); + Notifications.success($translate.instant('group.remove.success')); + }); + } + ); + }; +}); + +module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Group, Notifications, $location, $translate) { + $scope.realm = realm; + + if (!group.attributes) { + group.attributes = {} + } + convertAttributeValuesToString(group); + + + $scope.group = angular.copy(group); + + $scope.changed = false; // $scope.create; + $scope.$watch('group', function() { + if (!angular.equals($scope.group, group)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + convertAttributeValuesToLists(); + + Group.update({ + realm: realm.realm, + groupId: $scope.group.id + }, $scope.group, function () { + $scope.changed = false; + convertAttributeValuesToString($scope.group); + group = angular.copy($scope.group); + Notifications.success($translate.instant('group.edit.success')); + }); + }; + + function convertAttributeValuesToLists() { + var attrs = $scope.group.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + attrs[attribute] = attrs[attribute].split("##"); + } + } + } + + function convertAttributeValuesToString(group) { + var attrs = group.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + attrs[attribute] = attrs[attribute].join("##"); + } + } + } + + $scope.reset = function() { + $scope.group = angular.copy(group); + $scope.changed = false; + }; + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/groups"); + }; + + $scope.addAttribute = function() { + $scope.group.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.group.attributes[key]; + } +}); + +module.controller('GroupRoleMappingCtrl', function($scope, $http, $route, realm, group, clients, client, Client, Notifications, GroupRealmRoleMapping, + GroupClientRoleMapping, GroupAvailableRealmRoleMapping, GroupAvailableClientRoleMapping, + GroupCompositeRealmRoleMapping, GroupCompositeClientRoleMapping, $translate) { + $scope.realm = realm; + $scope.group = group; + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.clients = clients; + $scope.client = client; + $scope.clientRoles = []; + $scope.clientComposite = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + $scope.dummymodel = []; + + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + + $scope.addRealmRole = function() { + $scope.selectedRealmRolesToAdd = JSON.parse('[' + $scope.selectedRealmRoles + ']'); + $scope.selectedRealmRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm', + $scope.selectedRealmRolesToAdd).then(function() { + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.selectedClient) { + console.log('load available'); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + $scope.selectedRealmRolesToAdd = []; + Notifications.success($translate.instant('group.roles.add.success')); + + }); + }; + + $scope.deleteRealmRole = function() { + $scope.selectedRealmMappingsToRemove = JSON.parse('[' + $scope.selectedRealmMappings + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm', + {data : $scope.selectedRealmMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function() { + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.selectedClient) { + console.log('load available'); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + $scope.selectedRealmMappingsToRemove = []; + Notifications.success($translate.instant('group.roles.remove.success')); + }); + }; + + $scope.addClientRole = function() { + $scope.selectedClientRolesToAdd = JSON.parse('[' + $scope.selectedClientRoles + ']'); + $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.selectedClient.id, + $scope.selectedClientRolesToAdd).then(function() { + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedClientRolesToAdd = []; + Notifications.success($translate.instant('group.roles.add.success')); + }); + }; + + $scope.deleteClientRole = function() { + $scope.selectedClientMappingsToRemove = JSON.parse('[' + $scope.selectedClientMappings + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.selectedClient.id, + {data : $scope.selectedClientMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function() { + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedClientMappingsToRemove = []; + Notifications.success($translate.instant('group.roles.remove.success')); + }); + }; + + + $scope.changeClient = function(client) { + $scope.selectedClient = client; + if (!client || !client.id) { + $scope.selectedClient = null; + $scope.clientRoles = null; + $scope.clientMappings = null; + $scope.clientComposite = null; + return; + } + if ($scope.selectedClient) { + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.selectedClient.id}); + } + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + }; + + clientSelectControl($scope, $route.current.params.realm, Client); + +}); + +module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMembership) { + $scope.realm = realm; + $scope.page = 0; + $scope.group = group; + + $scope.query = { + realm: realm.realm, + groupId: group.id, + max : 5, + first : 0 + }; + + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + }; + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + }; + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + }; + + $scope.searchQuery = function() { + console.log("query.search: " + $scope.query.search); + $scope.searchLoaded = false; + + $scope.users = GroupMembership.query($scope.query, function() { + console.log('search loaded'); + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); + +}); + +module.controller('DefaultGroupsCtrl', function($scope, $q, realm, Groups, GroupsCount, DefaultGroups, Notifications, $translate) { + $scope.realm = realm; + $scope.groupList = []; + $scope.selectedGroup = null; + $scope.tree = []; + + $scope.searchCriteria = ''; + $scope.currentPage = 1; + $scope.currentPageInput = $scope.currentPage; + $scope.pageSize = 20; + $scope.numberOfPages = 1; + + var refreshDefaultGroups = function () { + DefaultGroups.query({realm: realm.realm}, function(data) { + $scope.defaultGroups = data; + }); + } + + var refreshAvailableGroups = function (search) { + var first = ($scope.currentPage * $scope.pageSize) - $scope.pageSize; + $scope.currentPageInput = $scope.currentPage; + var queryParams = { + realm : realm.realm, + first : first, + max : $scope.pageSize + }; + var countParams = { + realm : realm.realm, + top : 'true' + }; + + if(angular.isDefined(search) && search !== '') { + queryParams.search = search; + countParams.search = search; + } + + var promiseGetGroups = $q.defer(); + Groups.query(queryParams, function(entry) { + promiseGetGroups.resolve(entry); + }, function() { + promiseGetGroups.reject($translate.instant('group.fetch.fail', {params: queryParams})); + }); + promiseGetGroups.promise.then(function(groups) { + $scope.groupList = groups; + }, function (failed) { + Notifications.success(failed); + }); + + var promiseCount = $q.defer(); + GroupsCount.query(countParams, function(entry) { + promiseCount.resolve(entry); + }, function() { + promiseCount.reject($translate.instant('group.fetch.fail', {params: countParams})); + }); + promiseCount.promise.then(function(entry) { + if(angular.isDefined(entry.count) && entry.count > $scope.pageSize) { + $scope.numberOfPages = Math.ceil(entry.count/$scope.pageSize); + } + }, function (failed) { + Notifications.success(failed); + }); + }; + + refreshAvailableGroups(); + + $scope.$watch('currentPage', function(newValue, oldValue) { + if(parseInt(newValue, 10) !== parseInt(oldValue, 10)) { + refreshAvailableGroups($scope.searchCriteria); + } + }); + + $scope.clearSearch = function() { + $scope.searchCriteria = ''; + if (parseInt($scope.currentPage, 10) === 1) { + refreshAvailableGroups(); + } else { + $scope.currentPage = 1; + } + }; + + $scope.searchGroup = function() { + if (parseInt($scope.currentPage, 10) === 1) { + refreshAvailableGroups($scope.searchCriteria); + } else { + $scope.currentPage = 1; + } + }; + + refreshDefaultGroups(); + + $scope.addDefaultGroup = function() { + if (!$scope.tree.currentNode) { + Notifications.error($translate.instant('group.default.add.error')); + return; + } + + DefaultGroups.update({realm: realm.realm, groupId: $scope.tree.currentNode.id}, function() { + refreshDefaultGroups(); + Notifications.success($translate.instant('group.default.add.success')); + }); + + }; + + $scope.removeDefaultGroup = function() { + DefaultGroups.remove({realm: realm.realm, groupId: $scope.selectedGroup.id}, function() { + refreshDefaultGroups(); + Notifications.success($translate.instant('group.default.remove.success')); + }); + + }; + + var isLeaf = function(node) { + return node.id !== "realm" && (!node.subGroups || node.subGroups.length === 0); + }; + + $scope.getGroupClass = function(node) { + if (node.id === "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + }; + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id === node.id) { + return 'cut'; + } + return undefined; + } + +}); diff --git a/base/admin/resources/js/controllers/realm.js b/base/admin/resources/js/controllers/realm.js new file mode 100644 index 0000000..3777330 --- /dev/null +++ b/base/admin/resources/js/controllers/realm.js @@ -0,0 +1,3229 @@ +function getAccess(Auth, Current, role) { + if (!Current.realm)return false; + var realmAccess = Auth.user && Auth.user['realm_access']; + if (realmAccess) { + realmAccess = realmAccess[Current.realm.realm]; + if (realmAccess) { + return realmAccess.indexOf(role) >= 0; + } + } + return false; +} + +function getAccessObject(Auth, Current) { + return { + get createRealm() { + return Auth.user && Auth.user.createRealm; + }, + + get queryUsers() { + return getAccess(Auth, Current, 'query-users') || this.viewUsers; + }, + + get queryGroups() { + return getAccess(Auth, Current, 'query-groups') || this.viewUsers; + }, + + get queryClients() { + return getAccess(Auth, Current, 'query-clients') || this.viewClients; + }, + + get viewRealm() { + return getAccess(Auth, Current, 'view-realm') || getAccess(Auth, Current, 'manage-realm') || this.manageRealm; + }, + + get viewClients() { + return getAccess(Auth, Current, 'view-clients') || getAccess(Auth, Current, 'manage-clients') || this.manageClients; + }, + + get viewUsers() { + return getAccess(Auth, Current, 'view-users') || getAccess(Auth, Current, 'manage-users') || this.manageClients; + }, + + get viewEvents() { + return getAccess(Auth, Current, 'view-events') || getAccess(Auth, Current, 'manage-events') || this.manageClients; + }, + + get viewIdentityProviders() { + return getAccess(Auth, Current, 'view-identity-providers') || getAccess(Auth, Current, 'manage-identity-providers') || this.manageIdentityProviders; + }, + + get viewAuthorization() { + return getAccess(Auth, Current, 'view-authorization') || this.manageAuthorization; + }, + + get manageRealm() { + return getAccess(Auth, Current, 'manage-realm'); + }, + + get manageClients() { + return getAccess(Auth, Current, 'manage-clients'); + }, + + get manageUsers() { + return getAccess(Auth, Current, 'manage-users'); + }, + + get manageEvents() { + return getAccess(Auth, Current, 'manage-events'); + }, + + get manageIdentityProviders() { + return getAccess(Auth, Current, 'manage-identity-providers'); + }, + + get manageAuthorization() { + return getAccess(Auth, Current, 'manage-authorization'); + }, + + get impersonation() { + return getAccess(Auth, Current, 'impersonation'); + } + }; +} + + +module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications, ServerInfo, RealmSpecificLocalizationTexts) { + $scope.authUrl = authUrl; + $scope.resourceUrl = resourceUrl; + $scope.auth = Auth; + $scope.serverInfo = ServerInfo.get(); + + $scope.access = getAccessObject(Auth, Current); + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.fragment = $location.path(); + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch(function() { + return Current.realm; + }, function() { + if(Current.realm !== null && currentRealm !== Current.realm.id) { + currentRealm = Current.realm.id; + translateProvider.translations(locale, resourceBundle); + RealmSpecificLocalizationTexts.get({id: Current.realm.realm, locale: locale}, function (localizationTexts) { + translateProvider.translations(locale, localizationTexts.toJSON()); + }) + } + }) +}); + +module.controller('HomeCtrl', function(Realm, Auth, Current, $location) { + + Realm.query(null, function(realms) { + var realm; + if (realms.length == 1) { + realm = realms[0]; + } else if (realms.length == 2) { + if (realms[0].realm == Auth.user.realm) { + realm = realms[1]; + } else if (realms[1].realm == Auth.user.realm) { + realm = realms[0]; + } + } + if (realm) { + Current.realms = realms; + Current.realm = realm; + var access = getAccessObject(Auth, Current); + if (access.viewRealm || access.manageRealm) { + $location.url('/realms/' + realm.realm ); + } else if (access.queryClients) { + $location.url('/realms/' + realm.realm + "/clients"); + } else if (access.viewIdentityProviders) { + $location.url('/realms/' + realm.realm + "/identity-provider-settings"); + } else if (access.queryUsers) { + $location.url('/realms/' + realm.realm + "/users"); + } else if (access.queryGroups) { + $location.url('/realms/' + realm.realm + "/groups"); + } else if (access.viewEvents) { + $location.url('/realms/' + realm.realm + "/events"); + } + } else { + $location.url('/realms'); + } + }); +}); + +module.controller('RealmTabCtrl', function(Dialog, $scope, Current, Realm, Notifications, $location) { + $scope.removeRealm = function() { + Dialog.confirmDelete(Current.realm.realm, 'realm', function() { + Realm.remove({ id : Current.realm.realm }, function() { + Current.realms = Realm.query(); + Notifications.success("The realm has been deleted."); + $location.url("/"); + }); + }); + }; +}); + +module.controller('ServerInfoCtrl', function($scope, ServerInfo) { + ServerInfo.reload(); + + $scope.serverInfo = ServerInfo.get(); + + $scope.$watch($scope.serverInfo, function() { + $scope.providers = []; + for(var spi in $scope.serverInfo.providers) { + var p = angular.copy($scope.serverInfo.providers[spi]); + p.name = spi; + $scope.providers.push(p) + } + }); + + $scope.serverInfoReload = function() { + ServerInfo.reload(); + } +}); + +module.controller('RealmListCtrl', function($scope, Realm, Current) { + $scope.realms = Realm.query(); + Current.realms = $scope.realms; +}); + +module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $location) { +// Current.realms = Realm.get(); + $scope.current = Current; + + $scope.changeRealm = function(selectedRealm) { + $location.url("/realms/" + selectedRealm); + } +}); + +module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, $location, $route, Dialog, Notifications, Auth, $modal) { + console.log('RealmCreateCtrl'); + + Current.realm = null; + + $scope.realm = { + enabled: true + }; + + $scope.changed = false; + $scope.files = []; + + var oldCopy = angular.copy($scope.realm); + + $scope.importFile = function($fileContent){ + $scope.realm = angular.copy(JSON.parse($fileContent)); + $scope.importing = true; + }; + + $scope.viewImportDetails = function() { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-object.html', + controller: 'ObjectModalCtrl', + resolve: { + object: function () { + return $scope.realm; + } + } + }) + }; + + $scope.$watch('realm', function() { + if (!angular.equals($scope.realm, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.$watch('realm.realm', function() { + $scope.realm.id = $scope.realm.realm; + }, true); + + $scope.save = function() { + var realmCopy = angular.copy($scope.realm); + Realm.create(realmCopy, function() { + Notifications.success("The realm has been created."); + + Auth.refreshPermissions(function() { + $scope.$apply(function() { + $location.url("/realms/" + realmCopy.realm); + }); + }); + }); + }; + + $scope.cancel = function() { + $location.url("/"); + }; + + $scope.reset = function() { + $route.reload(); + } +}); + +module.controller('ObjectModalCtrl', function($scope, object) { + $scope.object = object; +}); + +module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, $window, Dialog, Notifications, Auth) { + $scope.createRealm = !realm.realm; + $scope.serverInfo = serverInfo; + $scope.realmName = realm.realm; + $scope.disableRename = realm.realm == masterRealm; + $scope.authServerUrl = authServerUrl; + + if (Current.realm == null || Current.realm.realm != realm.realm) { + for (var i = 0; i < Current.realms.length; i++) { + if (realm.realm == Current.realms[i].realm) { + Current.realm = Current.realms[i]; + break; + } + } + } + for (var i = 0; i < Current.realms.length; i++) { + if (Current.realms[i].realm == realm.realm) { + Current.realm = Current.realms[i]; + } + } + $scope.realm = angular.copy(realm); + + var oldCopy = angular.copy($scope.realm); + + $scope.changed = $scope.create; + + $scope.$watch('realm', function() { + if (!angular.equals($scope.realm, oldCopy)) { + $scope.changed = true; + } + }, true); + $scope.$watch('realmName', function() { + if (!angular.equals($scope.realmName, oldCopy.realm)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + var realmCopy = angular.copy($scope.realm); + realmCopy.realm = $scope.realmName; + $scope.changed = false; + var nameChanged = !angular.equals($scope.realmName, oldCopy.realm); + var oldName = oldCopy.realm; + Realm.update({ id : oldCopy.realm}, realmCopy, function () { + var data = Realm.query(function () { + Current.realms = data; + for (var i = 0; i < Current.realms.length; i++) { + if (Current.realms[i].realm == realmCopy.realm) { + Current.realm = Current.realms[i]; + oldCopy = angular.copy($scope.realm); + } + } + }); + + if (nameChanged) { + console.debug(Auth); + console.debug(Auth.authz.tokenParsed.iss); + + if (Auth.authz.tokenParsed.iss.endsWith(masterRealm)) { + Auth.refreshPermissions(function () { + Auth.refreshPermissions(function () { + Notifications.success("Your changes have been saved to the realm."); + $scope.$apply(function () { + $location.url("/realms/" + realmCopy.realm); + }); + }); + }); + } else { + delete Auth.authz.token; + delete Auth.authz.refreshToken; + + var newLocation = $window.location.href.replace('/' + oldName + '/', '/' + realmCopy.realm + '/') + .replace('/realms/' + oldName, '/realms/' + realmCopy.realm); + window.location.replace(newLocation); + } + } else { + $location.url("/realms/" + realmCopy.realm); + Notifications.success("Your changes have been saved to the realm."); + } + }); + }; + + $scope.reset = function() { + $scope.realm = angular.copy(oldCopy); + $scope.changed = false; + }; + + $scope.cancel = function() { + window.history.back(); + }; +}); + +function genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, url) { + $scope.realm = angular.copy(realm); + $scope.serverInfo = serverInfo; + $scope.registrationAllowed = $scope.realm.registrationAllowed; + + var oldCopy = angular.copy($scope.realm); + + $scope.changed = false; + + $scope.$watch('realm', function() { + if (!angular.equals($scope.realm, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + var realmCopy = angular.copy($scope.realm); + console.log('updating realm...'); + $scope.changed = false; + console.log('oldCopy.realm - ' + oldCopy.realm); + Realm.update({ id : oldCopy.realm}, realmCopy, function () { + $route.reload(); + Notifications.success("Your changes have been saved to the realm."); + $scope.registrationAllowed = $scope.realm.registrationAllowed; + }); + }; + + $scope.reset = function() { + $scope.realm = angular.copy(oldCopy); + $scope.changed = false; + }; + + $scope.cancel = function() { + $route.reload(); + }; + +} + +module.controller('DefenseHeadersCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) { + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/defense/headers"); +}); + +module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) { + // KEYCLOAK-5474: Make sure duplicateEmailsAllowed is disabled if loginWithEmailAllowed + $scope.$watch('realm.loginWithEmailAllowed', function() { + if ($scope.realm.loginWithEmailAllowed) { + $scope.realm.duplicateEmailsAllowed = false; + } + }); + + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/login-settings"); +}); + +module.controller('RealmOtpPolicyCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) { + $scope.optionsDigits = [ 6, 8 ]; + + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/otp-policy"); +}); + +module.controller('RealmWebAuthnPolicyCtrl', function ($scope, Current, Realm, realm, serverInfo, $http, $route, $location, Dialog, Notifications) { + + $scope.deleteAcceptableAaguid = function(index) { + $scope.realm.webAuthnPolicyAcceptableAaguids.splice(index, 1); + }; + + $scope.addAcceptableAaguid = function() { + $scope.realm.webAuthnPolicyAcceptableAaguids.push($scope.newAcceptableAaguid); + $scope.newAcceptableAaguid = ""; + }; + + // Just for case the user fill particular URL with disabled WebAuthn feature. + $scope.redirectIfWebAuthnDisabled = function () { + if (!serverInfo.featureEnabled('WEB_AUTHN')) { + $location.url("/realms/" + $scope.realm.realm + "/authentication"); + } + }; + + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/webauthn-policy"); +}); + +module.controller('RealmWebAuthnPasswordlessPolicyCtrl', function ($scope, Current, Realm, realm, serverInfo, $http, $route, $location, Dialog, Notifications) { + + $scope.deleteAcceptableAaguid = function(index) { + $scope.realm.webAuthnPolicyPasswordlessAcceptableAaguids.splice(index, 1); + }; + + $scope.addAcceptableAaguid = function() { + $scope.realm.webAuthnPolicyPasswordlessAcceptableAaguids.push($scope.newAcceptableAaguid); + $scope.newAcceptableAaguid = ""; + }; + + // Just for case the user fill particular URL with disabled WebAuthn feature. + $scope.redirectIfWebAuthnDisabled = function () { + if (!serverInfo.featureEnabled('WEB_AUTHN')) { + $location.url("/realms/" + $scope.realm.realm + "/authentication"); + } + }; + + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/webauthn-policy-passwordless"); +}); + +module.controller('RealmCibaPolicyCtrl', function ($scope, Current, Realm, realm, serverInfo, $http, $route, $location, Dialog, Notifications) { + + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/authentication/ciba-policy"); +}); + +module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) { + genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings"); + + $scope.supportedLocalesOptions = { + 'multiple' : true, + 'simple_tags' : true, + 'tags' : [] + }; + + updateSupported(); + + function localeForTheme(type, name) { + name = name || 'base'; + for (var i = 0; i < serverInfo.themes[type].length; i++) { + if (serverInfo.themes[type][i].name == name) { + return serverInfo.themes[type][i].locales || []; + } + } + return []; + } + + function updateSupported() { + if ($scope.realm.internationalizationEnabled) { + var accountLocales = localeForTheme('account', $scope.realm.accountTheme); + var loginLocales = localeForTheme('login', $scope.realm.loginTheme); + var emailLocales = localeForTheme('email', $scope.realm.emailTheme); + + var supportedLocales = []; + for (var i = 0; i < accountLocales.length; i++) { + var l = accountLocales[i]; + if (loginLocales.indexOf(l) >= 0 && emailLocales.indexOf(l) >= 0) { + supportedLocales.push(l); + } + } + + $scope.supportedLocalesOptions.tags = supportedLocales; + + if (!$scope.realm.supportedLocales) { + $scope.realm.supportedLocales = supportedLocales; + } else { + for (var i = 0; i < $scope.realm.supportedLocales.length; i++) { + if (supportedLocales.indexOf($scope.realm.supportedLocales[i]) == -1) { + $scope.realm.supportedLocales = supportedLocales; + } + } + } + + if (!$scope.realm.defaultLocale || supportedLocales.indexOf($scope.realm.defaultLocale) == -1) { + $scope.realm.defaultLocale = 'en'; + } + } + } + + $scope.$watch('realm.loginTheme', updateSupported); + $scope.$watch('realm.accountTheme', updateSupported); + $scope.$watch('realm.emailTheme', updateSupported); + $scope.$watch('realm.internationalizationEnabled', updateSupported); +}); + +module.controller('RealmLocalizationCtrl', function($scope, Current, $location, Realm, realm, serverInfo, Notifications, RealmSpecificLocales, realmSpecificLocales, RealmSpecificLocalizationTexts, RealmSpecificLocalizationText, Dialog, $translate){ + $scope.realm = realm; + $scope.realmSpecificLocales = realmSpecificLocales; + $scope.newLocale = null; + $scope.selectedRealmSpecificLocales = null; + $scope.localizationTexts = null; + + $scope.createLocale = function() { + if(!$scope.newLocale) { + Notifications.error($translate.instant('missing-locale')); + return; + } + $scope.realmSpecificLocales.push($scope.newLocale) + $scope.selectedRealmSpecificLocales = $scope.newLocale; + $scope.newLocale = null; + $location.url('/create/localization/' + realm.realm + '/' + $scope.selectedRealmSpecificLocales); + } + + $scope.$watch(function() { + return $scope.selectedRealmSpecificLocales; + }, function() { + if($scope.selectedRealmSpecificLocales != null) { + $scope.updateRealmSpecificLocalizationTexts(); + } + }) + + $scope.updateRealmSpecificLocales = function() { + RealmSpecificLocales.get({id: realm.realm}, function (updated) { + $scope.realmSpecificLocales = updated; + }) + } + + $scope.updateRealmSpecificLocalizationTexts = function() { + RealmSpecificLocalizationTexts.get({id: realm.realm, locale: $scope.selectedRealmSpecificLocales }, function (updated) { + $scope.localizationTexts = updated; + }) + } + + $scope.removeLocalizationText = function(key) { + Dialog.confirmDelete(key, 'localization text', function() { + RealmSpecificLocalizationText.remove({ + realm: realm.realm, + locale: $scope.selectedRealmSpecificLocales, + key: key + }, function () { + $scope.updateRealmSpecificLocalizationTexts(); + Notifications.success($translate.instant('localization-text.remove.success')); + }); + }); + } +}); + +module.controller('RealmLocalizationUploadCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications, $upload, $translate){ + $scope.realm = realm; + $scope.locale = null; + $scope.files = []; + + $scope.onFileSelect = function($files) { + $scope.files = $files; + }; + + $scope.reset = function() { + $scope.locale = null; + $scope.files = null; + }; + + $scope.save = function() { + + if(!$scope.files || $scope.files.length === 0) { + Notifications.error($translate.instant('missing-file')); + return; + } + //$files: an array of files selected, each file has name, size, and type. + for (var i = 0; i < $scope.files.length; i++) { + var $file = $scope.files[i]; + $scope.upload = $upload.upload({ + url: authUrl + '/admin/realms/' + realm.realm + '/localization/' + $scope.locale, + file: $file + }).then(function(response) { + $scope.reset(); + Notifications.success($translate.instant('localization-file.upload.success')); + }).catch(function() { + Notifications.error($translate.instant('localization-file.upload.error')); + }); + } + }; + +}); + +module.controller('RealmLocalizationDetailCtrl', function($scope, Current, $location, Realm, realm, Notifications, locale, key, RealmSpecificLocalizationText, localizationText, $translate){ + $scope.realm = realm; + $scope.locale = locale; + $scope.key = key; + $scope.value = ((localizationText)? localizationText.content : null); + + $scope.create = !key; + + $scope.save = function() { + if ($scope.create) { + RealmSpecificLocalizationText.save({ + realm: realm.realm, + locale: $scope.locale, + key: $scope.key + }, $scope.value, function (data, headers) { + $location.url("/realms/" + realm.realm + "/localization"); + Notifications.success($translate.instant('localization-text.create.success')); + }); + } else { + RealmSpecificLocalizationText.save({ + realm: realm.realm, + locale: $scope.locale, + key: $scope.key + }, $scope.value, function (data, headers) { + $location.url("/realms/" + realm.realm + "/localization"); + Notifications.success($translate.instant('localization-text.update.success')); + }); + } + }; + + $scope.cancel = function () { + $location.url("/realms/" + realm.realm + "/localization"); + }; + +}); + +module.controller('RealmCacheCtrl', function($scope, realm, RealmClearUserCache, RealmClearRealmCache, RealmClearKeysCache, Notifications) { + $scope.realm = angular.copy(realm); + + $scope.clearUserCache = function() { + RealmClearUserCache.save({ realm: realm.realm}, function () { + Notifications.success("User cache cleared"); + }); + } + + $scope.clearRealmCache = function() { + RealmClearRealmCache.save({ realm: realm.realm}, function () { + Notifications.success("Realm cache cleared"); + }); + } + + $scope.clearKeysCache = function() { + RealmClearKeysCache.save({ realm: realm.realm}, function () { + Notifications.success("Public keys cache cleared"); + }); + } + + +}); + +module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, serverInfo) { + var parse = function(policyString) { + var policies = []; + if (!policyString || policyString.length == 0){ + return policies; + } + + var policyArray = policyString.split(" and "); + + for (var i = 0; i < policyArray.length; i ++){ + var policyToken = policyArray[i]; + var id; + var value; + if (policyToken.indexOf('(') == -1) { + id = policyToken.trim(); + value = null; + } else { + id = policyToken.substring(0, policyToken.indexOf('(')); + value = policyToken.substring(policyToken.indexOf('(') + 1, policyToken.lastIndexOf(')')).trim(); + } + + for (var j = 0; j < serverInfo.passwordPolicies.length; j++) { + if (serverInfo.passwordPolicies[j].id == id) { + // clone + var p = JSON.parse(JSON.stringify(serverInfo.passwordPolicies[j])); + + p.value = value && value || p.defaultValue; + policies.push(p); + } + } + } + return policies; + }; + + var toString = function(policies) { + if (!policies || policies.length == 0) { + return ""; + } + var policyString = ""; + for (var i = 0; i < policies.length; i++) { + policyString += policies[i].id + '(' + policies[i].value + ')'; + if (i != policies.length - 1) { + policyString += ' and '; + } + } + return policyString; + } + + $scope.realm = realm; + $scope.serverInfo = serverInfo; + + $scope.changed = false; + console.log(JSON.stringify(parse(realm.passwordPolicy))); + $scope.policy = parse(realm.passwordPolicy); + var oldCopy = angular.copy($scope.policy); + + $scope.$watch('policy', function() { + $scope.changed = ! angular.equals($scope.policy, oldCopy); + }, true); + + $scope.addPolicy = function(policy){ + policy.value = policy.defaultValue; + if (!$scope.policy) { + $scope.policy = []; + } + $scope.policy.push(policy); + } + + $scope.removePolicy = function(index){ + $scope.policy.splice(index, 1); + } + + $scope.save = function() { + $scope.realm.passwordPolicy = toString($scope.policy); + console.log($scope.realm.passwordPolicy); + + Realm.update($scope.realm, function () { + $route.reload(); + Notifications.success("Your changes have been saved to the realm."); + }); + }; + + $scope.reset = function() { + $route.reload(); + }; +}); + +module.controller('RealmDefaultRolesCtrl', function ($scope, $route, realm, roles, Notifications, ClientRole, Client, RoleRealmComposites, RoleClientComposites, ComponentUtils, $http) { + + console.log('RealmDefaultRolesCtrl'); + + $scope.realm = realm; + $scope.availableRealmRoles = angular.copy(roles); + $scope.selectedRealmRoles = []; + $scope.selectedRealmDefRoles = []; + + $scope.availableClientRoles = []; + $scope.selectedClientRoles = []; + $scope.selectedClientDefRoles = []; + + for (var j = 0; j < $scope.availableRealmRoles.length; j++) { + if ($scope.availableRealmRoles[j].id === realm.defaultRole.id) { + var realmRole = $scope.availableRealmRoles[j]; + var idx = $scope.availableRealmRoles.indexOf(realmRole); + $scope.availableRealmRoles.splice(idx, 1); + break; + } + } + + $scope.realmMappings = RoleRealmComposites.query({realm : realm.realm, role : realm.defaultRole.id}, function(){ + for (var i = 0; i < $scope.realmMappings.length; i++) { + var role = $scope.realmMappings[i]; + for (var j = 0; j < $scope.availableRealmRoles.length; j++) { + var realmRole = $scope.availableRealmRoles[j]; + if (realmRole.id === role.id) { + var idx = $scope.availableRealmRoles.indexOf(realmRole); + if (idx !== -1) { + $scope.availableRealmRoles.splice(idx, 1); + break; + } + } + } + } + }); + + $scope.addRealmDefaultRole = function () { + + $scope.selectedRealmRolesToAdd = JSON.parse('[' + $scope.selectedRealmRoles + ']'); + $http.post(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + realm.defaultRole.id + '/composites', + $scope.selectedRealmRolesToAdd).then(function() { + // Remove selected roles from the Available roles and add them to realm default roles (move from left to right). + for (var i = 0; i < $scope.selectedRealmRolesToAdd.length; i++) { + var selectedRole = $scope.selectedRealmRolesToAdd[i]; + var index = ComponentUtils.findIndexById($scope.availableRealmRoles, selectedRole.id); + if (index > -1) { + $scope.availableRealmRoles.splice(index, 1); + $scope.realmMappings.push(selectedRole); + } + } + + $scope.selectedRealmRoles = []; + $scope.selectedRealmRolesToAdd = []; + Notifications.success("Default roles updated."); + }); + }; + + $scope.deleteRealmDefaultRole = function () { + + $scope.selectedClientRolesToRemove = JSON.parse('[' + $scope.selectedRealmDefRoles + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + realm.defaultRole.id + '/composites', + {data : $scope.selectedClientRolesToRemove, headers : {"content-type" : "application/json"}}).then(function() { + // Remove selected roles from the realm default roles and add them to available roles (move from right to left). + for (var i = 0; i < $scope.selectedClientRolesToRemove.length; i++) { + var selectedRole = $scope.selectedClientRolesToRemove[i]; + var index = ComponentUtils.findIndexById($scope.realmMappings, selectedRole.id); + if (index > -1) { + $scope.realmMappings.splice(index, 1); + $scope.availableRealmRoles.push(selectedRole); + } + } + + $scope.selectedRealmDefRoles = []; + $scope.selectedClientRolesToRemove = []; + Notifications.success("Default roles updated."); + }); + }; + + $scope.changeClient = function (client) { + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } + $scope.selectedClient = client; + $scope.selectedClientRoles = []; + $scope.selectedClientDefRoles = []; + + // Populate available roles for selected client + if ($scope.selectedClient) { + $scope.availableClientRoles = ClientRole.query({realm: realm.realm, client: client.id}, function () { + $scope.clientMappings = RoleClientComposites.query({realm : realm.realm, role : realm.defaultRole.id, client : client.id}, function(){ + for (var i = 0; i < $scope.clientMappings.length; i++) { + var role = $scope.clientMappings[i]; + for (var j = 0; j < $scope.availableClientRoles.length; j++) { + var clientRole = $scope.availableClientRoles[j]; + if (clientRole.id === role.id) { + var idx = $scope.availableClientRoles.indexOf(clientRole); + if (idx !== -1) { + $scope.availableClientRoles.splice(idx, 1); + break; + } + } + } + } + }); + for (var j = 0; j < $scope.availableClientRoles.length; j++) { + if ($scope.availableClientRoles[j] === realm.defaultRole.id) { + var clientRole = $scope.availableClientRoles[j]; + var idx = $scope.availableClientRoles.indexof(clientRole); + $scope.availableClientRoles.splice(idx, 1); + break; + } + } + }); + } else { + $scope.availableClientRoles = null; + } + }; + + $scope.addClientDefaultRole = function () { + + $scope.selectedClientRolesToAdd = JSON.parse('[' + $scope.selectedClientRoles + ']'); + $http.post(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + realm.defaultRole.id + '/composites', + $scope.selectedClientRolesToAdd).then(function() { + // Remove selected roles from the app available roles and add them to app default roles (move from left to right). + for (var i = 0; i < $scope.selectedClientRolesToAdd.length; i++) { + var selectedRole = $scope.selectedClientRolesToAdd[i]; + + var index = ComponentUtils.findIndexById($scope.availableClientRoles, selectedRole.id); + if (index > -1) { + $scope.availableClientRoles.splice(index, 1); + $scope.clientMappings.push(selectedRole); + } + } + + $scope.selectedClientRoles = []; + $scope.selectedClientRolesToAdd = []; + Notifications.success("Default roles updated."); + }); + }; + + $scope.rmClientDefaultRole = function () { + + $scope.selectedClientRolesToRemove = JSON.parse('[' + $scope.selectedClientDefRoles + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + realm.defaultRole.id + '/composites', + {data : $scope.selectedClientRolesToRemove, headers : {"content-type" : "application/json"}}).then(function() { + // Remove selected roles from the realm default roles and add them to available roles (move from right to left). + for (var i = 0; i < $scope.selectedClientRolesToRemove.length; i++) { + var selectedRole = $scope.selectedClientRolesToRemove[i]; + var index = ComponentUtils.findIndexById($scope.clientMappings, selectedRole.id); + if (index > -1) { + $scope.clientMappings.splice(index, 1); + $scope.availableClientRoles.push(selectedRole); + } + } + + $scope.selectedClientDefRoles = []; + $scope.selectedClientRolesToRemove = []; + Notifications.success("Default roles updated."); + }); + }; + + clientSelectControl($scope, $route.current.params.realm, Client); +}); + + + +module.controller('IdentityProviderTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeIdentityProvider = function() { + Dialog.confirmDelete($scope.identityProvider.alias, 'provider', function() { + $scope.identityProvider.$remove({ + realm : Current.realm.realm, + alias : $scope.identityProvider.alias + }, function() { + $location.url("/realms/" + Current.realm.realm + "/identity-provider-settings"); + Notifications.success("The identity provider has been deleted."); + }); + }); + }; +}); + +module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload, $http, $route, realm, instance, providerFactory, IdentityProvider, serverInfo, authFlows, $location, Notifications, Dialog) { + $scope.realm = angular.copy(realm); + + $scope.initSamlProvider = function() { + $scope.nameIdFormats = [ + { + format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + name: "Persistent" + + }, + { + format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", + name: "Transient" + }, + { + format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + name: "Email" + + }, + { + format: "urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos", + name: "Kerberos" + + }, + { + format: "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName", + name: "X.509 Subject Name" + + }, + { + format: "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName", + name: "Windows Domain Qualified Name" + + }, + { + format: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + name: "Unspecified" + + } + ]; + $scope.signatureAlgorithms = [ + "RSA_SHA1", + "RSA_SHA256", + "RSA_SHA256_MGF1", + "RSA_SHA512", + "RSA_SHA512_MGF1", + "DSA_SHA1" + ]; + $scope.xmlKeyNameTranformers = [ + "NONE", + "KEY_ID", + "CERT_SUBJECT" + ]; + $scope.principalTypes = [ + { + type: "SUBJECT", + name: "Subject NameID" + + }, + { + type: "ATTRIBUTE", + name: "Attribute [Name]" + + }, + { + type: "FRIENDLY_ATTRIBUTE", + name: "Attribute [Friendly Name]" + + } + ]; + if (instance && instance.alias) { + + } else { + $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format; + $scope.identityProvider.config.principalType = $scope.principalTypes[0].type; + $scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1]; + $scope.identityProvider.config.xmlSigKeyInfoKeyNameTransformer = $scope.xmlKeyNameTranformers[1]; + $scope.identityProvider.config.allowCreate = 'true'; + } + $scope.identityProvider.config.entityId = $scope.identityProvider.config.entityId || (authUrl + '/realms/' + realm.realm); + } + + $scope.hidePassword = true; + $scope.fromUrl = { + data: '' + }; + + if (instance && instance.alias) { + $scope.identityProvider = angular.copy(instance); + $scope.newIdentityProvider = false; + for (var i in serverInfo.identityProviders) { + var provider = serverInfo.identityProviders[i]; + + if (provider.id == instance.providerId) { + $scope.provider = provider; + } + } + } else { + $scope.identityProvider = {}; + $scope.identityProvider.config = {}; + $scope.identityProvider.alias = providerFactory.id; + $scope.identityProvider.providerId = providerFactory.id; + + $scope.identityProvider.enabled = true; + $scope.identityProvider.authenticateByDefault = false; + $scope.identityProvider.firstBrokerLoginFlowAlias = 'first broker login'; + $scope.identityProvider.config.useJwksUrl = 'true'; + $scope.identityProvider.config.syncMode = 'IMPORT'; + $scope.newIdentityProvider = true; + } + + $scope.changed = $scope.newIdentityProvider; + + $scope.$watch('identityProvider', function() { + if (!angular.equals($scope.identityProvider, instance)) { + $scope.changed = true; + } + }, true); + + + $scope.serverInfo = serverInfo; + + $scope.allProviders = angular.copy(serverInfo.identityProviders); + + $scope.configuredProviders = angular.copy(realm.identityProviders); + + removeUsedSocial(); + + $scope.authFlows = []; + for (var i=0 ; i 0) { + $scope.importUrl = true; + } else{ + $scope.importUrl = false; + } + }); + + $scope.$watch('configuredProviders', function(configuredProviders) { + if (configuredProviders) { + $scope.configuredProviders = angular.copy(configuredProviders); + + for (var j = 0; j < configuredProviders.length; j++) { + var configProvidedId = configuredProviders[j].providerId; + + for (var i in $scope.allProviders) { + var provider = $scope.allProviders[i]; + if (provider.id == configProvidedId) { + configuredProviders[j].provider = provider; + } + } + } + $scope.configuredProviders = angular.copy(configuredProviders); + } + }, true); + + $scope.callbackUrl = authServerUrl + "/realms/" + realm.realm + "/broker/"; + + $scope.addProvider = function(provider) { + $location.url("/create/identity-provider/" + realm.realm + "/" + provider.id); + }; + + $scope.save = function() { + if ($scope.newIdentityProvider) { + if (!$scope.identityProvider.alias) { + Notifications.error("You must specify an alias"); + return; + } + IdentityProvider.save({ + realm: $scope.realm.realm, alias: '' + }, $scope.identityProvider, function () { + $location.url("/realms/" + realm.realm + "/identity-provider-settings/provider/" + $scope.identityProvider.providerId + "/" + $scope.identityProvider.alias); + Notifications.success("The " + $scope.identityProvider.alias + " provider has been created."); + }); + } else { + IdentityProvider.update({ + realm: $scope.realm.realm, + alias: $scope.identityProvider.alias + }, $scope.identityProvider, function () { + $route.reload(); + Notifications.success("The " + $scope.identityProvider.alias + " provider has been updated."); + }); + } + }; + + $scope.cancel = function() { + if ($scope.newIdentityProvider) { + $location.url("/realms/" + realm.realm + "/identity-provider-settings"); + } else { + $route.reload(); + } + }; + + + $scope.reset = function() { + $scope.identityProvider = {}; + $scope.configuredProviders = angular.copy($scope.realm.identityProviders); + }; + + $scope.showPassword = function(flag) { + $scope.hidePassword = flag; + }; + + $scope.removeIdentityProvider = function(identityProvider) { + Dialog.confirmDelete(identityProvider.alias, 'provider', function() { + IdentityProvider.remove({ + realm : realm.realm, + alias : identityProvider.alias + }, function() { + $route.reload(); + Notifications.success("The identity provider has been deleted."); + }); + }); + }; + + // KEYCLOAK-5932: remove social providers that have already been defined + function removeUsedSocial() { + var i = $scope.allProviders.length; + while (i--) { + if ($scope.allProviders[i].groupName !== 'Social') continue; + if ($scope.configuredProviders != null) { + for (var j = 0; j < $scope.configuredProviders.length; j++) { + if ($scope.configuredProviders[j].providerId === $scope.allProviders[i].id) { + $scope.allProviders.splice(i, 1); + break; + } + } + } + } + }; + + if (instance && instance.alias) { + try { $scope.authnContextClassRefs = JSON.parse($scope.identityProvider.config.authnContextClassRefs || '[]'); } catch (e) { $scope.authnContextClassRefs = []; } + try { $scope.authnContextDeclRefs = JSON.parse($scope.identityProvider.config.authnContextDeclRefs || '[]'); } catch (e) { $scope.authnContextDeclRefs = []; } + } else { + $scope.authnContextClassRefs = []; + $scope.authnContextDeclRefs = []; + } + + $scope.deleteAuthnContextClassRef = function(index) { + $scope.authnContextClassRefs.splice(index, 1); + $scope.identityProvider.config.authnContextClassRefs = JSON.stringify($scope.authnContextClassRefs); + }; + + $scope.addAuthnContextClassRef = function() { + $scope.authnContextClassRefs.push($scope.newAuthnContextClassRef); + $scope.identityProvider.config.authnContextClassRefs = JSON.stringify($scope.authnContextClassRefs); + $scope.newAuthnContextClassRef = ""; + }; + + $scope.deleteAuthnContextDeclRef = function(index) { + $scope.authnContextDeclRefs.splice(index, 1); + $scope.identityProvider.config.authnContextDeclRefs = JSON.stringify($scope.authnContextDeclRefs); + }; + + $scope.addAuthnContextDeclRef = function() { + $scope.authnContextDeclRefs.push($scope.newAuthnContextDeclRef); + $scope.identityProvider.config.authnContextDeclRefs = JSON.stringify($scope.authnContextDeclRefs); + $scope.newAuthnContextDeclRef = ""; + }; +}); + +module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, TimeUnit, TimeUnit2, serverInfo) { + $scope.realm = realm; + $scope.serverInfo = serverInfo; + $scope.actionTokenProviders = $scope.serverInfo.providers.actionTokenHandler.providers; + + $scope.realm.accessTokenLifespan = TimeUnit2.asUnit(realm.accessTokenLifespan); + $scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit2.asUnit(realm.accessTokenLifespanForImplicitFlow); + $scope.realm.ssoSessionIdleTimeout = TimeUnit2.asUnit(realm.ssoSessionIdleTimeout); + $scope.realm.ssoSessionMaxLifespan = TimeUnit2.asUnit(realm.ssoSessionMaxLifespan); + $scope.realm.ssoSessionIdleTimeoutRememberMe = TimeUnit2.asUnit(realm.ssoSessionIdleTimeoutRememberMe); + $scope.realm.ssoSessionMaxLifespanRememberMe = TimeUnit2.asUnit(realm.ssoSessionMaxLifespanRememberMe); + $scope.realm.offlineSessionIdleTimeout = TimeUnit2.asUnit(realm.offlineSessionIdleTimeout); + // KEYCLOAK-7688 Offline Session Max for Offline Token + $scope.realm.offlineSessionMaxLifespan = TimeUnit2.asUnit(realm.offlineSessionMaxLifespan); + $scope.realm.clientSessionIdleTimeout = TimeUnit2.asUnit(realm.clientSessionIdleTimeout); + $scope.realm.clientSessionMaxLifespan = TimeUnit2.asUnit(realm.clientSessionMaxLifespan); + $scope.realm.clientOfflineSessionIdleTimeout = TimeUnit2.asUnit(realm.clientOfflineSessionIdleTimeout); + $scope.realm.clientOfflineSessionMaxLifespan = TimeUnit2.asUnit(realm.clientOfflineSessionMaxLifespan); + $scope.realm.accessCodeLifespan = TimeUnit2.asUnit(realm.accessCodeLifespan); + $scope.realm.accessCodeLifespanLogin = TimeUnit2.asUnit(realm.accessCodeLifespanLogin); + $scope.realm.accessCodeLifespanUserAction = TimeUnit2.asUnit(realm.accessCodeLifespanUserAction); + $scope.realm.actionTokenGeneratedByAdminLifespan = TimeUnit2.asUnit(realm.actionTokenGeneratedByAdminLifespan); + $scope.realm.actionTokenGeneratedByUserLifespan = TimeUnit2.asUnit(realm.actionTokenGeneratedByUserLifespan); + $scope.realm.oauth2DeviceCodeLifespan = TimeUnit2.asUnit(realm.oauth2DeviceCodeLifespan); + $scope.realm.attributes = realm.attributes + + var oldCopy = angular.copy($scope.realm); + $scope.changed = false; + + $scope.$watch('realm', function() { + if (!angular.equals($scope.realm, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.$watch('actionLifespanId', function () { + // changedActionLifespanId signals other watchers that we were merely + // changing the dropdown and we should not enable 'save' button + if ($scope.actionTokenAttribute && $scope.actionTokenAttribute.hasOwnProperty('time')) { + $scope.changedActionLifespanId = true; + } + + $scope.actionTokenAttribute = TimeUnit2.asUnit($scope.realm.attributes['actionTokenGeneratedByUserLifespan.' + $scope.actionLifespanId]); + }, true); + + $scope.$watch('actionTokenAttribute', function () { + if ($scope.actionLifespanId === null) return; + + if ($scope.changedActionLifespanId) { + $scope.changedActionLifespanId = false; + return; + } else { + $scope.changed = true; + } + + if ($scope.actionTokenAttribute !== null) { + $scope.realm.attributes['actionTokenGeneratedByUserLifespan.' + $scope.actionLifespanId] = $scope.actionTokenAttribute.toSeconds(); + } + }, true); + + $scope.changeRevokeRefreshToken = function() { + + }; + + $scope.save = function() { + $scope.realm.accessTokenLifespan = $scope.realm.accessTokenLifespan.toSeconds(); + $scope.realm.accessTokenLifespanForImplicitFlow = $scope.realm.accessTokenLifespanForImplicitFlow.toSeconds(); + $scope.realm.ssoSessionIdleTimeout = $scope.realm.ssoSessionIdleTimeout.toSeconds(); + $scope.realm.ssoSessionMaxLifespan = $scope.realm.ssoSessionMaxLifespan.toSeconds(); + $scope.realm.ssoSessionIdleTimeoutRememberMe = $scope.realm.ssoSessionIdleTimeoutRememberMe.toSeconds(); + $scope.realm.ssoSessionMaxLifespanRememberMe = $scope.realm.ssoSessionMaxLifespanRememberMe.toSeconds(); + $scope.realm.offlineSessionIdleTimeout = $scope.realm.offlineSessionIdleTimeout.toSeconds(); + // KEYCLOAK-7688 Offline Session Max for Offline Token + $scope.realm.offlineSessionMaxLifespan = $scope.realm.offlineSessionMaxLifespan.toSeconds(); + $scope.realm.clientSessionIdleTimeout = $scope.realm.clientSessionIdleTimeout.toSeconds(); + $scope.realm.clientSessionMaxLifespan = $scope.realm.clientSessionMaxLifespan.toSeconds(); + $scope.realm.clientOfflineSessionIdleTimeout = $scope.realm.clientOfflineSessionIdleTimeout.toSeconds(); + $scope.realm.clientOfflineSessionMaxLifespan = $scope.realm.clientOfflineSessionMaxLifespan.toSeconds(); + $scope.realm.accessCodeLifespan = $scope.realm.accessCodeLifespan.toSeconds(); + $scope.realm.accessCodeLifespanUserAction = $scope.realm.accessCodeLifespanUserAction.toSeconds(); + $scope.realm.accessCodeLifespanLogin = $scope.realm.accessCodeLifespanLogin.toSeconds(); + $scope.realm.actionTokenGeneratedByAdminLifespan = $scope.realm.actionTokenGeneratedByAdminLifespan.toSeconds(); + $scope.realm.actionTokenGeneratedByUserLifespan = $scope.realm.actionTokenGeneratedByUserLifespan.toSeconds(); + $scope.realm.oauth2DeviceCodeLifespan = $scope.realm.oauth2DeviceCodeLifespan.toSeconds(); + + Realm.update($scope.realm, function () { + $route.reload(); + Notifications.success("The changes have been saved to the realm."); + }); + }; + + $scope.resetToDefaultToken = function (actionTokenId) { + $scope.actionTokenAttribute = {}; + delete $scope.realm.attributes['actionTokenGeneratedByUserLifespan.' + $scope.actionLifespanId]; + //Only for UI effects, resets to the original state + $scope.actionTokenAttribute.unit = 'Minutes'; + } + + $scope.reset = function() { + $route.reload(); + }; +}); + +module.controller('ViewKeyCtrl', function($scope, key) { + $scope.key = key; +}); + +module.controller('RealmKeysCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications, serverInfo, keys, Components, $modal) { + $scope.realm = angular.copy(realm); + $scope.keys = keys.keys; + $scope.active = {}; + + Components.query({realm: realm.realm, + parent: realm.id, + type: 'org.keycloak.keys.KeyProvider' + }, function(data) { + for (var i = 0; i < keys.keys.length; i++) { + for (var j = 0; j < data.length; j++) { + if (keys.keys[i].providerId == data[j].id) { + keys.keys[i].provider = data[j]; + } + } + } + + for (var t in keys.active) { + for (var i = 0; i < keys.keys.length; i++) { + if (keys.active[t] == keys.keys[i].kid) { + $scope.active[t] = keys.keys[i]; + } + } + } + }); + + $scope.viewKey = function(key) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-key.html', + controller: 'ViewKeyCtrl', + resolve: { + key: function () { + return key; + } + } + }) + } +}); + +module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications, serverInfo, Components, $modal) { + $scope.realm = angular.copy(realm); + $scope.enableUpload = false; + + $scope.providers = serverInfo.componentTypes['org.keycloak.keys.KeyProvider']; + + Components.query({realm: realm.realm, + parent: realm.id, + type: 'org.keycloak.keys.KeyProvider' + }, function(data) { + $scope.instances = data; + + for (var i = 0; i < $scope.instances.length; i++) { + for (var j = 0; j < $scope.providers.length; j++) { + if ($scope.providers[j].id === $scope.instances[i].providerId) { + $scope.instances[i].provider = $scope.providers[j]; + } + } + } + }); + + $scope.addProvider = function(provider) { + $location.url("/create/keys/" + realm.realm + "/providers/" + provider.id); + }; + + $scope.removeInstance = function(instance) { + Dialog.confirmDelete(instance.name, 'key provider', function() { + Components.remove({ + realm : realm.realm, + componentId : instance.id + }, function() { + $route.reload(); + Notifications.success("The provider has been deleted."); + }); + }); + }; +}); + +module.controller('GenericKeystoreCtrl', function($scope, $location, Notifications, $route, Dialog, realm, serverInfo, instance, providerId, Components) { + $scope.create = !instance.providerId; + $scope.realm = realm; + + var providers = serverInfo.componentTypes['org.keycloak.keys.KeyProvider']; + var providerFactory = null; + for (var i = 0; i < providers.length; i++) { + var p = providers[i]; + if (p.id == providerId) { + $scope.providerFactory = p; + providerFactory = p; + break; + } + } + + if ($scope.create) { + $scope.instance = { + name: providerFactory.id, + providerId: providerFactory.id, + providerType: 'org.keycloak.keys.KeyProvider', + parentId: realm.id, + config: { + 'priority': ["0"] + } + } + } else { + $scope.instance = angular.copy(instance); + } + + if (providerFactory.properties) { + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (!$scope.instance.config[configProperty.name]) { + if (configProperty.defaultValue) { + $scope.instance.config[configProperty.name] = [configProperty.defaultValue]; + if (!$scope.create) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } + } else { + $scope.instance.config[configProperty.name] = ['']; + if (!$scope.create) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } + } + } + } + } + + $scope.$watch('instance', function() { + if (!angular.equals($scope.instance, instance)) { + $scope.changed = true; + } + + }, true); + + $scope.save = function() { + $scope.changed = false; + if ($scope.create) { + Components.save({realm: realm.realm}, $scope.instance, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/keys/providers/" + $scope.instance.providerId + "/" + id); + Notifications.success("The provider has been created."); + }); + } else { + Components.update({realm: realm.realm, + componentId: instance.id + }, + $scope.instance, function () { + $route.reload(); + Notifications.success("The provider has been updated."); + }); + } + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.cancel = function() { + if ($scope.create) { + $location.url("/realms/" + realm.realm + "/keys"); + } else { + $route.reload(); + } + }; +}); + +module.controller('RealmSessionStatsCtrl', function($scope, realm, stats, RealmClientSessionStats, RealmLogoutAll, Notifications) { + $scope.realm = realm; + $scope.stats = stats; + + $scope.logoutAll = function() { + RealmLogoutAll.save({realm : realm.realm}, function (globalReqResult) { + var successCount = globalReqResult.successRequests ? globalReqResult.successRequests.length : 0; + var failedCount = globalReqResult.failedRequests ? globalReqResult.failedRequests.length : 0; + + if (failedCount > 0) { + var msgStart = successCount>0 ? 'Successfully logout all users under: ' + globalReqResult.successRequests + ' . ' : ''; + Notifications.error(msgStart + 'Failed to logout users under: ' + globalReqResult.failedRequests + '. Verify availability of failed hosts and try again'); + } else { + window.location.reload(); + } + }); + }; +}); + + +module.controller('RealmRevocationCtrl', function($scope, Realm, RealmPushRevocation, realm, $http, $location, Dialog, Notifications) { + $scope.realm = angular.copy(realm); + + var setNotBefore = function() { + if ($scope.realm.notBefore == 0) { + $scope.notBefore = "None"; + } else { + $scope.notBefore = new Date($scope.realm.notBefore * 1000); + } + }; + + setNotBefore(); + + var reset = function() { + Realm.get({ id : realm.realm }, function(updated) { + $scope.realm = updated; + setNotBefore(); + }) + + }; + + $scope.clear = function() { + Realm.update({ realm: realm.realm, notBefore : 0 }, function () { + $scope.notBefore = "None"; + Notifications.success('Not Before cleared for realm.'); + reset(); + }); + } + $scope.setNotBeforeNow = function() { + Realm.update({ realm: realm.realm, notBefore : new Date().getTime()/1000}, function () { + Notifications.success('Not Before set for realm.'); + reset(); + }); + } + $scope.pushRevocation = function() { + RealmPushRevocation.save({ realm: realm.realm}, function (globalReqResult) { + var successCount = globalReqResult.successRequests ? globalReqResult.successRequests.length : 0; + var failedCount = globalReqResult.failedRequests ? globalReqResult.failedRequests.length : 0; + + if (successCount==0 && failedCount==0) { + Notifications.warn('No push sent. No admin URI configured or no registered cluster nodes available'); + } else if (failedCount > 0) { + var msgStart = successCount>0 ? 'Successfully push notBefore to: ' + globalReqResult.successRequests + ' . ' : ''; + Notifications.error(msgStart + 'Failed to push notBefore to: ' + globalReqResult.failedRequests + '. Verify availability of failed hosts and try again'); + } else { + Notifications.success('Successfully push notBefore to all configured clients'); + } + }); + } + +}); + + +module.controller('RoleTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeRole = function() { + Dialog.confirmDelete($scope.role.name, 'role', function() { + RoleById.remove({ + realm: realm.realm, + role: $scope.role.id + }, function () { + $route.reload(); + Notifications.success("The role has been deleted."); + }); + }); + }; +}); + + +module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, RoleList, RoleById, filterFilter) { + $scope.realm = realm; + $scope.roles = []; + $scope.defaultRoleName = realm.defaultRole.name; + + $scope.query = { + realm: realm.realm, + search : null, + max : 20, + first : 0 + } + + $scope.$watch('query.search', function (newVal, oldVal) { + if($scope.query.search && $scope.query.search.length >= 3) { + $scope.firstPage(); + } + }, true); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.roles = RoleList.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); + + $scope.determineEditLink = function(role) { + return role.name === $scope.defaultRoleName ? "/realms/" + $scope.realm.realm + "/default-roles" : "/realms/" + $scope.realm.realm + "/roles/" + role.id; + } + + $scope.removeRole = function (role) { + if (role.name === $scope.defaultRoleName) return; + + Dialog.confirmDelete(role.name, 'role', function () { + RoleById.remove({ + realm: realm.realm, + role: role.id + }, function () { + $route.reload(); + Notifications.success("The role has been deleted."); + }); + }); + }; +}); + + +module.controller('RoleDetailCtrl', function($scope, realm, role, roles, Client, $route, + Role, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, + $http, $location, Dialog, Notifications, RealmRoleRemover, ComponentUtils) { + $scope.realm = realm; + $scope.role = angular.copy(role); + $scope.create = !role.name; + + $scope.changed = $scope.create; + + $scope.save = function() { + convertAttributeValuesToLists(); + console.log('save'); + if ($scope.create) { + Role.save({ + realm: realm.realm + }, $scope.role, function (data, headers) { + $scope.changed = false; + convertAttributeValuesToString($scope.role); + role = angular.copy($scope.role); + + Role.get({ realm: realm.realm, role: role.name }, function(role) { + var id = role.id; + $location.url("/realms/" + realm.realm + "/roles/" + id); + Notifications.success("The role has been created."); + }); + }); + } else { + $scope.update(); + } + }; + + $scope.remove = function() { + RealmRoleRemover.remove($scope.role, realm, Dialog, $location, Notifications); + }; + + $scope.cancel = function () { + $location.url("/realms/" + realm.realm + "/roles"); + }; + + $scope.addAttribute = function() { + $scope.role.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.role.attributes[key]; + } + + function convertAttributeValuesToLists() { + var attrs = $scope.role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + var attrVals = attrs[attribute].split("##"); + attrs[attribute] = attrVals; + } + } + } + + function convertAttributeValuesToString(role) { + var attrs = role.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + var attrVals = attrs[attribute].join("##"); + attrs[attribute] = attrVals; + console.log("attribute" + attrVals) + } + } + } + + roleControl($scope, $route, realm, role, roles, Client, + ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, + $http, $location, Notifications, Dialog, ComponentUtils); +}); + +module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications, RealmSMTPConnectionTester) { + console.log('RealmSMTPSettingsCtrl'); + + var booleanSmtpAtts = ["auth","ssl","starttls"]; + + $scope.realm = realm; + + if ($scope.realm.smtpServer) { + $scope.realm.smtpServer = typeObject($scope.realm.smtpServer); + }; + + var oldCopy = angular.copy($scope.realm); + $scope.changed = false; + + $scope.$watch('realm', function() { + if (!angular.equals($scope.realm, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + var realmCopy = angular.copy($scope.realm); + realmCopy['smtpServer'] = detypeObject(realmCopy.smtpServer); + $scope.changed = false; + Realm.update(realmCopy, function () { + $location.url("/realms/" + realm.realm + "/smtp-settings"); + Notifications.success("Your changes have been saved to the realm."); + }); + }; + + $scope.reset = function() { + $scope.realm = angular.copy(oldCopy); + $scope.changed = false; + }; + + $scope.testConnection = function() { + RealmSMTPConnectionTester.save({realm: realm.realm}, realm.smtpServer, function() { + Notifications.success("SMTP connection successful. E-mail was sent!"); + }, function(errorResponse) { + if (error.data.errorMessage) { + Notifications.error(error.data.errorMessage); + } else { + Notifications.error('Unexpected error during SMTP validation'); + } + }); + }; + + /* Convert string attributes containing a boolean to actual boolean type + convert an integer string (port) to integer. */ + function typeObject(obj){ + for (var att in obj){ + if (booleanSmtpAtts.indexOf(att) < 0) + continue; + if (obj[att] === "true"){ + obj[att] = true; + } else if (obj[att] === "false"){ + obj[att] = false; + } + } + + obj['port'] = parseInt(obj['port']); + + return obj; + } + + /* Convert all non-string values to strings to invert changes caused by the typeObject function. */ + function detypeObject(obj){ + for (var att in obj){ + if (booleanSmtpAtts.indexOf(att) < 0) + continue; + if (obj[att] === true){ + obj[att] = "true"; + } else if (obj[att] === false){ + obj[att] = "false" + } + } + + obj['port'] = obj['port'] && obj['port'].toString(); + + return obj; + } +}); + +module.controller('RealmEventsConfigCtrl', function($scope, eventsConfig, RealmEventsConfig, RealmEvents, RealmAdminEvents, realm, serverInfo, $location, Notifications, TimeUnit, Dialog) { + $scope.realm = realm; + + $scope.eventsConfig = eventsConfig; + + $scope.eventsConfig.expirationUnit = TimeUnit.autoUnit(eventsConfig.eventsExpiration); + $scope.eventsConfig.eventsExpiration = TimeUnit.toUnit(eventsConfig.eventsExpiration, $scope.eventsConfig.expirationUnit); + + $scope.eventListeners = Object.keys(serverInfo.providers.eventsListener.providers); + + $scope.eventsConfigSelectOptions = { + 'multiple': true, + 'simple_tags': true, + 'tags': $scope.eventListeners + }; + + $scope.eventSelectOptions = { + 'multiple': true, + 'simple_tags': true, + 'tags': serverInfo.enums['eventType'] + }; + + var oldCopy = angular.copy($scope.eventsConfig); + $scope.changed = false; + + $scope.$watch('eventsConfig', function() { + if (!angular.equals($scope.eventsConfig, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + $scope.changed = false; + + var copy = angular.copy($scope.eventsConfig) + delete copy['expirationUnit']; + + copy.eventsExpiration = TimeUnit.toSeconds($scope.eventsConfig.eventsExpiration, $scope.eventsConfig.expirationUnit); + + RealmEventsConfig.update({ + id : realm.realm + }, copy, function () { + $location.url("/realms/" + realm.realm + "/events-settings"); + Notifications.success("Your changes have been saved to the realm."); + }); + }; + + $scope.reset = function() { + $scope.eventsConfig = angular.copy(oldCopy); + $scope.changed = false; + }; + + $scope.clearEvents = function() { + Dialog.confirmDelete($scope.realm.realm, 'events', function() { + RealmEvents.remove({ id : $scope.realm.realm }, function() { + Notifications.success("The events has been cleared."); + }); + }); + }; + + $scope.clearAdminEvents = function() { + Dialog.confirmDelete($scope.realm.realm, 'admin-events', function() { + RealmAdminEvents.remove({ id : $scope.realm.realm }, function() { + Notifications.success("The admin events has been cleared."); + }); + }); + }; +}); + +module.controller('RealmEventsCtrl', function($scope, RealmEvents, realm, serverInfo) { + $scope.realm = realm; + $scope.page = 0; + + $scope.eventSelectOptions = { + 'multiple': true, + 'simple_tags': true, + 'tags': serverInfo.enums['eventType'] + }; + + $scope.query = { + id : realm.realm, + max : 5, + first : 0 + } + + $scope.disablePaste = function(e) { + e.preventDefault(); + return false; + } + + $scope.update = function() { + $scope.query.first = 0; + for (var i in $scope.query) { + if ($scope.query[i] === '') { + delete $scope.query[i]; + } + } + $scope.events = RealmEvents.query($scope.query); + } + + $scope.reset = function() { + $scope.query.first = 0; + $scope.query.max = 5; + $scope.query.type = ''; + $scope.query.client = ''; + $scope.query.user = ''; + $scope.query.dateFrom = ''; + $scope.query.dateTo = ''; + + $scope.update(); + } + + $scope.queryUpdate = function() { + for (var i in $scope.query) { + if ($scope.query[i] === '') { + delete $scope.query[i]; + } + } + $scope.events = RealmEvents.query($scope.query); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.queryUpdate(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.queryUpdate(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.queryUpdate(); + } + + $scope.update(); +}); + +module.controller('RealmAdminEventsCtrl', function($scope, RealmAdminEvents, realm, serverInfo, $modal, $filter) { + $scope.realm = realm; + $scope.page = 0; + + $scope.query = { + id : realm.realm, + max : 5, + first : 0 + }; + + $scope.adminEnabledEventOperationsOptions = { + 'multiple': true, + 'simple_tags': true, + 'tags': serverInfo.enums['operationType'] + }; + + $scope.adminEnabledEventResourceTypesOptions = { + 'multiple': true, + 'simple_tags': true, + 'tags': serverInfo.enums['resourceType'] + }; + + $scope.disablePaste = function(e) { + e.preventDefault(); + return false; + } + + $scope.update = function() { + $scope.query.first = 0; + for (var i in $scope.query) { + if ($scope.query[i] === '') { + delete $scope.query[i]; + } + } + $scope.events = RealmAdminEvents.query($scope.query); + }; + + $scope.reset = function() { + $scope.query.first = 0; + $scope.query.max = 5; + $scope.query.operationTypes = ''; + $scope.query.resourceTypes = ''; + $scope.query.resourcePath = ''; + $scope.query.authRealm = ''; + $scope.query.authClient = ''; + $scope.query.authUser = ''; + $scope.query.authIpAddress = ''; + $scope.query.dateFrom = ''; + $scope.query.dateTo = ''; + + $scope.update(); + }; + + $scope.queryUpdate = function() { + for (var i in $scope.query) { + if ($scope.query[i] === '') { + delete $scope.query[i]; + } + } + $scope.events = RealmAdminEvents.query($scope.query); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.queryUpdate(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.queryUpdate(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.queryUpdate(); + } + + $scope.update(); + + $scope.viewRepresentation = function(event) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/realm-events-admin-representation.html', + controller: 'RealmAdminEventsModalCtrl', + resolve: { + event: function () { + return event; + } + } + }) + } + + $scope.viewAuth = function(event) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/realm-events-admin-auth.html', + controller: 'RealmAdminEventsModalCtrl', + resolve: { + event: function () { + return event; + } + } + }) + } +}); + +module.controller('RealmAdminEventsModalCtrl', function($scope, $filter, event) { + $scope.event = event; +}); + +module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, TimeUnit, $route) { + console.log('RealmBruteForceCtrl'); + + $scope.realm = realm; + + $scope.realm.waitIncrementUnit = TimeUnit.autoUnit(realm.waitIncrementSeconds); + $scope.realm.waitIncrement = TimeUnit.toUnit(realm.waitIncrementSeconds, $scope.realm.waitIncrementUnit); + + $scope.realm.minimumQuickLoginWaitUnit = TimeUnit.autoUnit(realm.minimumQuickLoginWaitSeconds); + $scope.realm.minimumQuickLoginWait = TimeUnit.toUnit(realm.minimumQuickLoginWaitSeconds, $scope.realm.minimumQuickLoginWaitUnit); + + $scope.realm.maxFailureWaitUnit = TimeUnit.autoUnit(realm.maxFailureWaitSeconds); + $scope.realm.maxFailureWait = TimeUnit.toUnit(realm.maxFailureWaitSeconds, $scope.realm.maxFailureWaitUnit); + + $scope.realm.maxDeltaTimeUnit = TimeUnit.autoUnit(realm.maxDeltaTimeSeconds); + $scope.realm.maxDeltaTime = TimeUnit.toUnit(realm.maxDeltaTimeSeconds, $scope.realm.maxDeltaTimeUnit); + + var oldCopy = angular.copy($scope.realm); + $scope.changed = false; + + $scope.$watch('realm', function() { + if (!angular.equals($scope.realm, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + var realmCopy = angular.copy($scope.realm); + delete realmCopy["waitIncrementUnit"]; + delete realmCopy["waitIncrement"]; + delete realmCopy["minimumQuickLoginWaitUnit"]; + delete realmCopy["minimumQuickLoginWait"]; + delete realmCopy["maxFailureWaitUnit"]; + delete realmCopy["maxFailureWait"]; + delete realmCopy["maxDeltaTimeUnit"]; + delete realmCopy["maxDeltaTime"]; + + realmCopy.waitIncrementSeconds = TimeUnit.toSeconds($scope.realm.waitIncrement, $scope.realm.waitIncrementUnit) + realmCopy.minimumQuickLoginWaitSeconds = TimeUnit.toSeconds($scope.realm.minimumQuickLoginWait, $scope.realm.minimumQuickLoginWaitUnit) + realmCopy.maxFailureWaitSeconds = TimeUnit.toSeconds($scope.realm.maxFailureWait, $scope.realm.maxFailureWaitUnit) + realmCopy.maxDeltaTimeSeconds = TimeUnit.toSeconds($scope.realm.maxDeltaTime, $scope.realm.maxDeltaTimeUnit) + + $scope.changed = false; + Realm.update(realmCopy, function () { + oldCopy = angular.copy($scope.realm); + $location.url("/realms/" + realm.realm + "/defense/brute-force"); + Notifications.success("Your changes have been saved to the realm."); + }); + }; + + $scope.reset = function() { + $route.reload(); + }; +}); + + +module.controller('IdentityProviderMapperListCtrl', function($scope, realm, identityProvider, mapperTypes, mappers) { + $scope.realm = realm; + $scope.identityProvider = identityProvider; + $scope.mapperTypes = mapperTypes; + $scope.mappers = mappers; +}); + +module.controller('IdentityProviderMapperCtrl', function($scope, realm, identityProvider, mapperTypes, mapper, IdentityProviderMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.identityProvider = identityProvider; + $scope.create = false; + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + $scope.mapperType = mapperTypes[mapper.identityProviderMapper]; + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch('mapper', function() { + if (!angular.equals($scope.mapper, mapper)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + IdentityProviderMapper.update({ + realm : realm.realm, + alias: identityProvider.alias, + mapperId : mapper.id + }, $scope.mapper, function() { + $scope.changed = false; + mapper = angular.copy($scope.mapper); + $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + mapper.id); + Notifications.success("Your changes have been saved."); + }); + }; + + $scope.reset = function() { + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.mapper.name, 'mapper', function() { + IdentityProviderMapper.remove({ realm: realm.realm, alias: mapper.identityProviderAlias, mapperId : $scope.mapper.id }, function() { + Notifications.success("The mapper has been deleted."); + $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers"); + }); + }); + }; + +}); + +module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, identityProvider, mapperTypes, IdentityProviderMapper, Notifications, Dialog, $location) { + $scope.realm = realm; + $scope.identityProvider = identityProvider; + $scope.create = true; + $scope.mapper = { identityProviderAlias: identityProvider.alias, config: {}}; + $scope.mapperTypes = mapperTypes; + + // make first type the default + $scope.mapperType = mapperTypes[Object.keys(mapperTypes)[0]]; + $scope.mapper.config.syncMode = 'INHERIT'; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.save = function() { + $scope.mapper.identityProviderMapper = $scope.mapperType.id; + IdentityProviderMapper.save({ + realm : realm.realm, alias: identityProvider.alias + }, $scope.mapper, function(data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + '/identity-provider-mappers/' + identityProvider.alias + "/mappers/" + id); + Notifications.success("Mapper has been created."); + }); + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + +}); + +module.controller('RealmFlowBindingCtrl', function($scope, flows, Current, Realm, realm, serverInfo, $http, $route, Dialog, Notifications) { + $scope.flows = []; + $scope.clientFlows = []; + for (var i=0 ; i 0) { + $scope.provider = formProviders[0]; + } + + $scope.save = function() { + $scope.flow.provider = $scope.provider.id; + CreateExecutionFlow.save({realm: realm.realm, alias: parentFlow.alias}, $scope.flow, function() { + $location.url("/realms/" + realm.realm + "/authentication/flows"); + Notifications.success("Flow Created."); + }) + } + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/authentication/flows"); + }; +}); + +module.controller('CreateExecutionCtrl', function($scope, realm, parentFlow, formActionProviders, authenticatorProviders, clientAuthenticatorProviders, + CreateExecution, + Notifications, $location) { + $scope.realm = realm; + $scope.parentFlow = parentFlow; + + if (parentFlow.providerId == 'form-flow') { + $scope.providers = formActionProviders; + } else if (parentFlow.providerId == 'client-flow') { + $scope.providers = clientAuthenticatorProviders; + } else { + $scope.providers = authenticatorProviders; + } + + $scope.provider = {}; + if ($scope.providers.length > 0) { + $scope.provider = $scope.providers[0]; + } + + $scope.save = function() { + var execution = { + provider: $scope.provider.id + } + CreateExecution.save({realm: realm.realm, alias: parentFlow.alias}, execution, function() { + $location.url("/realms/" + realm.realm + "/authentication/flows"); + Notifications.success("Execution Created."); + }) + } + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/authentication/flows"); + }; +}); + + + +module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flows, selectedFlow, LastFlowSelected, Dialog, + AuthenticationFlows, AuthenticationFlowsCopy, AuthenticationFlowsUpdate, AuthenticationFlowExecutions, + AuthenticationExecution, AuthenticationExecutionRaisePriority, AuthenticationExecutionLowerPriority, + $modal, Notifications, CopyDialog, UpdateDialog, $location) { + $scope.realm = realm; + $scope.flows = flows; + + if (selectedFlow !== null) { + LastFlowSelected.alias = selectedFlow; + } + + if (selectedFlow === null && LastFlowSelected.alias !== null) { + selectedFlow = LastFlowSelected.alias; + } + + if (flows.length > 0) { + $scope.flow = flows[0]; + if (selectedFlow) { + for (var i = 0; i < flows.length; i++) { + if (flows[i].alias == selectedFlow) { + $scope.flow = flows[i]; + break; + } + } + } + } + + $scope.selectFlow = function(flow) { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.alias); + }; + + var setupForm = function() { + AuthenticationFlowExecutions.query({realm: realm.realm, alias: $scope.flow.alias}, function(data) { + $scope.executions = data; + $scope.choicesmax = 0; + $scope.levelmax = 0; + for (var i = 0; i < $scope.executions.length; i++ ) { + var execution = $scope.executions[i]; + if (execution.requirementChoices.length > $scope.choicesmax) { + $scope.choicesmax = execution.requirementChoices.length; + } + if (execution.level > $scope.levelmax) { + $scope.levelmax = execution.level; + } + } + $scope.levelmaxempties = []; + for (j = 0; j < $scope.levelmax; j++) { + $scope.levelmaxempties.push(j); + + } + for (var i = 0; i < $scope.executions.length; i++ ) { + var execution = $scope.executions[i]; + execution.empties = []; + for (j = 0; j < $scope.choicesmax - execution.requirementChoices.length; j++) { + execution.empties.push(j); + } + execution.preLevels = []; + for (j = 0; j < execution.level; j++) { + execution.preLevels.push(j); + } + execution.postLevels = []; + for (j = execution.level; j < $scope.levelmax; j++) { + execution.postLevels.push(j); + } + } + }) + }; + + $scope.copyFlow = function() { + CopyDialog.open('Copy Authentication Flow', $scope.flow.alias, function(name) { + AuthenticationFlowsCopy.save({realm: realm.realm, alias: $scope.flow.alias}, { + newName: name + }, function() { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + name); + Notifications.success("Flow copied."); + }) + }) + }; + + $scope.deleteFlow = function() { + Dialog.confirmDelete($scope.flow.alias, 'flow', function() { + $scope.removeFlow(); + }); + }; + + $scope.removeFlow = function() { + console.log('Remove flow:' + $scope.flow.alias); + if (realm.browserFlow == $scope.flow.alias) { + Notifications.error("Cannot remove flow, it is currently being used as the browser flow."); + + } else if (realm.registrationFlow == $scope.flow.alias) { + Notifications.error("Cannot remove flow, it is currently being used as the registration flow."); + + } else if (realm.directGrantFlow == $scope.flow.alias) { + Notifications.error("Cannot remove flow, it is currently being used as the direct grant flow."); + + } else if (realm.resetCredentialsFlow == $scope.flow.alias) { + Notifications.error("Cannot remove flow, it is currently being used as the reset credentials flow."); + + } else if (realm.clientAuthenticationFlow == $scope.flow.alias) { + Notifications.error("Cannot remove flow, it is currently being used as the client authentication flow."); + + } else if (realm.dockerAuthenticationFlow == $scope.flow.alias) { + Notifications.error("Cannot remove flow, it is currently being used as the docker authentication flow."); + } else { + AuthenticationFlows.remove({realm: realm.realm, flow: $scope.flow.id}, function () { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + flows[0].alias); + Notifications.success("Flow removed"); + }) + } + + }; + + $scope.editFlow = function(flow) { + var copy = angular.copy(flow); + UpdateDialog.open('Update Authentication Flow', copy.alias, copy.description, function(name, desc) { + copy.alias = name; + copy.description = desc; + AuthenticationFlowsUpdate.update({realm: realm.realm, flow: flow.id}, copy, function() { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + name); + Notifications.success("Flow updated"); + }); + }) + }; + + $scope.addFlow = function() { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/flow/execution/' + $scope.flow.id); + + } + + $scope.addSubFlow = function(execution) { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + execution.flowId + '/create/flow/execution/' + $scope.flow.alias); + + } + + $scope.addSubFlowExecution = function(execution) { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + execution.flowId + '/create/execution/' + $scope.flow.alias); + + } + + $scope.addExecution = function() { + $location.url("/realms/" + realm.realm + '/authentication/flows/' + $scope.flow.id + '/create/execution/' + $scope.flow.id); + + } + + $scope.createFlow = function() { + $location.url("/realms/" + realm.realm + '/authentication/create/flow'); + } + + $scope.updateExecution = function(execution) { + var copy = angular.copy(execution); + delete copy.empties; + delete copy.levels; + delete copy.preLevels; + delete copy.postLevels; + AuthenticationFlowExecutions.update({realm: realm.realm, alias: $scope.flow.alias}, copy, function() { + Notifications.success("Auth requirement updated"); + setupForm(); + }); + + }; + + $scope.editExecutionFlow = function(execution) { + var copy = angular.copy(execution); + delete copy.empties; + delete copy.levels; + delete copy.preLevels; + delete copy.postLevels; + UpdateDialog.open('Update Execution Flow', copy.displayName, copy.description, function(name, desc) { + copy.displayName = name; + copy.description = desc; + AuthenticationFlowExecutions.update({realm: realm.realm, alias: $scope.flow.alias}, copy, function() { + Notifications.success("Execution Flow updated"); + setupForm(); + }); + }) + }; + + $scope.removeExecution = function(execution) { + console.log('removeExecution: ' + execution.id); + var exeOrFlow = execution.authenticationFlow ? 'flow' : 'execution'; + Dialog.confirmDelete(execution.displayName, exeOrFlow, function() { + AuthenticationExecution.remove({realm: realm.realm, execution: execution.id}, function() { + Notifications.success("The " + exeOrFlow + " was removed."); + setupForm(); + }); + }); + + } + + $scope.raisePriority = function(execution) { + AuthenticationExecutionRaisePriority.save({realm: realm.realm, execution: execution.id}, function() { + Notifications.success("Priority raised"); + setupForm(); + }) + } + + $scope.lowerPriority = function(execution) { + AuthenticationExecutionLowerPriority.save({realm: realm.realm, execution: execution.id}, function() { + Notifications.success("Priority lowered"); + setupForm(); + }) + } + + $scope.setupForm = setupForm; + + if (selectedFlow == null) { + $scope.selectFlow(flows[0]); + } else { + setupForm(); + } +}); + +module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredRequiredActions, + $modal, $route, + RegisterRequiredAction, RequiredActions, RequiredActionRaisePriority, RequiredActionLowerPriority, Notifications) { + console.log('RequiredActionsCtrl'); + $scope.realm = realm; + $scope.unregisteredRequiredActions = unregisteredRequiredActions; + $scope.requiredActions = []; + var setupRequiredActionsForm = function() { + console.log('setupRequiredActionsForm'); + RequiredActions.query({realm: realm.realm}, function(data) { + $scope.requiredActions = []; + for (var i = 0; i < data.length; i++) { + $scope.requiredActions.push(data[i]); + } + }); + }; + + $scope.updateRequiredAction = function(action) { + RequiredActions.update({realm: realm.realm, alias: action.alias}, action, function() { + Notifications.success("Required action updated"); + setupRequiredActionsForm(); + }); + } + + $scope.raisePriority = function(action) { + RequiredActionRaisePriority.save({realm: realm.realm, alias: action.alias}, function() { + Notifications.success("Required action's priority raised"); + setupRequiredActionsForm(); + }) + } + + $scope.lowerPriority = function(action) { + RequiredActionLowerPriority.save({realm: realm.realm, alias: action.alias}, function() { + Notifications.success("Required action's priority lowered"); + setupRequiredActionsForm(); + }) + } + + $scope.register = function() { + var controller = function($scope, $modalInstance) { + $scope.unregisteredRequiredActions = unregisteredRequiredActions; + $scope.selected = { + selected: $scope.unregisteredRequiredActions[0] + } + $scope.ok = function () { + $modalInstance.close(); + RegisterRequiredAction.save({realm: realm.realm}, $scope.selected.selected, function() { + $route.reload(); + }); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + } + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/unregistered-required-action-selector.html', + controller: controller, + resolve: { + } + }); + } + + setupRequiredActionsForm(); + + +}); + +module.controller('AuthenticationConfigCtrl', function($scope, realm, flow, configType, config, AuthenticationConfig, Notifications, + Dialog, $location, ComponentUtils) { + $scope.realm = realm; + $scope.flow = flow; + $scope.configType = configType; + $scope.create = false; + $scope.config = angular.copy(config); + $scope.changed = false; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch('config', function() { + if (!angular.equals($scope.config, config)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + var configCopy = angular.copy($scope.config); + ComponentUtils.convertAllListValuesToMultivaluedString(configType.properties, configCopy.config); + + AuthenticationConfig.update({ + realm : realm.realm, + config : config.id + }, configCopy, function() { + $scope.changed = false; + config = angular.copy($scope.config); + $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + config.id); + Notifications.success("Your changes have been saved."); + }); + }; + + $scope.reset = function() { + $scope.config = angular.copy(config); + $scope.changed = false; + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.config.alias, 'config', function() { + AuthenticationConfig.remove({ realm: realm.realm, config : $scope.config.id }, function() { + Notifications.success("The config has been deleted."); + $location.url("/realms/" + realm.realm + '/authentication/flows/' + flow.id); + }); + }); + }; + +}); + +module.controller('AuthenticationConfigCreateCtrl', function($scope, realm, flow, configType, execution, AuthenticationExecutionConfig, + Notifications, Dialog, $location, ComponentUtils) { + $scope.realm = realm; + $scope.flow = flow; + $scope.create = true; + $scope.configType = configType; + + var defaultConfig = {}; + if (configType && Array.isArray(configType.properties)) { + for(var i = 0; i < configType.properties.length; i++) { + var property = configType.properties[i]; + if (property && property.name) { + defaultConfig[property.name] = property.defaultValue; + } + } + } + + $scope.config = { config: defaultConfig}; + + $scope.$watch(function() { + return $location.path(); + }, function() { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.save = function() { + var configCopy = angular.copy($scope.config); + ComponentUtils.convertAllListValuesToMultivaluedString(configType.properties, configCopy.config); + + AuthenticationExecutionConfig.save({ + realm : realm.realm, + execution: execution + }, configCopy, function(data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + var url = "/realms/" + realm.realm + '/authentication/flows/' + flow.id + '/config/' + configType.providerId + "/" + id; + console.log('redirect url: ' + url); + $location.url(url); + Notifications.success("Config has been created."); + }); + }; + + $scope.cancel = function() { + //$location.url("/realms"); + window.history.back(); + }; +}); + +module.controller('ClientInitialAccessCtrl', function($scope, realm, clientInitialAccess, ClientInitialAccess, Dialog, Notifications, $route, $location) { + $scope.realm = realm; + $scope.clientInitialAccess = clientInitialAccess; + + $scope.remove = function(id) { + Dialog.confirmDelete(id, 'initial access token', function() { + ClientInitialAccess.remove({ realm: realm.realm, id: id }, function() { + Notifications.success("The initial access token was deleted."); + $route.reload(); + }); + }); + } +}); + +module.controller('ClientInitialAccessCreateCtrl', function($scope, realm, ClientInitialAccess, TimeUnit, Dialog, $location, $translate) { + $scope.expirationUnit = 'Days'; + $scope.expiration = TimeUnit.toUnit(0, $scope.expirationUnit); + $scope.count = 1; + $scope.realm = realm; + + $scope.save = function() { + var expiration = TimeUnit.toSeconds($scope.expiration, $scope.expirationUnit); + ClientInitialAccess.save({ + realm: realm.realm + }, { expiration: expiration, count: $scope.count}, function (data) { + console.debug(data); + $scope.id = data.id; + $scope.token = data.token; + }); + }; + + $scope.cancel = function() { + $location.url('/realms/' + realm.realm + '/client-registration/client-initial-access'); + }; + + $scope.done = function() { + var btns = { + ok: { + label: $translate.instant('continue'), + cssClass: 'btn btn-primary' + }, + cancel: { + label: $translate.instant('cancel'), + cssClass: 'btn btn-default' + } + } + + var title = $translate.instant('initial-access-token.confirm.title'); + var message = $translate.instant('initial-access-token.confirm.text'); + Dialog.open(title, message, btns, function() { + $location.url('/realms/' + realm.realm + '/client-registration/client-initial-access'); + }); + }; +}); + +module.controller('ClientRegPoliciesCtrl', function($scope, realm, clientRegistrationPolicyProviders, policies, Dialog, Notifications, Components, $route, $location) { + $scope.realm = realm; + $scope.providers = clientRegistrationPolicyProviders; + $scope.anonPolicies = []; + $scope.authPolicies = []; + for (var i=0 ; i { + $scope.headerTitle = translatedValue; + }).catch(() => { + $scope.headerTitle = $scope.instance.providerId; + }); + + if ($scope.create) { + $scope.instance.name = ""; + $scope.instance.parentId = realm.id; + $scope.instance.config = {}; + + if ($scope.providerType.properties) { + + for (let i = 0; i < $scope.providerType.properties.length; i++) { + let configProperty = $scope.providerType.properties[i]; + $scope.instance.config[configProperty.name] = toDefaultValue(configProperty); + } + } + } + + if ($scope.providerType.properties) { + ComponentUtils.addLastEmptyValueToMultivaluedLists($scope.providerType.properties, $scope.instance.config); + ComponentUtils.addMvOptionsToMultivaluedLists($scope.providerType.properties); + } + + let oldCopy = angular.copy($scope.instance); + $scope.changed = false; + + $scope.$watch('instance', function() { + if (!angular.equals($scope.instance, oldCopy)) { + $scope.changed = true; + } + }, true); + + $scope.reset = function() { + $scope.create ? window.history.back() : $route.reload(); + }; + + $scope.hasValidValues = () => $scope.changed && $scope.instance.name; + + $scope.save = function() { + $scope.changed = false; + if ($scope.create) { + Components.save({realm: realm.realm}, $scope.instance, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + $location.url("/realms/" + realm.realm + "/client-registration/client-reg-policies/" + $scope.instance.providerId + "/" + id); + Notifications.success("The policy has been created."); + }); + } else { + Components.update({realm: realm.realm, + componentId: instance.id + }, + $scope.instance, function () { + $route.reload(); + Notifications.success("The policy has been updated."); + }); + } + }; + +}); + +module.controller('RealmImportCtrl', function($scope, realm, $route, + Notifications, $modal, $resource) { + $scope.rawContent = {}; + $scope.fileContent = { + enabled: true + }; + $scope.changed = false; + $scope.files = []; + $scope.realm = realm; + $scope.overwrite = false; + $scope.skip = false; + $scope.importUsers = false; + $scope.importGroups = false; + $scope.importClients = false; + $scope.importIdentityProviders = false; + $scope.importRealmRoles = false; + $scope.importClientRoles = false; + $scope.ifResourceExists='FAIL'; + $scope.isMultiRealm = false; + $scope.results = {}; + $scope.currentPage = 0; + var pageSize = 15; + + var oldCopy = angular.copy($scope.fileContent); + + $scope.importFile = function($fileContent){ + var parsed; + try { + parsed = JSON.parse($fileContent); + } catch (e) { + Notifications.error('Unable to parse JSON file.'); + return; + } + + $scope.rawContent = angular.copy(parsed); + if (($scope.rawContent instanceof Array) && ($scope.rawContent.length > 0)) { + if ($scope.rawContent.length > 1) $scope.isMultiRealm = true; + $scope.fileContent = $scope.rawContent[0]; + } else { + $scope.fileContent = $scope.rawContent; + } + + $scope.importing = true; + setOnOffSwitchDefaults(); + $scope.results = {}; + if (!$scope.hasResources()) { + $scope.nothingToImport(); + } + }; + + $scope.hasResults = function() { + return (Object.keys($scope.results).length > 0) && + ($scope.results.results !== undefined) && + ($scope.results.results.length > 0); + } + + $scope.resultsPage = function() { + if (!$scope.hasResults()) return {}; + return $scope.results.results.slice(startIndex(), endIndex()); + } + + function startIndex() { + return pageSize * $scope.currentPage; + } + + function endIndex() { + var length = $scope.results.results.length; + var endIndex = startIndex() + pageSize; + if (endIndex > length) endIndex = length; + return endIndex; + } + + function setOnOffSwitchDefaults() { + $scope.importUsers = $scope.hasArray('users'); + $scope.importGroups = $scope.hasArray('groups'); + $scope.importClients = $scope.hasArray('clients'); + $scope.importIdentityProviders = $scope.hasArray('identityProviders'); + $scope.importRealmRoles = $scope.hasRealmRoles(); + $scope.importClientRoles = $scope.hasClientRoles(); + } + + $scope.setFirstPage = function() { + $scope.currentPage = 0; + } + + $scope.setNextPage = function() { + $scope.currentPage++; + } + + $scope.setPreviousPage = function() { + $scope.currentPage--; + } + + $scope.hasNext = function() { + if (!$scope.hasResults()) return false; + var length = $scope.results.results.length; + //console.log('length=' + length); + var endIndex = startIndex() + pageSize; + //console.log('endIndex=' + endIndex); + return length > endIndex; + } + + $scope.hasPrevious = function() { + if (!$scope.hasResults()) return false; + return $scope.currentPage > 0; + } + + $scope.viewImportDetails = function() { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/view-object.html', + controller: 'ObjectModalCtrl', + resolve: { + object: function () { + return $scope.fileContent; + } + } + }) + }; + + $scope.hasArray = function(section) { + return ($scope.fileContent !== 'undefined') && + ($scope.fileContent.hasOwnProperty(section)) && + ($scope.fileContent[section] instanceof Array) && + ($scope.fileContent[section].length > 0); + } + + $scope.hasRealmRoles = function() { + return $scope.hasRoles() && + ($scope.fileContent.roles.hasOwnProperty('realm')) && + ($scope.fileContent.roles.realm instanceof Array) && + ($scope.fileContent.roles.realm.length > 0); + } + + $scope.hasRoles = function() { + return ($scope.fileContent !== 'undefined') && + ($scope.fileContent.hasOwnProperty('roles')) && + ($scope.fileContent.roles !== 'undefined'); + } + + $scope.hasClientRoles = function() { + return $scope.hasRoles() && + ($scope.fileContent.roles.hasOwnProperty('client')) && + (Object.keys($scope.fileContent.roles.client).length > 0); + } + + $scope.itemCount = function(section) { + if (!$scope.importing) return 0; + if ($scope.hasRealmRoles() && (section === 'roles.realm')) return $scope.fileContent.roles.realm.length; + if ($scope.hasClientRoles() && (section === 'roles.client')) return clientRolesCount($scope.fileContent.roles.client); + + if (!$scope.fileContent.hasOwnProperty(section)) return 0; + + return $scope.fileContent[section].length; + } + + clientRolesCount = function(clientRoles) { + var total = 0; + for (var clientName in clientRoles) { + total += clientRoles[clientName].length; + } + return total; + } + + $scope.hasResources = function() { + return ($scope.importUsers && $scope.hasArray('users')) || + ($scope.importGroups && $scope.hasArray('groups')) || + ($scope.importClients && $scope.hasArray('clients')) || + ($scope.importIdentityProviders && $scope.hasArray('identityProviders')) || + ($scope.importRealmRoles && $scope.hasRealmRoles()) || + ($scope.importClientRoles && $scope.hasClientRoles()); + } + + $scope.nothingToImport = function() { + Notifications.error('No resources specified to import.'); + } + + $scope.$watch('fileContent', function() { + if (!angular.equals($scope.fileContent, oldCopy)) { + $scope.changed = true; + } + setOnOffSwitchDefaults(); + }, true); + + $scope.successMessage = function() { + var message = $scope.results.added + ' records added. '; + if ($scope.ifResourceExists === 'SKIP') { + message += $scope.results.skipped + ' records skipped.' + } + if ($scope.ifResourceExists === 'OVERWRITE') { + message += $scope.results.overwritten + ' records overwritten.'; + } + return message; + } + + $scope.save = function() { + var json = angular.copy($scope.fileContent); + json.ifResourceExists = $scope.ifResourceExists; + if (!$scope.importUsers) delete json.users; + if (!$scope.importGroups) delete json.groups; + if (!$scope.importIdentityProviders) delete json.identityProviders; + if (!$scope.importClients) delete json.clients; + + if (json.hasOwnProperty('roles')) { + if (!$scope.importRealmRoles) delete json.roles.realm; + if (!$scope.importClientRoles) delete json.roles.client; + } + + var importFile = $resource(authUrl + '/admin/realms/' + realm.realm + '/partialImport'); + $scope.results = importFile.save(json, function() { + Notifications.success($scope.successMessage()); + }, function(error) { + if (error.data.errorMessage) { + Notifications.error(error.data.errorMessage); + } else { + Notifications.error('Unexpected error during import'); + } + }); + }; + + $scope.reset = function() { + $route.reload(); + } + +}); + +module.controller('RealmExportCtrl', function($scope, realm, $http, + $httpParamSerializer, Notifications, Dialog) { + $scope.realm = realm; + $scope.exportGroupsAndRoles = false; + $scope.exportClients = false; + + $scope.export = function() { + if ($scope.exportGroupsAndRoles || $scope.exportClients) { + Dialog.confirm('Export', 'This operation may make server unresponsive for a while.\n\nAre you sure you want to proceed?', download); + } else { + download(); + } + } + + function download() { + var exportUrl = authUrl + '/admin/realms/' + realm.realm + '/partial-export'; + var params = {}; + if ($scope.exportGroupsAndRoles) { + params['exportGroupsAndRoles'] = true; + } + if ($scope.exportClients) { + params['exportClients'] = true; + } + if (Object.keys(params).length > 0) { + exportUrl += '?' + $httpParamSerializer(params); + } + $http.post(exportUrl) + .then(function(response) { + var download = angular.fromJson(response.data); + download = angular.toJson(download, true); + saveAs(new Blob([download], { type: 'application/json' }), 'realm-export.json'); + }).catch(function() { + Notifications.error("Sorry, something went wrong."); + }); + } +}); diff --git a/base/admin/resources/js/controllers/roles.js b/base/admin/resources/js/controllers/roles.js new file mode 100644 index 0000000..bc24c57 --- /dev/null +++ b/base/admin/resources/js/controllers/roles.js @@ -0,0 +1,48 @@ +module.controller('RoleMembersCtrl', function($scope, realm, role, RoleMembership, Dialog, Notifications, $location, RealmRoleRemover) { + $scope.realm = realm; + $scope.page = 0; + $scope.role = role; + + $scope.query = { + realm: realm.realm, + role: role.name, + max : 5, + first : 0 + } + + $scope.remove = function() { + RealmRoleRemover.remove($scope.role, realm, Dialog, $location, Notifications); + }; + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + console.log("query.search: " + $scope.query.search); + $scope.searchLoaded = false; + + $scope.users = RoleMembership.query($scope.query, function() { + console.log('search loaded'); + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); + +}); diff --git a/base/admin/resources/js/controllers/users.js b/base/admin/resources/js/controllers/users.js new file mode 100755 index 0000000..34df029 --- /dev/null +++ b/base/admin/resources/js/controllers/users.js @@ -0,0 +1,2044 @@ +module.controller('UserRoleMappingCtrl', function($scope, $http, $route, realm, user, client, Client, Notifications, RealmRoleMapping, + ClientRoleMapping, AvailableRealmRoleMapping, AvailableClientRoleMapping, + CompositeRealmRoleMapping, CompositeClientRoleMapping, $translate) { + $scope.realm = realm; + $scope.user = user; + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.client = client; + $scope.clientRoles = []; + $scope.clientComposite = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + $scope.dummymodel = []; + $scope.selectedClient = null; + + + $scope.realmMappings = RealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + + $scope.addRealmRole = function() { + $scope.realmRolesToAdd = JSON.parse('[' + $scope.selectedRealmRoles + ']'); + $scope.selectedRealmRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/realm', + $scope.realmRolesToAdd).then(function() { + $scope.realmMappings = RealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.selectedClient) { + console.log('load available'); + $scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + Notifications.success($translate.instant('user.roles.add.success')); + + }); + }; + + $scope.deleteRealmRole = function() { + $scope.realmRolesToRemove = JSON.parse('[' + $scope.selectedRealmMappings + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/realm', + {data : $scope.realmRolesToRemove, headers : {"content-type" : "application/json"}}).then(function() { + $scope.realmMappings = RealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.selectedClient) { + console.log('load available'); + $scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + Notifications.success($translate.instant('user.roles.remove.success')); + }); + }; + + $scope.addClientRole = function() { + $scope.clientRolesToAdd = JSON.parse('[' + $scope.selectedClientRoles + ']'); + $http.post(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/clients/' + $scope.selectedClient.id, + $scope.clientRolesToAdd).then(function() { + $scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + Notifications.success($translate.instant('user.roles.add.success')); + }); + }; + + $scope.deleteClientRole = function() { + $scope.clientRolesToRemove = JSON.parse('[' + $scope.selectedClientMappings + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/clients/' + $scope.selectedClient.id, + {data : $scope.clientRolesToRemove, headers : {"content-type" : "application/json"}}).then(function() { + $scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + $scope.realmRoles = AvailableRealmRoleMapping.query({realm : realm.realm, userId : user.id}); + Notifications.success($translate.instant('user.roles.remove.success')); + }); + }; + + $scope.changeClient = function(client) { + console.log("selected client: ", client); + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } else { + $scope.selectedClient = client; + } + if ($scope.selectedClient) { + console.log('load available'); + $scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + $scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.selectedClient.id}); + } else { + $scope.clientRoles = null; + $scope.clientMappings = null; + $scope.clientComposite = null; + } + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + }; + + clientSelectControl($scope, $route.current.params.realm, Client); +}); + +module.controller('UserSessionsCtrl', function($scope, realm, user, sessions, UserSessions, UserLogout, UserSessionLogout, Notifications, $translate) { + $scope.realm = realm; + $scope.user = user; + $scope.sessions = sessions; + + $scope.logoutAll = function() { + UserLogout.save({realm : realm.realm, user: user.id}, function () { + Notifications.success($translate.instant('user.logout.all.success')); + UserSessions.query({realm: realm.realm, user: user.id}, function(updated) { + $scope.sessions = updated; + }) + }); + }; + + $scope.logoutSession = function(sessionId) { + console.log('here in logoutSession'); + UserSessionLogout.delete({realm : realm.realm, session: sessionId}, function() { + UserSessions.query({realm: realm.realm, user: user.id}, function(updated) { + $scope.sessions = updated; + Notifications.success($translate.instant('user.logout.session.success')); + }) + }); + } +}); + +module.controller('UserFederatedIdentityCtrl', function($scope, $location, realm, user, federatedIdentities, UserFederatedIdentity, Notifications, Dialog, $translate) { + $scope.realm = realm; + $scope.user = user; + $scope.federatedIdentities = federatedIdentities; + + $scope.hasAnyProvidersToCreate = function() { + return realm.identityProviders.length - $scope.federatedIdentities.length > 0; + } + + $scope.removeProviderLink = function(providerLink) { + + console.log("Removing provider link: " + providerLink.identityProvider); + + Dialog.confirmWithButtonText( + $translate.instant('user.fedid.link.remove.confirm.title', {name: providerLink.identityProvider}), + $translate.instant('user.fedid.link.remove.confirm.message', {name: providerLink.identityProvider}), + $translate.instant('dialogs.delete.confirm'), + function() { + UserFederatedIdentity.remove({ realm: realm.realm, user: user.id, provider: providerLink.identityProvider }, function() { + Notifications.success($translate.instant('user.fedid.link.remove.success')); + var indexToRemove = $scope.federatedIdentities.indexOf(providerLink); + $scope.federatedIdentities.splice(indexToRemove, 1); + }); + } + ); + } +}); + +module.controller('UserFederatedIdentityAddCtrl', function($scope, $location, realm, user, federatedIdentities, UserFederatedIdentity, Notifications, $translate) { + $scope.realm = realm; + $scope.user = user; + $scope.federatedIdentity = {}; + + var getAvailableProvidersToCreate = function() { + var realmProviders = []; + for (var i=0 ; i 0) { + $scope.previousPage(); + } + + Notifications.success($translate.instant('user.remove.success')); + }, function() { + Notifications.error($translate.instant('user.remove.error')); + }); + } + ); + }; +}); + + +module.controller('UserTabCtrl', function($scope, $location, Dialog, Notifications, Current) { + $scope.removeUser = function() { + Dialog.confirmDelete($scope.user.id, 'user', function() { + $scope.user.$remove({ + realm : Current.realm.realm, + userId : $scope.user.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/users"); + Notifications.success($translate.instant('user.remove.success')); + }, function() { + Notifications.error($translate.instant('user.remove.error')); + }); + }); + }; +}); + +function loadUserStorageLink(realm, user, console, Components, UserStorageOperations, $scope, $location) { + if(user.federationLink) { + console.log("federationLink is not null. It is " + user.federationLink); + + if ($scope.access.viewRealm) { + Components.get({realm: realm.realm, componentId: user.federationLink}, function (link) { + $scope.federationLinkName = link.name; + $scope.federationLink = "#/realms/" + realm.realm + "/user-storage/providers/" + link.providerId + "/" + link.id; + }); + } else { + // KEYCLOAK-4328 + UserStorageOperations.simpleName.get({realm: realm.realm, componentId: user.federationLink}, function (link) { + $scope.federationLinkName = link.name; + $scope.federationLink = $location.absUrl(); + }) + } + + } else { + console.log("federationLink is null"); + } + + if(user.origin) { + if ($scope.access.viewRealm) { + Components.get({realm: realm.realm, componentId: user.origin}, function (link) { + $scope.originName = link.name; + $scope.originLink = "#/realms/" + realm.realm + "/user-storage/providers/" + link.providerId + "/" + link.id; + }) + } + else { + // KEYCLOAK-4328 + UserStorageOperations.simpleName.get({realm: realm.realm, componentId: user.origin}, function (link) { + $scope.originName = link.name; + $scope.originLink = $location.absUrl(); + }) + } + } else { + console.log("origin is null"); + } +}; + +module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser, User, + Components, + UserImpersonation, RequiredActions, + UserStorageOperations, + $location, $http, Dialog, Notifications, $translate, Groups) { + $scope.realm = realm; + $scope.create = !user.id; + $scope.editUsername = $scope.create || $scope.realm.editUsernameAllowed; + $scope.emailAsUsername = $scope.realm.registrationEmailAsUsername; + $scope.groupSearch = { selectedGroup : null }; + + if ($scope.create) { + $scope.user = { enabled: true, attributes: {}, groups: [] } + } else { + if (!user.attributes) { + user.attributes = {} + } + convertAttributeValuesToString(user); + + + $scope.user = angular.copy(user); + $scope.impersonate = function() { + UserImpersonation.save({realm : realm.realm, user: $scope.user.id}, function (data) { + if (data.sameRealm) { + window.location = data.redirect; + } else { + window.open(data.redirect, "_blank"); + } + }); + }; + + loadUserStorageLink(realm, user, console, Components, UserStorageOperations, $scope, $location); + + console.log('realm brute force? ' + realm.bruteForceProtected) + $scope.temporarilyDisabled = false; + var isDisabled = function () { + BruteForceUser.get({realm: realm.realm, userId: user.id}, function(data) { + console.log('here in isDisabled ' + data.disabled); + $scope.temporarilyDisabled = data.disabled; + }); + }; + + console.log("check if disabled"); + isDisabled(); + + $scope.unlockUser = function() { + BruteForceUser.delete({realm: realm.realm, userId: user.id}, function(data) { + isDisabled(); + }); + } + } + + $scope.changed = false; // $scope.create; + if (user.requiredActions) { + for (var i = 0; i < user.requiredActions.length; i++) { + console.log("user require action: " + user.requiredActions[i]); + } + } + // ID - Name map for required actions. IDs are enum names. + RequiredActions.query({realm: realm.realm}, function(data) { + $scope.userReqActionList = []; + for (var i = 0; i < data.length; i++) { + console.log("listed required action: " + data[i].name); + if (data[i].enabled) { + var item = data[i]; + $scope.userReqActionList.push(item); + } + } + console.log("---------------------"); + console.log("ng-model: user.requiredActions=" + JSON.stringify($scope.user.requiredActions)); + console.log("---------------------"); + console.log("ng-repeat: userReqActionList=" + JSON.stringify($scope.userReqActionList)); + console.log("---------------------"); + }); + $scope.$watch('user', function() { + if (!angular.equals($scope.user, user)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + convertAttributeValuesToLists(); + + if ($scope.create) { + pushSelectedGroupsToUser(); + + User.save({ + realm: realm.realm + }, $scope.user, function (data, headers) { + $scope.changed = false; + convertAttributeValuesToString($scope.user); + user = angular.copy($scope.user); + var l = headers().location; + + console.debug("Location == " + l); + + var id = l.substring(l.lastIndexOf("/") + 1); + + + $location.url("/realms/" + realm.realm + "/users/" + id); + Notifications.success($translate.instant('user.create.success')); + }); + } else { + User.update({ + realm: realm.realm, + userId: $scope.user.id + }, $scope.user, function () { + $scope.changed = false; + convertAttributeValuesToString($scope.user); + user = angular.copy($scope.user); + Notifications.success($translate.instant('user.edit.success')); + }); + } + }; + + function convertAttributeValuesToLists() { + var attrs = $scope.user.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "string") { + var attrVals = attrs[attribute].split("##"); + attrs[attribute] = attrVals; + } + } + } + + function convertAttributeValuesToString(user) { + var attrs = user.attributes; + for (var attribute in attrs) { + if (typeof attrs[attribute] === "object") { + var attrVals = attrs[attribute].join("##"); + attrs[attribute] = attrVals; + } + } + } + + function pushSelectedGroupsToUser() { + var groups = $scope.user.groups; + if ($scope.selectedGroups) { + for (i = 0; i < $scope.selectedGroups.length; i++) { + var groupPath = $scope.selectedGroups[i].path; + if (!groups.includes(groupPath)) { + groups.push(groupPath); + } + } + } + } + + function bfs(tree, collection) { + if (!tree["subGroups"] || tree["subGroups"].length === 0) return; + for (var i=0; i < tree["subGroups"].length; i++) { + var child = tree["subGroups"][i] + collection.push(child); + bfs(child, collection); + } + return; + } + + function flattenGroups(groups) { + var flattenedGroups = []; + if (!groups || groups.length === 0) return groups; + for (var i=0; i < groups.length; i++) { + flattenedGroups.push(groups[i]); + bfs(groups[i], flattenedGroups); + } + + return flattenedGroups; + } + + /** + * Only keep groups that : + * - include the search term in their path + * - are not already selected + */ + function filterSearchedGroups(groups, term, selectedGroups) { + if (!groups || groups.length === 0) return groups; + if (!selectedGroups) selectedGroups = []; + + return groups.filter(group => group.path?.includes(term) && !selectedGroups.some(selGroup => selGroup.id === group.id)); + } + + $scope.reset = function() { + $scope.user = angular.copy(user); + $scope.changed = false; + }; + + $scope.cancel = function() { + $location.url("/realms/" + realm.realm + "/users"); + }; + + $scope.addAttribute = function() { + $scope.user.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.user.attributes[key]; + } + + $scope.groupsUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + search: query.term.trim(), + max : 20, + first : 0 + }; + Groups.query($scope.query, function(response) { + data.results = filterSearchedGroups(flattenGroups(response), query.term.trim(), $scope.selectedGroups); + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.path; + return object.path; + } + }; + + $scope.removeGroup = function(list, group) { + for (i = 0; i < angular.copy(list).length; i++) { + if (group.id == list[i].id) { + list.splice(i, 1); + } + } + } + + $scope.selectGroup = function(group) { + if (!group || !group.id) { + return; + } + + $scope.groupSearch.selectedGroup = group; + + if (!$scope.selectedGroups) { + $scope.selectedGroups = []; + } + + for (i = 0; i < $scope.selectedGroups.length; i++) { + if ($scope.selectedGroups[i].id == group.id) { + return; + } + } + + $scope.selectedGroups.push(group); + $scope.groupSearch.selectedGroup = null; + } + + $scope.clearGroupSelection = function() { + $scope.groupSearch.selectedGroup = null; + $('#groups').val(null).trigger('change.select2'); + } +}); + +module.controller('UserCredentialsCtrl', function($scope, realm, user, $route, $location, RequiredActions, User, UserExecuteActionsEmail, + UserCredentials, Notifications, Dialog, TimeUnit2, Components, UserStorageOperations, $modal, $translate) { + console.log('UserCredentialsCtrl'); + + $scope.hasPassword = false; + + loadCredentials(); + + loadUserStorageLink(realm, user, console, Components, UserStorageOperations, $scope, $location); + + $scope.getUserStorageProviderName = function() { + return user.federationLink ? $scope.federationLinkName : $scope.originName; + } + + $scope.getUserStorageProviderLink = function() { + return user.federationLink ? $scope.federationLink : $scope.originLink; + } + + $scope.updateCredentialLabel = function(credential) { + UserCredentials.updateCredentialLabel({ realm: realm.realm, userId: user.id, credentialId: credential.id }, { + 'id': credential.id, + 'userLabel': credential.userLabel ? credential.userLabel : "", + // We JSONify the credential data + 'credentialData': JSON.stringify(credential.credentialData) + }, function() { + Notifications.success($translate.instant('user.credential.update.success')); + }, function(err) { + Notifications.error($translate.instant('user.credential.update.error')); + console.log(err); + }); + } + + $scope.deleteCredential = function(credential) { + Dialog.confirmWithButtonText( + $translate.instant('user.credential.remove.confirm.title', {name: credential.id}), + $translate.instant('user.credential.remove.confirm.message', {name: credential.id}), + $translate.instant('dialogs.delete.confirm'), + function() { + UserCredentials.deleteCredential({ realm: realm.realm, userId: user.id, credentialId: credential.id }, null, function() { + Notifications.success($translate.instant('user.credential.remove.success')); + $route.reload(); + }, function(err) { + Notifications.error($translate.instant('user.credential.remove.error')); + console.log(err); + }) + } + ); + } + + $scope.moveUp = function(credentials, index) { + // Safety first + if (index == 0) { + return; + } else if (index == 1) { + UserCredentials.moveToFirst( + { + realm: realm.realm, + userId: user.id, + credentialId: credentials[index].id + }, + function () { + $route.reload(); + }, + function (err) { + Notifications.error($translate.instant('user.credential.move-top.error')); + console.log(err); + }); + + } else { + UserCredentials.moveCredentialAfter( + { + realm: realm.realm, + userId: user.id, + credentialId: credentials[index].id, + newPreviousCredentialId: credentials[index - 2].id + }, + function () { + $route.reload(); + }, + function (err) { + Notifications.error($translate.instant('user.credential.move-up.error')); + console.log(err); + }); + } + } + + $scope.moveDown = function(credentials, index) { + // Safety first + if (index == credentials.length - 1) { + return; + } + UserCredentials.moveCredentialAfter( + { + realm: realm.realm, + userId: user.id, + credentialId: credentials[index].id, + newPreviousCredentialId: credentials[index + 1].id + }, + function() { + $route.reload(); + }, + function(err) { + Notifications.error($translate.instant('user.credential.move-down.error')); + console.log(err); + }); + } + + $scope.showData = function(credentialData) { + $modal.open({ + templateUrl: resourceUrl + '/partials/modal/user-credential-data.html', + controller: 'UserCredentialsDataModalCtrl', + resolve: { + credentialData: function () { + return credentialData; + } + } + }) + } + + $scope.realm = realm; + $scope.user = angular.copy(user); + $scope.temporaryPassword = true; + + $scope.isTotp = false; + if(!!user.totp){ + $scope.isTotp = user.totp; + } + // ID - Name map for required actions. IDs are enum names. + RequiredActions.query({realm: realm.realm}, function(data) { + $scope.userReqActionList = []; + for (var i = 0; i < data.length; i++) { + console.log("listed required action: " + data[i].name); + if (data[i].enabled) { + var item = data[i]; + $scope.userReqActionList.push(item); + } + } + + }); + + function loadCredentials() { + UserCredentials.getCredentials({ realm: realm.realm, userId: user.id }, null, function(credentials) { + $scope.credentials = credentials.map(function(c) { + // We de-JSONify the credential data + if (c.credentialData) { + c.credentialData = JSON.parse(c.credentialData); + } + if (c.type == 'password') { + $scope.hasPassword = true; + } + return c; + }); + }, function(err) { + Notifications.error($translate.instant('user.credential.fetch.error')); + console.log(err); + }); + + UserCredentials.getConfiguredUserStorageCredentialTypes({ realm: realm.realm, userId: user.id }, null, function(userStorageCredentialTypes) { + $scope.userStorageCredentialTypes = userStorageCredentialTypes; + $scope.hasPassword = $scope.hasPassword || userStorageCredentialTypes.lastIndexOf("password") > -1; + }, function(err) { + Notifications.error($translate.instant('user.credential.storage.fetch.error')); + console.log(err); + }); + } + + $scope.resetPassword = function() { + // hit enter without entering both fields - ignore + if (!$scope.passwordAndConfirmPasswordEntered()) return; + + if ($scope.pwdChange) { + if ($scope.password != $scope.confirmPassword) { + Notifications.error($translate.instant('user.password.error.not-matching')); + return; + } + } + + var msgTitle = ($scope.hasPassword ? $translate.instant('user.password.reset.confirm.title') : $translate.instant('user.password.set.confirm.title')); + var msg = ($scope.hasPassword ? $translate.instant('user.password.reset.confirm.message') : $translate.instant('user.password.set.confirm.message')); + var msgSuccess = ($scope.hasPassword ? $translate.instant('user.password.reset.success') : $translate.instant('user.password.set.success')); + + Dialog.confirm(msgTitle, msg, function() { + UserCredentials.resetPassword({ realm: realm.realm, userId: user.id }, { type : "password", value : $scope.password, temporary: $scope.temporaryPassword }, function() { + Notifications.success(msgSuccess); + $scope.password = null; + $scope.confirmPassword = null; + $route.reload(); + }); + }, function() { + $scope.password = null; + $scope.confirmPassword = null; + }); + }; + + $scope.passwordAndConfirmPasswordEntered = function() { + return $scope.password && $scope.confirmPassword; + } + + $scope.disableCredentialTypes = function() { + Dialog.confirm( + $translate.instant('user.credential.disable.confirm.title'), + $translate.instant('user.credential.disable.confirm.message'), + function() { + UserCredentials.disableCredentialTypes({ realm: realm.realm, userId: user.id }, $scope.disableableCredentialTypes, function() { + $route.reload(); + Notifications.success($translate.instant('user.credential.disable.confirm.success')); + }, function() { + Notifications.error($translate.instant('user.credential.disable.confirm.error')); + }); + }); + }; + + $scope.emailActions = []; + $scope.emailActionsTimeout = TimeUnit2.asUnit(realm.actionTokenGeneratedByAdminLifespan); + $scope.disableableCredentialTypes = []; + + $scope.sendExecuteActionsEmail = function() { + if ($scope.changed) { + Dialog.message($translate.instant('user.actions-email.send.pending-changes.title'), + $translate.instant('user.actions-email.send.pending-changes.message')); + return; + } + Dialog.confirm( + $translate.instant('user.actions-email.send.confirm.title'), + $translate.instant('user.actions-email.send.confirm.message'), + function() { + UserExecuteActionsEmail.update({ realm: realm.realm, userId: user.id, lifespan: $scope.emailActionsTimeout.toSeconds() }, $scope.emailActions, function() { + Notifications.success($translate.instant('user.actions-email.send.confirm.success')); + }, function() { + Notifications.error($translate.instant('user.actions-email.send.confirm.error')); + }); + }); + }; + + + + $scope.$watch('user', function() { + if (!angular.equals($scope.user, user)) { + $scope.userChange = true; + } else { + $scope.userChange = false; + } + }, true); + + $scope.$watch('password', function() { + if (!!$scope.password){ + $scope.pwdChange = true; + } else { + $scope.pwdChange = false; + } + }, true); + + $scope.reset = function() { + $scope.password = ""; + $scope.confirmPassword = ""; + + $scope.user = angular.copy(user); + + $scope.isTotp = false; + if(!!user.totp){ + $scope.isTotp = user.totp; + } + + $scope.pwdChange = false; + $scope.userChange = false; + }; +}); + +module.controller('UserCredentialsDataModalCtrl', function($scope, credentialData) { + $scope.credentialData = credentialData; + + $scope.keys = function(object) { + return object ? Object.keys(object) : []; + } +}); + +module.controller('UserFederationCtrl', function($scope, $location, $route, realm, serverInfo, Components, Notifications, Dialog, $translate) { + console.log('UserFederationCtrl ++++****'); + $scope.realm = realm; + $scope.providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider']; + $scope.instancesLoaded = false; + + if (!$scope.providers) $scope.providers = []; + + $scope.addProvider = function(provider) { + console.log('Add provider: ' + provider.id); + $location.url("/create/user-storage/" + realm.realm + "/providers/" + provider.id); + }; + + $scope.getInstanceLink = function(instance) { + return "/realms/" + realm.realm + "/user-storage/providers/" + instance.providerId + "/" + instance.id; + } + + $scope.getInstanceName = function(instance) { + return instance.name; + } + $scope.getInstanceProvider = function(instance) { + return instance.providerId; + } + + $scope.isProviderEnabled = function(instance) { + return !instance.config['enabled'] || instance.config['enabled'][0] == 'true'; + } + + $scope.getInstancePriority = function(instance) { + if (!instance.config['priority']) { + console.log('getInstancePriority is undefined'); + return -1; + } + return +instance.config['priority'][0]; + } + + Components.query({realm: realm.realm, + parent: realm.id, + type: 'org.keycloak.storage.UserStorageProvider' + }, function(data) { + $scope.instances = data; + $scope.instancesLoaded = true; + }); + + $scope.removeInstance = function(instance) { + Dialog.confirmWithButtonText( + $translate.instant('user.storage.remove.confirm.title', {name: instance.name}), + $translate.instant('user.storage.remove.confirm.message', {name: instance.name}), + $translate.instant('dialogs.delete.confirm'), + function() { + Components.remove({ + realm : realm.realm, + componentId : instance.id + }, function() { + $route.reload(); + Notifications.success($translate.instant('user.storage.remove.success')); + }); + } + ); + }; +}); + +module.controller('GenericUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, + serverInfo, instance, providerId, Components, UserStorageOperations, $translate) { + console.log('GenericUserStorageCtrl'); + console.log('providerId: ' + providerId); + $scope.create = !instance.providerId; + console.log('create: ' + $scope.create); + var providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider']; + console.log('providers length ' + providers.length); + var providerFactory = null; + for (var i = 0; i < providers.length; i++) { + var p = providers[i]; + console.log('provider: ' + p.id); + if (p.id == providerId) { + $scope.providerFactory = p; + providerFactory = p; + break; + } + + } + $scope.showSync = false; + $scope.changed = false; + + console.log("providerFactory: " + providerFactory.id); + + function initUserStorageSettings() { + if ($scope.create) { + $scope.changed = true; + instance.name = providerFactory.id; + instance.providerId = providerFactory.id; + instance.providerType = 'org.keycloak.storage.UserStorageProvider'; + instance.parentId = realm.id; + instance.config = { + + }; + instance.config['priority'] = ["0"]; + instance.config['enabled'] = ["true"]; + + $scope.fullSyncEnabled = false; + $scope.changedSyncEnabled = false; + if (providerFactory.metadata.synchronizable) { + instance.config['fullSyncPeriod'] = ['-1']; + instance.config['changedSyncPeriod'] = ['-1']; + + } + instance.config['cachePolicy'] = ['DEFAULT']; + instance.config['evictionDay'] = ['']; + instance.config['evictionHour'] = ['']; + instance.config['evictionMinute'] = ['']; + instance.config['maxLifespan'] = ['']; + if (providerFactory.properties) { + + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (configProperty.defaultValue) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } else { + instance.config[configProperty.name] = ['']; + } + + } + } + + } else { + $scope.changed = false; + $scope.fullSyncEnabled = (instance.config['fullSyncPeriod'] && instance.config['fullSyncPeriod'][0] > 0); + $scope.changedSyncEnabled = (instance.config['changedSyncPeriod'] && instance.config['changedSyncPeriod'][0]> 0); + if (providerFactory.metadata.synchronizable) { + if (!instance.config['fullSyncPeriod']) { + console.log('setting to -1'); + instance.config['fullSyncPeriod'] = ['-1']; + + } + if (!instance.config['changedSyncPeriod']) { + console.log('setting to -1'); + instance.config['changedSyncPeriod'] = ['-1']; + + } + } + if (!instance.config['enabled']) { + instance.config['enabled'] = ['true']; + } + if (!instance.config['cachePolicy']) { + instance.config['cachePolicy'] = ['DEFAULT']; + + } + if (!instance.config['evictionDay']) { + instance.config['evictionDay'] = ['']; + + } + if (!instance.config['evictionHour']) { + instance.config['evictionHour'] = ['']; + + } + if (!instance.config['evictionMinute']) { + instance.config['evictionMinute'] = ['']; + + } + if (!instance.config['maxLifespan']) { + instance.config['maxLifespan'] = ['']; + + } + if (!instance.config['priority']) { + instance.config['priority'] = ['0']; + } + + if (providerFactory.properties) { + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (!instance.config[configProperty.name]) { + instance.config[configProperty.name] = ['']; + } + } + } + + } + if (providerFactory.metadata.synchronizable) { + if (instance.config && instance.config['importEnabled']) { + $scope.showSync = instance.config['importEnabled'][0] == 'true'; + } else { + $scope.showSync = true; + } + } + + } + + initUserStorageSettings(); + $scope.instance = angular.copy(instance); + $scope.realm = realm; + + $scope.$watch('instance', function() { + if (!angular.equals($scope.instance, instance)) { + $scope.changed = true; + } + + }, true); + + $scope.$watch('fullSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['fullSyncPeriod'][0] = $scope.fullSyncEnabled ? "604800" : "-1"; + $scope.changed = true; + }); + + $scope.$watch('changedSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['changedSyncPeriod'][0] = $scope.changedSyncEnabled ? "86400" : "-1"; + $scope.changed = true; + }); + + + $scope.save = function() { + console.log('save provider'); + $scope.changed = false; + if ($scope.create) { + console.log('saving new provider'); + Components.save({realm: realm.realm}, $scope.instance, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id); + Notifications.success($translate.instant('user.storage.create.success')); + }); + } else { + console.log('update existing provider'); + Components.update({realm: realm.realm, + componentId: instance.id + }, + $scope.instance, function () { + $route.reload(); + Notifications.success($translate.instant('user.storage.edit.success')); + }); + } + }; + + $scope.reset = function() { + //initUserStorageSettings(); + //$scope.instance = angular.copy(instance); + $route.reload(); + }; + + $scope.cancel = function() { + console.log('cancel'); + if ($scope.create) { + $location.url("/realms/" + realm.realm + "/user-federation"); + } else { + $route.reload(); + } + }; + + $scope.triggerFullSync = function() { + console.log('GenericCtrl: triggerFullSync'); + triggerSync('triggerFullSync'); + } + + $scope.triggerChangedUsersSync = function() { + console.log('GenericCtrl: triggerChangedUsersSync'); + triggerSync('triggerChangedUsersSync'); + } + + function triggerSync(action) { + UserStorageOperations.sync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success($translate.instant('user.storage.sync.success',{status: syncResult.status})); + }, function() { + $route.reload(); + Notifications.error($translate.instant('user.storage.sync.error')); + }); + } + $scope.removeImportedUsers = function() { + UserStorageOperations.removeImportedUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success($translate.instant('user.storage.remove-users.success')); + }, function() { + $route.reload(); + Notifications.error($translate.instant('user.storage.remove-users.error')); + }); + }; + $scope.unlinkUsers = function() { + UserStorageOperations.unlinkUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success($translate.instant('user.storage.unlink.success')); + }, function() { + $route.reload(); + Notifications.error($translate.instant('user.storage.unlink.error')); + }); + }; + +}); + + +function removeGroupMember(groups, member) { + for (var j = 0; j < groups.length; j++) { + //console.log('checking: ' + groups[j].path); + if (member.path == groups[j].path) { + groups.splice(j, 1); + break; + } + if (groups[j].subGroups && groups[j].subGroups.length > 0) { + //console.log('going into subgroups'); + removeGroupMember(groups[j].subGroups, member); + } + } +} + +module.controller('UserGroupMembershipCtrl', function($scope, $q, realm, user, UserGroupMembership, UserGroupMembershipCount, UserGroupMapping, Notifications, Groups, GroupsCount, ComponentUtils, $translate) { + $scope.realm = realm; + $scope.user = user; + $scope.groupList = []; + $scope.allGroupMemberships = []; + $scope.groupMemberships = []; + $scope.tree = []; + $scope.membershipTree = []; + + $scope.searchCriteria = ''; + $scope.searchCriteriaMembership = ''; + $scope.currentPage = 1; + $scope.currentMembershipPage = 1; + $scope.currentPageInput = $scope.currentPage; + $scope.currentMembershipPageInput = $scope.currentMembershipPage; + $scope.pageSize = 20; + $scope.numberOfPages = 1; + $scope.numberOfMembershipPages = 1; + + var refreshCompleteUserGroupMembership = function() { + var queryParams = { + realm : realm.realm, + userId: user.id + }; + + var promiseGetCompleteUserGroupMembership = $q.defer(); + UserGroupMembership.query(queryParams, function(entry) { + promiseGetCompleteUserGroupMembership.resolve(entry); + }, function() { + promiseGetCompleteUserGroupMembership.reject($translate.instant('user.groups.fetch.all.error', {params: queryParams})); + }); + promiseGetCompleteUserGroupMembership.promise.then(function(groups) { + for (var i = 0; i < groups.length; i++) { + $scope.allGroupMemberships.push(groups[i]); + $scope.getGroupClass(groups[i]); + } + }, function (failed) { + Notifications.error(failed); + }); + return promiseGetCompleteUserGroupMembership.promise; + }; + + var refreshUserGroupMembership = function (search) { + $scope.currentMembershipPageInput = $scope.currentMembershipPage; + var first = ($scope.currentMembershipPage * $scope.pageSize) - $scope.pageSize; + var queryParams = { + realm : realm.realm, + userId: user.id, + first : first, + max : $scope.pageSize + }; + + var countParams = { + realm : realm.realm, + userId: user.id + }; + + var isSearch = function() { + return angular.isDefined(search) && search !== ''; + }; + + if (isSearch()) { + queryParams.search = search; + countParams.search = search; + } + + var promiseGetUserGroupMembership = $q.defer(); + UserGroupMembership.query(queryParams, function(entry) { + promiseGetUserGroupMembership.resolve(entry); + }, function() { + promiseGetUserGroupMembership.reject($translate.instant('user.groups.fetch.error', {params: queryParams})); + }); + + var promiseMembershipCount = $q.defer(); + + promiseGetUserGroupMembership.promise.then(function(groups) { + $scope.groupMemberships = groups; + UserGroupMembershipCount.query(countParams, function(entry) { + promiseMembershipCount.resolve(entry); + }, function() { + promiseMembershipCount.reject($translate.instant('user.groups.fetch.error', {params: countParams})); + }); + promiseMembershipCount.promise.then(function(membershipEntry) { + if(angular.isDefined(membershipEntry.count) && membershipEntry.count > $scope.pageSize) { + $scope.numberOfMembershipPages = Math.ceil(membershipEntry.count/$scope.pageSize); + } else { + $scope.numberOfMembershipPages = 1; + } + if (parseInt($scope.currentMembershipPage, 10) > $scope.numberOfMembershipPages) { + $scope.currentMembershipPage = $scope.numberOfMembershipPages; + } + }, function (failed) { + Notifications.error(failed); + }); + }, function (failed) { + Notifications.error(failed); + }); + + return promiseMembershipCount.promise; + }; + + var refreshAvailableGroups = function (search) { + $scope.currentPageInput = $scope.currentPage; + var first = ($scope.currentPage * $scope.pageSize) - $scope.pageSize; + var queryParams = { + realm : realm.realm, + first : first, + max : $scope.pageSize + }; + + var countParams = { + realm : realm.realm, + top : 'true' + }; + + if(angular.isDefined(search) && search !== '') { + queryParams.search = search; + countParams.search = search; + } + + var promiseGetGroups = $q.defer(); + Groups.query(queryParams, function(entry) { + promiseGetGroups.resolve(entry); + }, function() { + promiseGetGroups.reject($translate.instant('user.groups.fetch.error', {params: queryParams})); + }); + + var promiseCount = $q.defer(); + + promiseGetGroups.promise.then(function(groups) { + $scope.groupList = ComponentUtils.sortGroups('name', groups); + GroupsCount.query(countParams, function(entry) { + promiseCount.resolve(entry); + }, function() { + promiseCount.reject($translate.instant('user.groups.fetch.error', {params: countParams})); + }); + promiseCount.promise.then(function(entry) { + if(angular.isDefined(entry.count) && entry.count > $scope.pageSize) { + $scope.numberOfPages = Math.ceil(entry.count/$scope.pageSize); + } else { + $scope.numberOfPages = 1; + } + }, function (failed) { + Notifications.error(failed); + }); + }, function (failed) { + Notifications.error(failed); + }); + + return promiseCount.promise; + }; + + $scope.clearSearchMembership = function() { + $scope.searchCriteriaMembership = ''; + if (parseInt($scope.currentMembershipPage, 10) === 1) { + refreshUserGroupMembership(); + } else { + $scope.currentMembershipPage = 1; + } + }; + + $scope.searchGroupMembership = function() { + if (parseInt($scope.currentMembershipPage, 10) === 1) { + refreshUserGroupMembership($scope.searchCriteriaMembership); + } else { + $scope.currentMembershipPage = 1; + } + }; + + refreshUserGroupMembership().then(function() { + refreshAvailableGroups(); + refreshCompleteUserGroupMembership(); + }); + + $scope.$watch('currentPage', function(newValue, oldValue) { + if(parseInt(newValue, 10) !== parseInt(oldValue, 10)) { + refreshAvailableGroups($scope.searchCriteria); + } + }); + + $scope.$watch('currentMembershipPage', function(newValue, oldValue) { + if(parseInt(newValue, 10) !== parseInt(oldValue, 10)) { + refreshUserGroupMembership($scope.searchCriteriaMembership); + } + }); + + $scope.clearSearch = function() { + $scope.searchCriteria = ''; + if (parseInt($scope.currentPage, 10) === 1) { + refreshAvailableGroups(); + } else { + $scope.currentPage = 1; + } + }; + + $scope.searchGroup = function() { + if (parseInt($scope.currentPage, 10) === 1) { + refreshAvailableGroups($scope.searchCriteria); + } else { + $scope.currentPage = 1; + } + }; + + $scope.joinGroup = function() { + if (!$scope.tree.currentNode) { + Notifications.error($translate.instant('user.groups.join.error.no-group-selected')); + return; + } + if (isMember($scope.tree.currentNode)) { + Notifications.error($translate.instant('user.groups.join.error.already-added')); + return; + } + UserGroupMapping.update({realm: realm.realm, userId: user.id, groupId: $scope.tree.currentNode.id}, function() { + $scope.allGroupMemberships.push($scope.tree.currentNode); + refreshUserGroupMembership($scope.searchCriteriaMembership); + Notifications.success($translate.instant('user.groups.join.success')); + }); + + }; + + $scope.leaveGroup = function() { + if (!$scope.membershipTree.currentNode) { + Notifications.error($translate.instant('user.groups.leave.error.no-group-selected')); + return; + } + UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.membershipTree.currentNode.id}, function () { + removeGroupMember($scope.allGroupMemberships, $scope.membershipTree.currentNode); + refreshUserGroupMembership($scope.searchCriteriaMembership); + Notifications.success($translate.instant('user.groups.leave.success')); + }); + + }; + + var isLeaf = function(node) { + return node.id !== 'realm' && (!node.subGroups || node.subGroups.length === 0); + }; + + var isMember = function(node) { + for (var i = 0; i < $scope.allGroupMemberships.length; i++) { + var member = $scope.allGroupMemberships[i]; + if (node.id === member.id) { + return true; + } + } + return false; + }; + + $scope.getGroupClass = function(node) { + if (node.id == "realm") { + return 'pficon pficon-users'; + } + if (isMember(node)) { + return 'normal deactivate'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + } + + $scope.getSelectedClass = function(node) { + if (node.selected) { + if (isMember(node)) { + return "deactivate_selected"; + } else { + return 'selected'; + } + } else if ($scope.cutNode && $scope.cutNode.id === node.id) { + return 'cut'; + } + return undefined; + } + +}); + +module.controller('LDAPUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, + serverInfo, instance, Components, UserStorageOperations, + RealmLDAPConnectionTester, $http) { + console.log('LDAPUserStorageCtrl'); + var providerId = 'ldap'; + console.log('providerId: ' + providerId); + $scope.create = !instance.providerId; + console.log('create: ' + $scope.create); + var providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider']; + console.log('providers length ' + providers.length); + var providerFactory = null; + for (var i = 0; i < providers.length; i++) { + var p = providers[i]; + console.log('provider: ' + p.id); + if (p.id == providerId) { + $scope.providerFactory = p; + providerFactory = p; + break; + } + + } + + $scope.provider = instance; + $scope.showSync = false; + + if (serverInfo.profileInfo.name == 'community') { + $scope.ldapVendors = [ + {"id": "ad", "name": "Active Directory"}, + {"id": "rhds", "name": "Red Hat Directory Server"}, + {"id": "tivoli", "name": "Tivoli"}, + {"id": "edirectory", "name": "Novell eDirectory"}, + {"id": "other", "name": "Other"} + ]; + } else { + $scope.ldapVendors = [ + {"id": "ad", "name": "Active Directory"}, + {"id": "rhds", "name": "Red Hat Directory Server"} + ]; + } + + $scope.authTypes = [ + { "id": "none", "name": "none" }, + { "id": "simple", "name": "simple" } + ]; + + $scope.searchScopes = [ + { "id": "1", "name": "One Level" }, + { "id": "2", "name": "Subtree" } + ]; + + $scope.useTruststoreOptions = [ + { "id": "always", "name": "Always" }, + { "id": "ldapsOnly", "name": "Only for ldaps" }, + { "id": "never", "name": "Never" } + ]; + + var DEFAULT_BATCH_SIZE = "1000"; + + + console.log("providerFactory: " + providerFactory.id); + + $scope.changed = false; + function initUserStorageSettings() { + if ($scope.create) { + $scope.changed = true; + instance.name = 'ldap'; + instance.providerId = 'ldap'; + instance.providerType = 'org.keycloak.storage.UserStorageProvider'; + instance.parentId = realm.id; + instance.config = { + + }; + instance.config['enabled'] = ["true"]; + instance.config['priority'] = ["0"]; + + $scope.fullSyncEnabled = false; + $scope.changedSyncEnabled = false; + instance.config['fullSyncPeriod'] = ['-1']; + instance.config['changedSyncPeriod'] = ['-1']; + instance.config['cachePolicy'] = ['DEFAULT']; + instance.config['evictionDay'] = ['']; + instance.config['evictionHour'] = ['']; + instance.config['evictionMinute'] = ['']; + instance.config['maxLifespan'] = ['']; + instance.config['batchSizeForSync'] = [DEFAULT_BATCH_SIZE]; + //instance.config['importEnabled'] = ['true']; + + if (providerFactory.properties) { + + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (configProperty.defaultValue) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } else { + instance.config[configProperty.name] = ['']; + } + + } + } + + + } else { + $scope.changed = false; + $scope.fullSyncEnabled = (instance.config['fullSyncPeriod'] && instance.config['fullSyncPeriod'][0] > 0); + $scope.changedSyncEnabled = (instance.config['changedSyncPeriod'] && instance.config['changedSyncPeriod'][0]> 0); + if (!instance.config['fullSyncPeriod']) { + console.log('setting to -1'); + instance.config['fullSyncPeriod'] = ['-1']; + + } + if (!instance.config['enabled']) { + instance.config['enabled'] = ['true']; + } + if (!instance.config['changedSyncPeriod']) { + console.log('setting to -1'); + instance.config['changedSyncPeriod'] = ['-1']; + + } + if (!instance.config['cachePolicy']) { + instance.config['cachePolicy'] = ['DEFAULT']; + + } + if (!instance.config['evictionDay']) { + instance.config['evictionDay'] = ['']; + + } + if (!instance.config['evictionHour']) { + instance.config['evictionHour'] = ['']; + + } + if (!instance.config['evictionMinute']) { + instance.config['evictionMinute'] = ['']; + + } + if (!instance.config['maxLifespan']) { + instance.config['maxLifespan'] = ['']; + + } + if (!instance.config['priority']) { + instance.config['priority'] = ['0']; + } + if (!instance.config['importEnabled']) { + instance.config['importEnabled'] = ['true']; + } + + if (providerFactory.properties) { + + for (var i = 0; i < providerFactory.properties.length; i++) { + var configProperty = providerFactory.properties[i]; + if (!instance.config[configProperty.name]) { + if (configProperty.defaultValue) { + instance.config[configProperty.name] = [configProperty.defaultValue]; + } else { + instance.config[configProperty.name] = ['']; + } + } + + } + } + + for (var i=0 ; i<$scope.ldapVendors.length ; i++) { + if ($scope.ldapVendors[i].id === instance.config['vendor'][0]) { + $scope.vendorName = $scope.ldapVendors[i].name; + } + }; + + + + } + if (instance.config && instance.config['importEnabled']) { + $scope.showSync = instance.config['importEnabled'][0] == 'true'; + } else { + $scope.showSync = true; + } + + $scope.lastVendor = instance.config['vendor'][0]; + } + + initUserStorageSettings(); + $scope.instance = angular.copy(instance); + $scope.realm = realm; + + $scope.$watch('instance', function() { + if (!angular.equals($scope.instance, instance)) { + $scope.changed = true; + } + + if (!angular.equals($scope.instance.config['vendor'][0], $scope.lastVendor)) { + console.log("LDAP vendor changed. Previous=" + $scope.lastVendor + " New=" + $scope.instance.config['vendor'][0]); + $scope.lastVendor = $scope.instance.config['vendor'][0]; + + if ($scope.lastVendor === "ad") { + $scope.instance.config['usernameLDAPAttribute'][0] = "cn"; + $scope.instance.config['userObjectClasses'][0] = "person, organizationalPerson, user"; + } else { + $scope.instance.config['usernameLDAPAttribute'][0] = "uid"; + $scope.instance.config['userObjectClasses'][0] = "inetOrgPerson, organizationalPerson"; + } + + $scope.instance.config['rdnLDAPAttribute'][0] = $scope.instance.config['usernameLDAPAttribute'][0]; + + var vendorToUUID = { + rhds: "nsuniqueid", + tivoli: "uniqueidentifier", + edirectory: "guid", + ad: "objectGUID", + other: "entryUUID" + }; + $scope.instance.config['uuidLDAPAttribute'][0] = vendorToUUID[$scope.lastVendor]; + } + + + }, true); + + $scope.$watch('fullSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['fullSyncPeriod'][0] = $scope.fullSyncEnabled ? "604800" : "-1"; + $scope.changed = true; + }); + + $scope.$watch('changedSyncEnabled', function(newVal, oldVal) { + if (oldVal == newVal) { + return; + } + + $scope.instance.config['changedSyncPeriod'][0] = $scope.changedSyncEnabled ? "86400" : "-1"; + $scope.changed = true; + }); + + + $scope.save = function() { + $scope.changed = false; + if (!$scope.instance.config['batchSizeForSync'] || !parseInt($scope.instance.config['batchSizeForSync'][0])) { + $scope.instance.config['batchSizeForSync'] = [ DEFAULT_BATCH_SIZE ]; + } else { + $scope.instance.config['batchSizeForSync'][0] = parseInt($scope.instance.config.batchSizeForSync).toString(); + } + + if ($scope.create) { + Components.save({realm: realm.realm}, $scope.instance, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id); + Notifications.success("The provider has been created."); + }); + } else { + Components.update({realm: realm.realm, + componentId: instance.id + }, + $scope.instance, function () { + $route.reload(); + Notifications.success("The provider has been updated."); + }); + } + }; + + $scope.reset = function() { + $route.reload(); + }; + + $scope.cancel = function() { + if ($scope.create) { + $location.url("/realms/" + realm.realm + "/user-federation"); + } else { + $route.reload(); + } + }; + + $scope.triggerFullSync = function() { + console.log('GenericCtrl: triggerFullSync'); + triggerSync('triggerFullSync'); + } + + $scope.triggerChangedUsersSync = function() { + console.log('GenericCtrl: triggerChangedUsersSync'); + triggerSync('triggerChangedUsersSync'); + } + + + function triggerSync(action) { + UserStorageOperations.sync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success("Sync of users finished successfully. " + syncResult.status); + }, function() { + $route.reload(); + Notifications.error("Error during sync of users"); + }); + } + $scope.removeImportedUsers = function() { + UserStorageOperations.removeImportedUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success("Remove imported users finished successfully. "); + }, function() { + $route.reload(); + Notifications.error("Error during remove"); + }); + }; + $scope.unlinkUsers = function() { + UserStorageOperations.unlinkUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) { + $route.reload(); + Notifications.success("Unlink of users finished successfully. "); + }, function() { + $route.reload(); + Notifications.error("Error during unlink"); + }); + }; + + var initConnectionTest = function(testAction, ldapConfig) { + return { + action: testAction, + connectionUrl: ldapConfig.connectionUrl && ldapConfig.connectionUrl[0], + authType: ldapConfig.authType && ldapConfig.authType[0], + bindDn: ldapConfig.bindDn && ldapConfig.bindDn[0], + bindCredential: ldapConfig.bindCredential && ldapConfig.bindCredential[0], + useTruststoreSpi: ldapConfig.useTruststoreSpi && ldapConfig.useTruststoreSpi[0], + connectionTimeout: ldapConfig.connectionTimeout && ldapConfig.connectionTimeout[0], + startTls: ldapConfig.startTls && ldapConfig.startTls[0], + componentId: instance.id + }; + }; + + $scope.testConnection = function() { + console.log('LDAPCtrl: testConnection'); + RealmLDAPConnectionTester.save({realm: realm.realm}, initConnectionTest("testConnection", $scope.instance.config), function() { + Notifications.success("LDAP connection successful."); + }, function() { + Notifications.error("Error when trying to connect to LDAP. See server.log for details."); + }); + } + + $scope.testAuthentication = function() { + console.log('LDAPCtrl: testAuthentication'); + RealmLDAPConnectionTester.save({realm: realm.realm}, initConnectionTest("testAuthentication", $scope.instance.config), function() { + Notifications.success("LDAP authentication successful."); + }, function() { + Notifications.error("LDAP authentication failed. See server.log for details"); + }); + } + + $scope.queryAndSetLdapSupportedExtensions = function() { + console.log('LDAPCtrl: getLdapSupportedExtensions'); + const PASSWORD_MODIFY_OID = '1.3.6.1.4.1.4203.1.11.1'; + + $http.post( + authUrl + '/admin/realms/' + realm.realm + '/ldap-server-capabilities', + initConnectionTest("queryServerCapabilities", $scope.instance.config)).then( + function(response) { + Notifications.success("LDAP supported extensions successfully requested."); + const ldapOids = response.data; + if (angular.isArray(ldapOids)) { + const passwordModifyOid = ldapOids.filter(function(ldapOid) { return ldapOid.oid === PASSWORD_MODIFY_OID; }); + $scope.instance.config['usePasswordModifyExtendedOp'][0] = (passwordModifyOid.length > 0).toString(); + } + }, + function() { + Notifications.error("Error when trying to request supported extensions of LDAP. See server.log for details."); + }); + } + +}); + +module.controller('LDAPTabCtrl', function(Dialog, $scope, Current, Notifications, $location) { + $scope.removeUserFederation = function() { + Dialog.confirmDelete($scope.instance.name, 'ldap provider', function() { + $scope.instance.$remove({ + realm : Current.realm.realm, + componentId : $scope.instance.id + }, function() { + $location.url("/realms/" + Current.realm.realm + "/user-federation"); + Notifications.success("The provider has been deleted."); + }); + }); + }; +}); + + +module.controller('LDAPMapperListCtrl', function($scope, $location, Notifications, $route, Dialog, realm, provider, mappers) { + console.log('LDAPMapperListCtrl'); + + $scope.realm = realm; + $scope.provider = provider; + $scope.instance = provider; + + $scope.mappers = mappers; + +}); + +module.controller('LDAPMapperCtrl', function($scope, $route, realm, provider, mapperTypes, mapper, clients, Components, LDAPMapperSync, Notifications, Dialog, $location) { + console.log('LDAPMapperCtrl'); + $scope.realm = realm; + $scope.provider = provider; + $scope.clients = clients; + $scope.create = false; + $scope.changed = false; + + for (var i = 0; i < mapperTypes.length; i++) { + console.log('mapper.providerId: ' + mapper.providerId); + console.log('mapperTypes[i].id ' + mapperTypes[i].id); + if (mapperTypes[i].id == mapper.providerId) { + $scope.mapperType = mapperTypes[i]; + break; + } + } + + if ($scope.mapperType.properties) { + + for (var i = 0; i < $scope.mapperType.properties.length; i++) { + var configProperty = $scope.mapperType.properties[i]; + if (!mapper.config[configProperty.name]) { + if (configProperty.defaultValue) { + mapper.config[configProperty.name] = [configProperty.defaultValue]; + } else { + mapper.config[configProperty.name] = ['']; + } + } + + } + } + $scope.mapper = angular.copy(mapper); + + + $scope.$watch('mapper', function() { + if (!angular.equals($scope.mapper, mapper)) { + $scope.changed = true; + } + }, true); + + $scope.save = function() { + Components.update({realm: realm.realm, + componentId: mapper.id + }, + $scope.mapper, function () { + $route.reload(); + Notifications.success("The mapper has been updated."); + }); + }; + + $scope.reset = function() { + $scope.mapper = angular.copy(mapper); + $scope.changed = false; + }; + + $scope.remove = function() { + Dialog.confirmDelete($scope.mapper.name, 'ldap mapper', function() { + Components.remove({ + realm : realm.realm, + componentId : mapper.id + }, function() { + $location.url("/realms/" + realm.realm + '/ldap-mappers/' + provider.id); + Notifications.success("The provider has been deleted."); + }); + }); + }; + + $scope.triggerFedToKeycloakSync = function() { + triggerMapperSync("fedToKeycloak") + } + + $scope.triggerKeycloakToFedSync = function() { + triggerMapperSync("keycloakToFed"); + } + + function triggerMapperSync(direction) { + LDAPMapperSync.save({ direction: direction, realm: realm.realm, parentId: provider.id, mapperId : $scope.mapper.id }, {}, function(syncResult) { + Notifications.success("Data synced successfully. " + syncResult.status); + }, function(error) { + Notifications.error(error.data.errorMessage); + }); + } + +}); + +module.controller('LDAPMapperCreateCtrl', function($scope, realm, provider, mapperTypes, clients, Components, Notifications, Dialog, $location) { + console.log('LDAPMapperCreateCtrl'); + $scope.realm = realm; + $scope.provider = provider; + $scope.clients = clients; + $scope.create = true; + $scope.mapper = { config: {}}; + $scope.mapperTypes = mapperTypes; + $scope.mapperType = null; + $scope.changed = true; + + $scope.$watch('mapperType', function() { + if ($scope.mapperType != null) { + $scope.mapper.config = {}; + if ($scope.mapperType.properties) { + + for (var i = 0; i < $scope.mapperType.properties.length; i++) { + var configProperty = $scope.mapperType.properties[i]; + if (!$scope.mapper.config[configProperty.name]) { + if (configProperty.defaultValue) { + $scope.mapper.config[configProperty.name] = [configProperty.defaultValue]; + } else { + $scope.mapper.config[configProperty.name] = ['']; + } + } + + } + } + } + }, true); + + $scope.save = function() { + if ($scope.mapperType == null) { + Notifications.error("You need to select mapper type!"); + return; + } + + $scope.mapper.providerId = $scope.mapperType.id; + $scope.mapper.providerType = 'org.keycloak.storage.ldap.mappers.LDAPStorageMapper'; + $scope.mapper.parentId = provider.id; + + if ($scope.mapper.config && $scope.mapper.config["role"] && !Array.isArray($scope.mapper.config["role"])) { + $scope.mapper.config["role"] = [$scope.mapper.config["role"]]; + } + + Components.save({realm: realm.realm}, $scope.mapper, function (data, headers) { + var l = headers().location; + var id = l.substring(l.lastIndexOf("/") + 1); + + $location.url("/realms/" + realm.realm + "/ldap-mappers/" + $scope.mapper.parentId + "/mappers/" + id); + Notifications.success("The mapper has been created."); + }); + }; + + $scope.reset = function() { + $location.url("/realms/" + realm.realm + '/ldap-mappers/' + provider.id); + }; + + +}); diff --git a/base/admin/resources/js/loaders.js b/base/admin/resources/js/loaders.js new file mode 100755 index 0000000..bbeabd9 --- /dev/null +++ b/base/admin/resources/js/loaders.js @@ -0,0 +1,570 @@ +'use strict'; + +var module = angular.module('keycloak.loaders', [ 'keycloak.services', 'ngResource' ]); + +module.factory('Loader', function($q) { + var loader = {}; + loader.get = function(service, id) { + return function() { + var i = id && id(); + var delay = $q.defer(); + service.get(i, function(entry) { + delay.resolve(entry); + }, function() { + delay.reject('Unable to fetch ' + i); + }); + return delay.promise; + }; + }; + loader.query = function(service, id) { + return function() { + var i = id && id(); + var delay = $q.defer(); + service.query(i, function(entry) { + delay.resolve(entry); + }, function() { + delay.reject('Unable to fetch ' + i); + }); + return delay.promise; + }; + }; + return loader; +}); + +module.factory('RealmListLoader', function(Loader, Realm, $q) { + return Loader.get(Realm); +}); + +module.factory('ServerInfoLoader', function(Loader, ServerInfo) { + return function() { + return ServerInfo.promise; + }; +}); + +module.factory('RealmLoader', function(Loader, Realm, $route, $q) { + return Loader.get(Realm, function() { + return { + id : $route.current.params.realm + } + }); +}); + +module.factory('RealmKeysLoader', function(Loader, RealmKeys, $route, $q) { + return Loader.get(RealmKeys, function() { + return { + id : $route.current.params.realm + } + }); +}); + +module.factory('RealmSpecificLocalesLoader', function(Loader, RealmSpecificLocales, $route, $q) { + return Loader.get(RealmSpecificLocales, function() { + return { + id : $route.current.params.realm + } + }); +}); + +module.factory('RealmSpecificlocalizationTextLoader', function(Loader, RealmSpecificLocalizationText, $route, $q) { + return Loader.get(RealmSpecificLocalizationText, function() { + return { + realm : $route.current.params.realm, + locale : $route.current.params.locale, + key: $route.current.params.key + } + }); +}); + +module.factory('RealmEventsConfigLoader', function(Loader, RealmEventsConfig, $route, $q) { + return Loader.get(RealmEventsConfig, function() { + return { + id : $route.current.params.realm + } + }); +}); + +module.factory('UserListLoader', function(Loader, User, $route, $q) { + return Loader.query(User, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('RequiredActionsListLoader', function(Loader, RequiredActions, $route, $q) { + return Loader.query(RequiredActions, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('UnregisteredRequiredActionsListLoader', function(Loader, UnregisteredRequiredActions, $route, $q) { + return Loader.query(UnregisteredRequiredActions, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('RealmSessionStatsLoader', function(Loader, RealmSessionStats, $route, $q) { + return Loader.get(RealmSessionStats, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('RealmClientSessionStatsLoader', function(Loader, RealmClientSessionStats, $route, $q) { + return Loader.query(RealmClientSessionStats, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('ClientProtocolMapperLoader', function(Loader, ClientProtocolMapper, $route, $q) { + return Loader.get(ClientProtocolMapper, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client, + id: $route.current.params.id + } + }); +}); + +module.factory('ClientScopeProtocolMapperLoader', function(Loader, ClientScopeProtocolMapper, $route, $q) { + return Loader.get(ClientScopeProtocolMapper, function() { + return { + realm : $route.current.params.realm, + clientScope : $route.current.params.clientScope, + id: $route.current.params.id + } + }); +}); + +module.factory('UserLoader', function(Loader, User, $route, $q) { + return Loader.get(User, function() { + return { + realm : $route.current.params.realm, + userId : $route.current.params.user + } + }); +}); + +module.factory('ComponentLoader', function(Loader, Components, $route, $q) { + return Loader.get(Components, function() { + return { + realm : $route.current.params.realm, + componentId: $route.current.params.componentId + } + }); +}); + +module.factory('LDAPMapperLoader', function(Loader, Components, $route, $q) { + return Loader.get(Components, function() { + return { + realm : $route.current.params.realm, + componentId: $route.current.params.mapperId + } + }); +}); + +module.factory('ComponentsLoader', function(Loader, Components, $route, $q) { + var componentsLoader = {}; + + componentsLoader.loadComponents = function(parent, componentType) { + return Loader.query(Components, function() { + return { + realm : $route.current.params.realm, + parent : parent, + type: componentType + } + })(); + }; + + return componentsLoader; +}); + +module.factory('SubComponentTypesLoader', function(Loader, SubComponentTypes, $route, $q) { + var componentsLoader = {}; + + componentsLoader.loadComponents = function(parent, componentType) { + return Loader.query(SubComponentTypes, function() { + return { + realm : $route.current.params.realm, + componentId : parent, + type: componentType + } + })(); + }; + + return componentsLoader; +}); + +module.factory('UserSessionStatsLoader', function(Loader, UserSessionStats, $route, $q) { + return Loader.get(UserSessionStats, function() { + return { + realm : $route.current.params.realm, + user : $route.current.params.user + } + }); +}); + +module.factory('UserSessionsLoader', function(Loader, UserSessions, $route, $q) { + return Loader.query(UserSessions, function() { + return { + realm : $route.current.params.realm, + user : $route.current.params.user + } + }); +}); + +module.factory('UserOfflineSessionsLoader', function(Loader, UserOfflineSessions, $route, $q) { + return Loader.query(UserOfflineSessions, function() { + return { + realm : $route.current.params.realm, + user : $route.current.params.user, + client : $route.current.params.client + } + }); +}); + +module.factory('UserFederatedIdentityLoader', function(Loader, UserFederatedIdentities, $route, $q) { + return Loader.query(UserFederatedIdentities, function() { + return { + realm : $route.current.params.realm, + user : $route.current.params.user + } + }); +}); + +module.factory('UserConsentsLoader', function(Loader, UserConsents, $route, $q) { + return Loader.query(UserConsents, function() { + return { + realm : $route.current.params.realm, + user : $route.current.params.user + } + }); +}); + + + +module.factory('RoleLoader', function(Loader, RoleById, $route, $q) { + return Loader.get(RoleById, function() { + return { + realm : $route.current.params.realm, + role : $route.current.params.role + } + }); +}); + +module.factory('RoleListLoader', function(Loader, Role, $route, $q) { + return Loader.query(Role, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('ClientRoleLoader', function(Loader, RoleById, $route, $q) { + return Loader.get(RoleById, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client, + role : $route.current.params.role + } + }); +}); + +module.factory('ClientSessionStatsLoader', function(Loader, ClientSessionStats, $route, $q) { + return Loader.get(ClientSessionStats, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + +module.factory('ClientSessionCountLoader', function(Loader, ClientSessionCount, $route, $q) { + return Loader.get(ClientSessionCount, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + +module.factory('ClientOfflineSessionCountLoader', function(Loader, ClientOfflineSessionCount, $route, $q) { + return Loader.get(ClientOfflineSessionCount, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + +module.factory('ClientDefaultClientScopesLoader', function(Loader, ClientDefaultClientScopes, $route, $q) { + return Loader.query(ClientDefaultClientScopes, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + +module.factory('ClientOptionalClientScopesLoader', function(Loader, ClientOptionalClientScopes, $route, $q) { + return Loader.query(ClientOptionalClientScopes, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + +module.factory('ClientLoader', function(Loader, Client, $route, $q) { + return Loader.get(Client, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + +module.factory('ClientListLoader', function(Loader, Client, $route, $q) { + return Loader.query(Client, function() { + return { + realm : $route.current.params.realm, + first: 0, + max: 20 + } + }); +}); + +module.factory('ClientScopeLoader', function(Loader, ClientScope, $route, $q) { + return Loader.get(ClientScope, function() { + return { + realm : $route.current.params.realm, + clientScope : $route.current.params.clientScope + } + }); +}); + +module.factory('ClientScopeListLoader', function(Loader, ClientScope, $route, $q) { + return Loader.query(ClientScope, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('RealmDefaultClientScopesLoader', function(Loader, RealmDefaultClientScopes, $route, $q) { + return Loader.query(RealmDefaultClientScopes, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('RealmOptionalClientScopesLoader', function(Loader, RealmOptionalClientScopes, $route, $q) { + return Loader.query(RealmOptionalClientScopes, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('ClientServiceAccountUserLoader', function(Loader, ClientServiceAccountUser, $route, $q) { + return Loader.get(ClientServiceAccountUser, function() { + return { + realm : $route.current.params.realm, + client : $route.current.params.client + } + }); +}); + + +module.factory('RoleMappingLoader', function(Loader, RoleMapping, $route, $q) { + var realm = $route.current.params.realm || $route.current.params.client; + + return Loader.query(RoleMapping, function() { + return { + realm : realm, + role : $route.current.params.role + } + }); +}); + +module.factory('IdentityProviderLoader', function(Loader, IdentityProvider, $route, $q) { + return Loader.get(IdentityProvider, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias + } + }); +}); + +module.factory('IdentityProviderFactoryLoader', function(Loader, IdentityProviderFactory, $route, $q) { + return Loader.get(IdentityProviderFactory, function () { + return { + realm: $route.current.params.realm, + provider_id: $route.current.params.provider_id + } + }); +}); + +module.factory('IdentityProviderMapperTypesLoader', function(Loader, IdentityProviderMapperTypes, $route, $q) { + return Loader.get(IdentityProviderMapperTypes, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias + } + }); +}); + +module.factory('IdentityProviderMappersLoader', function(Loader, IdentityProviderMappers, $route, $q) { + return Loader.query(IdentityProviderMappers, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias + } + }); +}); + +module.factory('IdentityProviderMapperLoader', function(Loader, IdentityProviderMapper, $route, $q) { + return Loader.get(IdentityProviderMapper, function () { + return { + realm: $route.current.params.realm, + alias: $route.current.params.alias, + mapperId: $route.current.params.mapperId + } + }); +}); + +module.factory('AuthenticationFlowsLoader', function(Loader, AuthenticationFlows, $route, $q) { + return Loader.query(AuthenticationFlows, function() { + return { + realm : $route.current.params.realm, + flow: '' + } + }); +}); + +module.factory('AuthenticationFormProvidersLoader', function(Loader, AuthenticationFormProviders, $route, $q) { + return Loader.query(AuthenticationFormProviders, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('AuthenticationFormActionProvidersLoader', function(Loader, AuthenticationFormActionProviders, $route, $q) { + return Loader.query(AuthenticationFormActionProviders, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('AuthenticatorProvidersLoader', function(Loader, AuthenticatorProviders, $route, $q) { + return Loader.query(AuthenticatorProviders, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('ClientAuthenticatorProvidersLoader', function(Loader, ClientAuthenticatorProviders, $route, $q) { + return Loader.query(ClientAuthenticatorProviders, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('AuthenticationFlowLoader', function(Loader, AuthenticationFlows, $route, $q) { + return Loader.get(AuthenticationFlows, function() { + return { + realm : $route.current.params.realm, + flow: $route.current.params.flow + } + }); +}); + +module.factory('AuthenticationConfigDescriptionLoader', function(Loader, AuthenticationConfigDescription, $route, $q) { + return Loader.get(AuthenticationConfigDescription, function () { + return { + realm: $route.current.params.realm, + provider: $route.current.params.provider + } + }); +}); + +module.factory('PerClientAuthenticationConfigDescriptionLoader', function(Loader, PerClientAuthenticationConfigDescription, $route, $q) { + return Loader.get(PerClientAuthenticationConfigDescription, function () { + return { + realm: $route.current.params.realm + } + }); +}); + +module.factory('ExecutionIdLoader', function($route) { + return function() { return $route.current.params.executionId; }; +}); + +module.factory('AuthenticationConfigLoader', function(Loader, AuthenticationConfig, $route, $q) { + return Loader.get(AuthenticationConfig, function () { + return { + realm: $route.current.params.realm, + config: $route.current.params.config + } + }); +}); + +module.factory('GroupListLoader', function(Loader, Groups, $route, $q) { + return Loader.query(Groups, function() { + return { + realm : $route.current.params.realm + } + }); +}); + +module.factory('GroupCountLoader', function(Loader, GroupsCount, $route, $q) { + return Loader.query(GroupsCount, function() { + return { + realm : $route.current.params.realm, + top : true + } + }); +}); + +module.factory('GroupLoader', function(Loader, Group, $route, $q) { + return Loader.get(Group, function() { + return { + realm : $route.current.params.realm, + groupId : $route.current.params.group + } + }); +}); + +module.factory('ClientInitialAccessLoader', function(Loader, ClientInitialAccess, $route) { + return Loader.query(ClientInitialAccess, function() { + return { + realm: $route.current.params.realm + } + }); +}); + +module.factory('ClientRegistrationPolicyProvidersLoader', function(Loader, ClientRegistrationPolicyProviders, $route) { + return Loader.query(ClientRegistrationPolicyProviders, function() { + return { + realm: $route.current.params.realm + } + }); +}); + + + + + + diff --git a/base/admin/resources/js/services.js b/base/admin/resources/js/services.js new file mode 100755 index 0000000..87a5b50 --- /dev/null +++ b/base/admin/resources/js/services.js @@ -0,0 +1,2225 @@ +'use strict'; + +var module = angular.module('keycloak.services', [ 'ngResource', 'ngRoute' ]); + +module.service('Dialog', function($modal, $translate) { + var dialog = {}; + + var openDialog = function(title, message, btns, template) { + var controller = function($scope, $modalInstance, title, message, btns) { + $scope.title = title; + $scope.message = message; + $scope.btns = btns; + + $scope.ok = function () { + $modalInstance.close(); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }; + + return $modal.open({ + templateUrl: resourceUrl + template, + controller: controller, + resolve: { + title: function() { + return title; + }, + message: function() { + return message; + }, + btns: function() { + return btns; + } + } + }).result; + } + + var escapeHtml = function(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + }; + + dialog.confirmDelete = function(name, type, success) { + var title = $translate.instant('dialogs.delete.title', {type: escapeHtml(type.charAt(0).toUpperCase() + type.slice(1))}); + var msg = $translate.instant('dialogs.delete.message', {type: type, name: name}); + var confirm = $translate.instant('dialogs.delete.confirm'); + + dialog.confirmWithButtonText(title, msg, confirm, success); + } + + dialog.confirmGenerateKeys = function(name, type, success) { + var title = 'Generate new keys for realm'; + var msg = 'Are you sure you want to permanently generate new keys for ' + name + '?'; + var btns = { + ok: { + label: 'Generate Keys', + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + } + + openDialog(title, msg, btns, '/templates/kc-modal.html').then(success); + } + + dialog.confirm = function(title, message, success, cancel) { + dialog.confirmWithButtonText(title, message, title, success, cancel); + } + + dialog.confirmWithButtonText = function(title, message, confirm, success, cancel) { + var btns = { + ok: { + label: confirm, + cssClass: 'btn btn-danger' + }, + cancel: { + label: $translate.instant('dialogs.cancel'), + cssClass: 'btn btn-default' + } + } + + openDialog(title, message, btns, '/templates/kc-modal.html').then(success, cancel); + } + + dialog.message = function(title, message, success, cancel) { + var btns = { + ok: { + label: $translate.instant('dialogs.ok'), + cssClass: 'btn btn-default' + } + } + + openDialog(title, message, btns, '/templates/kc-modal-message.html').then(success, cancel); + } + + dialog.open = function(title, message, btns, success, cancel) { + openDialog(title, message, btns, '/templates/kc-modal.html').then(success, cancel); + } + + return dialog +}); + +module.service('CopyDialog', function($modal) { + var dialog = {}; + dialog.open = function (title, suggested, success) { + var controller = function($scope, $modalInstance, title) { + $scope.title = title; + $scope.name = { value: 'Copy of ' + suggested }; + $scope.ok = function () { + console.log('ok with name: ' + $scope.name); + $modalInstance.close(); + success($scope.name.value); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + } + $modal.open({ + templateUrl: resourceUrl + '/templates/kc-copy.html', + controller: controller, + resolve: { + title: function() { + return title; + } + } + }); + }; + return dialog; +}); + +module.service('UpdateDialog', function($modal) { + var dialog = {}; + dialog.open = function (title, name, desc, success) { + var controller = function($scope, $modalInstance, title) { + $scope.title = title; + $scope.name = { value: name }; + $scope.description = { value: desc }; + $scope.ok = function () { + console.log('ok with name: ' + $scope.name + 'and description: ' + $scope.description); + $modalInstance.close(); + success($scope.name.value, $scope.description.value); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + } + $modal.open({ + templateUrl: resourceUrl + '/templates/kc-edit.html', + controller: controller, + resolve: { + title: function() { + return title; + } + } + }); + }; + return dialog; +}); + +module.factory('Notifications', function($rootScope, $timeout, $translate) { + // time (in ms) the notifications are shown + var delay = 5000; + + var notifications = {}; + notifications.current = { display: false }; + notifications.current.remove = function() { + if (notifications.scheduled) { + $timeout.cancel(notifications.scheduled); + delete notifications.scheduled; + } + delete notifications.current.type; + delete notifications.current.header; + delete notifications.current.message; + notifications.current.display = false; + console.debug("Remove message"); + } + + $rootScope.notification = notifications.current; + + notifications.message = function(type, header, message) { + notifications.current.remove(); + + notifications.current.type = type; + notifications.current.header = header; + notifications.current.message = message; + notifications.current.display = true; + + notifications.scheduled = $timeout(function() { + notifications.current.remove(); + }, delay); + + console.debug("Added message"); + } + + notifications.info = function(message) { + notifications.message("info", $translate.instant('notifications.info.header'), message); + }; + + notifications.success = function(message) { + notifications.message("success", $translate.instant('notifications.success.header'), message); + }; + + notifications.error = function(message) { + notifications.message("danger", $translate.instant('notifications.error.header'), message); + }; + + notifications.warn = function(message) { + notifications.message("warning", $translate.instant('notifications.warn.header'), message); + }; + + return notifications; +}); + + +module.factory('ComponentUtils', function() { + + function sortGroups(prop, arr) { + // sort current elements + arr.sort(function (a, b) { + if (a[prop] < b[prop]) { return -1; } + if (a[prop] > b[prop]) { return 1; } + return 0; + }); + // check sub groups + arr.forEach(function (item, index) { + if (!!item.subGroups) { + sortGroups(prop, item.subGroups); + } + }); + return arr; + }; + + var utils = {}; + + utils.sortGroups = sortGroups; + + utils.findIndexById = function(array, id) { + for (var i = 0; i < array.length; i++) { + if (array[i].id === id) return i; + } + return -1; + } + + utils.convertAllMultivaluedStringValuesToList = function(properties, config) { + if (!properties) { + return; + } + + for (var i=0 ; i 0) { + var lastVal = configVal[configVal.length - 1]; + if (lastVal === '') { + console.log('Remove empty value from config property: ' + prop.name); + configVal.splice(configVal.length - 1, 1); + } + } + + var attrVals = configVal.join("##"); + config[prop.name] = attrVals; + + } + } + } + } + + + + utils.addLastEmptyValueToMultivaluedLists = function(properties, config) { + if (!properties) { + return; + } + + for (var i=0 ; i 0) { + configProperty.push(''); + } + } + } + } + + + utils.removeLastEmptyValue = function(componentConfig) { + + for (var configPropertyName in componentConfig) { + var configVal = componentConfig[configPropertyName]; + if (configVal && configVal.length > 0) { + var lastVal = configVal[configVal.length - 1]; + if (lastVal === '') { + console.log('Remove empty value from config property: ' + configPropertyName); + configVal.splice(configVal.length - 1, 1); + } + } + } + } + + // Allows you to use ui-select2 with tag. + // In HTML you will then use property.mvOptions like this: + // Flows tab +module.service('LastFlowSelected', function() { + this.alias = null; +}); + +module.service('RealmRoleRemover', function() { + this.remove = function (role, realm, Dialog, $location, Notifications) { + Dialog.confirmDelete(role.name, 'role', function () { + role.$remove({ + realm: realm.realm, + role: role.id + }, function () { + $location.url("/realms/" + realm.realm + "/roles"); + Notifications.success("The role has been deleted."); + }); + }); + }; +}); + +module.factory('UserSessionStats', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/session-stats', { + realm : '@realm', + user : '@user' + }); +}); +module.factory('UserSessions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/sessions', { + realm : '@realm', + user : '@user' + }); +}); +module.factory('UserOfflineSessions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/offline-sessions/:client', { + realm : '@realm', + user : '@user', + client : '@client' + }); +}); + +module.factory('UserSessionLogout', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/sessions/:session', { + realm : '@realm', + session : '@session' + }); +}); + +module.factory('UserLogout', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/logout', { + realm : '@realm', + user : '@user' + }); +}); + +module.factory('UserFederatedIdentities', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/federated-identity', { + realm : '@realm', + user : '@user' + }); +}); +module.factory('UserFederatedIdentity', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/federated-identity/:provider', { + realm : '@realm', + user : '@user', + provider : '@provider' + }); +}); + +module.factory('UserConsents', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/consents/:client', { + realm : '@realm', + user : '@user', + client: '@client' + }); +}); + +module.factory('UserImpersonation', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:user/impersonation', { + realm : '@realm', + user : '@user' + }); +}); + +module.factory('UserCredentials', function($resource) { + var credentials = {}; + + credentials.getCredentials = $resource(authUrl + '/admin/realms/:realm/users/:userId/credentials', { + realm : '@realm', + userId : '@userId' + }).query; + + credentials.getConfiguredUserStorageCredentialTypes = $resource(authUrl + '/admin/realms/:realm/users/:userId/configured-user-storage-credential-types', { + realm : '@realm', + userId : '@userId' + }).query; + + credentials.deleteCredential = $resource(authUrl + '/admin/realms/:realm/users/:userId/credentials/:credentialId', { + realm : '@realm', + userId : '@userId', + credentialId : '@credentialId' + }).delete; + + credentials.updateCredentialLabel = $resource(authUrl + '/admin/realms/:realm/users/:userId/credentials/:credentialId/userLabel', { + realm : '@realm', + userId : '@userId', + credentialId : '@credentialId' + }, { + update : { + method : 'PUT', + headers: { + 'Content-Type': 'text/plain;charset=utf-8' + }, + transformRequest: function(credential, getHeaders) { + return credential.userLabel; + } + } + }).update; + + credentials.resetPassword = $resource(authUrl + '/admin/realms/:realm/users/:userId/reset-password', { + realm : '@realm', + userId : '@userId' + }, { + update : { + method : 'PUT' + } + }).update; + + credentials.removeTotp = $resource(authUrl + '/admin/realms/:realm/users/:userId/remove-totp', { + realm : '@realm', + userId : '@userId' + }, { + update : { + method : 'PUT' + } + }).update; + + credentials.disableCredentialTypes = $resource(authUrl + '/admin/realms/:realm/users/:userId/disable-credential-types', { + realm : '@realm', + userId : '@userId' + }, { + update : { + method : 'PUT' + } + }).update; + + credentials.moveCredentialAfter = $resource(authUrl + '/admin/realms/:realm/users/:userId/credentials/:credentialId/moveAfter/:newPreviousCredentialId', { + realm : '@realm', + userId : '@userId', + credentialId : '@credentialId', + newPreviousCredentialId : '@newPreviousCredentialId' + }, { + update : { + method : 'POST' + } + }).update; + + credentials.moveToFirst = $resource(authUrl + '/admin/realms/:realm/users/:userId/credentials/:credentialId/moveToFirst', { + realm : '@realm', + userId : '@userId', + credentialId : '@credentialId' + }, { + update : { + method : 'POST' + } + }).update; + + return credentials; +}); + +module.factory('UserExecuteActionsEmail', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/execute-actions-email', { + realm : '@realm', + userId : '@userId', + lifespan : '@lifespan', + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('RealmRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/role-mappings/realm', { + realm : '@realm', + userId : '@userId' + }); +}); + +module.factory('CompositeRealmRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/role-mappings/realm/composite', { + realm : '@realm', + userId : '@userId' + }); +}); + +module.factory('AvailableRealmRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/role-mappings/realm/available', { + realm : '@realm', + userId : '@userId' + }); +}); + + +module.factory('ClientRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/role-mappings/clients/:client', { + realm : '@realm', + userId : '@userId', + client : "@client" + }); +}); + +module.factory('AvailableClientRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/role-mappings/clients/:client/available', { + realm : '@realm', + userId : '@userId', + client : "@client" + }); +}); + +module.factory('CompositeClientRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/role-mappings/clients/:client/composite', { + realm : '@realm', + userId : '@userId', + client : "@client" + }); +}); + +module.factory('ClientRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/scope-mappings/realm', { + realm : '@realm', + client : '@client' + }); +}); + +module.factory('ClientAvailableRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/scope-mappings/realm/available', { + realm : '@realm', + client : '@client' + }); +}); + +module.factory('ClientCompositeRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/scope-mappings/realm/composite', { + realm : '@realm', + client : '@client' + }); +}); + +module.factory('ClientClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/scope-mappings/clients/:targetClient', { + realm : '@realm', + client : '@client', + targetClient : '@targetClient' + }); +}); + +module.factory('ClientAvailableClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/scope-mappings/clients/:targetClient/available', { + realm : '@realm', + client : '@client', + targetClient : '@targetClient' + }); +}); + +module.factory('ClientCompositeClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/scope-mappings/clients/:targetClient/composite', { + realm : '@realm', + client : '@client', + targetClient : '@targetClient' + }); +}); + + + +module.factory('RealmRoles', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles', { + realm : '@realm' + }); +}); + +module.factory('RoleRealmComposites', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/composites/realm', { + realm : '@realm', + role : '@role' + }); +}); + +module.factory('RealmPushRevocation', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/push-revocation', { + realm : '@realm' + }); +}); + +module.factory('RealmClearUserCache', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clear-user-cache', { + realm : '@realm' + }); +}); + +module.factory('RealmClearRealmCache', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clear-realm-cache', { + realm : '@realm' + }); +}); + +module.factory('RealmClearKeysCache', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clear-keys-cache', { + realm : '@realm' + }); +}); + +module.factory('RealmSessionStats', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/session-stats', { + realm : '@realm' + }); +}); + +module.factory('RealmClientSessionStats', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-session-stats', { + realm : '@realm' + }); +}); + + +module.factory('RoleClientComposites', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role/composites/clients/:client', { + realm : '@realm', + role : '@role', + client : "@client" + }); +}); + +function clientSelectControl($scope, realm, Client) { + $scope.clientsUiSelect = { + minimumInputLength: 0, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + Client.query({realm: realm, search: true, clientId: query.term.trim(), max: 20}, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.clientId; + return object.clientId; + } + }; +} + +function roleControl($scope, $route, realm, role, roles, Client, + ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, + $http, $location, Notifications, Dialog, ComponentUtils) { + $scope.$watch(function () { + return $location.path(); + }, function () { + $scope.path = $location.path().substring(1).split("/"); + }); + + $scope.$watch('role', function () { + if (!angular.equals($scope.role, role)) { + $scope.changed = true; + } + }, true); + + $scope.update = function () { + RoleById.update({ + realm: realm.realm, + role: role.id + }, $scope.role, function () { + $scope.changed = false; + role = angular.copy($scope.role); + Notifications.success("Your changes have been saved to the role."); + }); + }; + + $scope.reset = function () { + $scope.role = angular.copy(role); + $scope.changed = false; + }; + + if (!role.id) return; + + $scope.compositeSwitch = role.composite; + $scope.compositeSwitchDisabled = role.composite; + $scope.realmRoles = angular.copy(roles); + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.clientRoles = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + + for (var j = 0; j < $scope.realmRoles.length; j++) { + if ($scope.realmRoles[j].id == role.id) { + var realmRole = $scope.realmRoles[j]; + var idx = $scope.realmRoles.indexOf(realmRole); + $scope.realmRoles.splice(idx, 1); + break; + } + } + + + clientSelectControl($scope, $route.current.params.realm, Client); + + $scope.selectedClient = null; + + + $scope.realmMappings = RoleRealmComposites.query({realm : realm.realm, role : role.id}, function(){ + for (var i = 0; i < $scope.realmMappings.length; i++) { + var role = $scope.realmMappings[i]; + for (var j = 0; j < $scope.realmRoles.length; j++) { + var realmRole = $scope.realmRoles[j]; + if (realmRole.id == role.id) { + var idx = $scope.realmRoles.indexOf(realmRole); + if (idx != -1) { + $scope.realmRoles.splice(idx, 1); + break; + } + } + } + } + }); + + $scope.addRealmRole = function() { + $scope.compositeSwitchDisabled=true; + $scope.selectedRealmRolesToAdd = JSON.parse('[' + $scope.selectedRealmRoles + ']'); + $http.post(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', + $scope.selectedRealmRolesToAdd).then(function() { + for (var i = 0; i < $scope.selectedRealmRolesToAdd.length; i++) { + var role = $scope.selectedRealmRolesToAdd[i]; + var idx = ComponentUtils.findIndexById($scope.realmRoles, role.id); + if (idx != -1) { + $scope.realmRoles.splice(idx, 1); + $scope.realmMappings.push(role); + } + } + $scope.selectedRealmRoles = []; + $scope.selectedRealmRolesToAdd = []; + Notifications.success("Role added to composite."); + }); + }; + + $scope.deleteRealmRole = function() { + $scope.compositeSwitchDisabled=true; + $scope.selectedRealmMappingsToRemove = JSON.parse('[' + $scope.selectedRealmMappings + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', + {data : $scope.selectedRealmMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function() { + for (var i = 0; i < $scope.selectedRealmMappingsToRemove.length; i++) { + var role = $scope.selectedRealmMappingsToRemove[i]; + var idx = ComponentUtils.findIndexById($scope.realmMappings, role.id); + if (idx != -1) { + $scope.realmMappings.splice(idx, 1); + $scope.realmRoles.push(role); + } + } + $scope.selectedRealmMappings = []; + $scope.selectedRealmMappingsToRemove = []; + Notifications.success("Role removed from composite."); + }); + }; + + $scope.addClientRole = function() { + $scope.compositeSwitchDisabled=true; + $scope.selectedClientRolesToAdd = JSON.parse('[' + $scope.selectedClientRoles + ']'); + $http.post(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', + $scope.selectedClientRolesToAdd).then(function() { + for (var i = 0; i < $scope.selectedClientRolesToAdd.length; i++) { + var role = $scope.selectedClientRolesToAdd[i]; + var idx = ComponentUtils.findIndexById($scope.clientRoles, role.id); + if (idx != -1) { + $scope.clientRoles.splice(idx, 1); + $scope.clientMappings.push(role); + } + } + $scope.selectedClientRoles = []; + $scope.selectedClientRolesToAdd = []; + Notifications.success("Client role added."); + }); + }; + + $scope.deleteClientRole = function() { + $scope.compositeSwitchDisabled=true; + $scope.selectedClientMappingsToRemove = JSON.parse('[' + $scope.selectedClientMappings + ']'); + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', + {data : $scope.selectedClientMappingsToRemove, headers : {"content-type" : "application/json"}}).then(function() { + for (var i = 0; i < $scope.selectedClientMappingsToRemove.length; i++) { + var role = $scope.selectedClientMappingsToRemove[i]; + var idx = ComponentUtils.findIndexById($scope.clientMappings, role.id); + if (idx != -1) { + $scope.clientMappings.splice(idx, 1); + $scope.clientRoles.push(role); + } + } + $scope.selectedClientMappings = []; + $scope.selectedClientMappingsToRemove = []; + Notifications.success("Client role removed."); + }); + }; + + + $scope.changeClient = function(client) { + console.log("selected client: ", client); + if (!client || !client.id) { + $scope.selectedClient = null; + return; + } + $scope.selectedClient = client; + $scope.clientRoles = ClientRole.query({realm : realm.realm, client : client.id}, function() { + $scope.clientMappings = RoleClientComposites.query({realm : realm.realm, role : role.id, client : client.id}, function(){ + for (var i = 0; i < $scope.clientMappings.length; i++) { + var role = $scope.clientMappings[i]; + for (var j = 0; j < $scope.clientRoles.length; j++) { + var realmRole = $scope.clientRoles[j]; + if (realmRole.id == role.id) { + var idx = $scope.clientRoles.indexOf(realmRole); + if (idx != -1) { + $scope.clientRoles.splice(idx, 1); + break; + } + } + } + } + }); + for (var j = 0; j < $scope.clientRoles.length; j++) { + if ($scope.clientRoles[j] == role.id) { + var appRole = $scope.clientRoles[j]; + var idx = $scope.clientRoles.indexof(appRole); + $scope.clientRoles.splice(idx, 1); + break; + } + } + } + ); + }; + + + + +} + + +module.factory('Role', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles/:role', { + realm : '@realm', + role : '@role' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('RoleById', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles-by-id/:role', { + realm : '@realm', + role : '@role' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('ClientRole', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/roles/:role', { + realm : '@realm', + client : "@client", + role : '@role' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('ClientDefaultClientScopes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/default-client-scopes/:clientScopeId', { + realm : '@realm', + client : "@client", + clientScopeId : '@clientScopeId' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('ClientOptionalClientScopes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/optional-client-scopes/:clientScopeId', { + realm : '@realm', + client : "@client", + clientScopeId : '@clientScopeId' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('ClientEvaluateProtocolMappers', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/evaluate-scopes/protocol-mappers?scope=:scopeParam', { + realm : '@realm', + client : "@client", + scopeParam : "@scopeParam" + }); +}); + +module.factory('ClientEvaluateGrantedRoles', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/evaluate-scopes/scope-mappings/:roleContainer/granted?scope=:scopeParam', { + realm : '@realm', + client : "@client", + roleContainer : "@roleContainer", + scopeParam : "@scopeParam" + }); +}); + +module.factory('ClientEvaluateNotGrantedRoles', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/evaluate-scopes/scope-mappings/:roleContainer/not-granted?scope=:scopeParam', { + realm : '@realm', + client : "@client", + roleContainer : "@roleContainer", + scopeParam : "@scopeParam" + }); +}); + +module.factory('ClientEvaluateGenerateExampleAccessToken', function($resource) { + return buildClientEvaluateGenerateExampleUrl('generate-example-access-token'); +}); + +module.factory('ClientEvaluateGenerateExampleIDToken', function($resource) { + return buildClientEvaluateGenerateExampleUrl('generate-example-id-token'); +}); + +module.factory('ClientEvaluateGenerateExampleUserInfo', function($resource) { + return buildClientEvaluateGenerateExampleUrl('generate-example-userinfo'); +}); + +function buildClientEvaluateGenerateExampleUrl(subPath) { + var urlTemplate = authUrl + '/admin/realms/:realm/clients/:client/evaluate-scopes/' + subPath + '?scope=:scopeParam&userId=:userId'; + return { + url: function (parameters) { + return urlTemplate + .replace(':realm', parameters.realm) + .replace(':client', parameters.client) + .replace(':scopeParam', parameters.scopeParam) + .replace(':userId', parameters.userId); + } + } +} + +module.factory('ClientProtocolMappersByProtocol', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/protocol-mappers/protocol/:protocol', { + realm : '@realm', + client : "@client", + protocol : "@protocol" + }); +}); + +module.factory('ClientScopeProtocolMappersByProtocol', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/protocol-mappers/protocol/:protocol', { + realm : '@realm', + clientScope : "@clientScope", + protocol : "@protocol" + }); +}); + +module.factory('ClientScopeRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/scope-mappings/realm', { + realm : '@realm', + clientScope : '@clientScope' + }); +}); + +module.factory('ClientScopeAvailableRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/scope-mappings/realm/available', { + realm : '@realm', + clientScope : '@clientScope' + }); +}); + +module.factory('ClientScopeCompositeRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/scope-mappings/realm/composite', { + realm : '@realm', + clientScope : '@clientScope' + }); +}); + +module.factory('ClientScopeClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/scope-mappings/clients/:targetClient', { + realm : '@realm', + clientScope : '@clientScope', + targetClient : '@targetClient' + }); +}); + +module.factory('ClientScopeAvailableClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/scope-mappings/clients/:targetClient/available', { + realm : '@realm', + clientScope : '@clientScope', + targetClient : '@targetClient' + }); +}); + +module.factory('ClientScopeCompositeClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope/scope-mappings/clients/:targetClient/composite', { + realm : '@realm', + clientScope : '@clientScope', + targetClient : '@targetClient' + }); +}); + + +module.factory('ClientSessionStats', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/session-stats', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientSessionStatsWithUsers', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/session-stats?users=true', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientSessionCount', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/session-count', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientUserSessions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/user-sessions', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientOfflineSessionCount', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/offline-session-count', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientOfflineSessions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/offline-sessions', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('RealmLogoutAll', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/logout-all', { + realm : '@realm' + }); +}); + +module.factory('ClientPushRevocation', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/push-revocation', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientClusterNode', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/nodes/:node', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientTestNodesAvailable', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/test-nodes-available', { + realm : '@realm', + client : "@client" + }); +}); + +module.factory('ClientCertificate', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/certificates/:attribute', { + realm : '@realm', + client : "@client", + attribute: "@attribute" + }); +}); + +module.factory('ClientCertificateGenerate', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/certificates/:attribute/generate', { + realm : '@realm', + client : "@client", + attribute: "@attribute" + }, + { + generate : { + method : 'POST' + } + }); +}); + +module.factory('ClientCertificateDownload', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/certificates/:attribute/download', { + realm : '@realm', + client : "@client", + attribute: "@attribute" + }, + { + download : { + method : 'POST', + responseType: 'arraybuffer' + } + }); +}); + +module.factory('Client', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client', { + realm : '@realm', + client : '@client' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('ClientScope', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope', { + realm : '@realm', + clientScope : '@clientScope' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('RealmDefaultClientScopes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/default-default-client-scopes/:clientScopeId', { + realm : '@realm', + clientScopeId : '@clientScopeId' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('RealmOptionalClientScopes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/default-optional-client-scopes/:clientScopeId', { + realm : '@realm', + clientScopeId : '@clientScopeId' + }, { + update : { + method : 'PUT' + } + }); +}); + + +module.factory('ClientDescriptionConverter', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-description-converter', { + realm : '@realm' + }); +}); + +/* +module.factory('ClientInstallation', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/installation/providers/:provider', { + realm : '@realm', + client : '@client', + provider : '@provider' + }); +}); +*/ + + + +module.factory('ClientInstallation', function($resource) { + var url = authUrl + '/admin/realms/:realm/clients/:client/installation/providers/:provider'; + return { + url : function(parameters) + { + return url.replace(':realm', parameters.realm).replace(':client', parameters.client).replace(':provider', parameters.provider); + } + } +}); + +module.factory('ClientInstallationJBoss', function($resource) { + var url = authUrl + '/admin/realms/:realm/clients/:client/installation/jboss'; + return { + url : function(parameters) + { + return url.replace(':realm', parameters.realm).replace(':client', parameters.client); + } + } +}); + +module.factory('ClientSecret', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/client-secret', { + realm : '@realm', + client : '@client' + }, { + update : { + method : 'POST' + } + }); +}); + +module.factory('ClientRegistrationAccessToken', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/registration-access-token', { + realm : '@realm', + client : '@client' + }, { + update : { + method : 'POST' + } + }); +}); + +module.factory('ClientOrigins', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/allowed-origins', { + realm : '@realm', + client : '@client' + }, { + update : { + method : 'PUT', + isArray : true + } + }); +}); + +module.factory('ClientServiceAccountUser', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/service-account-user', { + realm : '@realm', + client : '@client' + }); +}); + +module.factory('Current', function(Realm, $route, $rootScope) { + var current = { + realms: {}, + realm: null + }; + + $rootScope.$on('$routeChangeStart', function() { + current.realms = Realm.query(null, function(realms) { + var currentRealm = null; + if ($route.current.params.realm) { + for (var i = 0; i < realms.length; i++) { + if (realms[i].realm == $route.current.params.realm) { + currentRealm = realms[i]; + } + } + } + current.realm = currentRealm; + }); + }); + + return current; +}); + +module.factory('TimeUnit', function() { + var t = {}; + + t.autoUnit = function(time) { + if (!time) { + return 'Hours'; + } + + var unit = 'Seconds'; + if (time % 60 == 0) { + unit = 'Minutes'; + time = time / 60; + } + if (time % 60 == 0) { + unit = 'Hours'; + time = time / 60; + } + if (time % 24 == 0) { + unit = 'Days' + time = time / 24; + } + return unit; + } + + t.toSeconds = function(time, unit) { + switch (unit) { + case 'Seconds': return time; + case 'Minutes': return time * 60; + case 'Hours': return time * 3600; + case 'Days': return time * 86400; + default: throw 'invalid unit ' + unit; + } + } + + t.toUnit = function(time, unit) { + switch (unit) { + case 'Seconds': return time; + case 'Minutes': return Math.ceil(time / 60); + case 'Hours': return Math.ceil(time / 3600); + case 'Days': return Math.ceil(time / 86400); + default: throw 'invalid unit ' + unit; + } + } + + return t; +}); + +module.factory('TimeUnit2', function() { + var t = {}; + + t.asUnit = function(time) { + + var unit = 'Minutes'; + + if (time) { + if (time == -1) { + time = -1; + } else { + if (time < 60) { + time = 60; + } + + if (time % 60 == 0) { + unit = 'Minutes'; + time = time / 60; + } + if (time % 60 == 0) { + unit = 'Hours'; + time = time / 60; + } + if (time % 24 == 0) { + unit = 'Days' + time = time / 24; + } + } + } + + var v = { + unit: unit, + time: time, + toSeconds: function() { + switch (v.unit) { + case 'Minutes': + return v.time * 60; + case 'Hours': + return v.time * 3600; + case 'Days': + return v.time * 86400; + } + } + } + + return v; + } + + return t; +}); + +module.filter('removeSelectedPolicies', function() { + return function(policies, selectedPolicies) { + var result = []; + for(var i in policies) { + var policy = policies[i]; + var policyAvailable = true; + for(var j in selectedPolicies) { + if(policy.id === selectedPolicies[j].id && !policy.multipleSupported) { + policyAvailable = false; + } + } + if(policyAvailable) { + result.push(policy); + } + } + return result; + } +}); + +module.factory('IdentityProvider', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias', { + realm : '@realm', + alias : '@alias' + }, { + update: { + method : 'PUT' + } + }); +}); + +module.factory('IdentityProviderExport', function($resource) { + var url = authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/export'; + return { + url : function(parameters) + { + return url.replace(':realm', parameters.realm).replace(':alias', parameters.alias); + } + } +}); + +module.factory('IdentityProviderFactory', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/providers/:provider_id', { + realm : '@realm', + provider_id : '@provider_id' + }); +}); + +module.factory('IdentityProviderMapperTypes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mapper-types', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('IdentityProviderMappers', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mappers', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('IdentityProviderMapper', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/identity-provider/instances/:alias/mappers/:mapperId', { + realm : '@realm', + alias : '@alias', + mapperId: '@mapperId' + }, { + update: { + method : 'PUT' + } + }); +}); + +module.factory('AuthenticationFlowExecutions', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:alias/executions', { + realm : '@realm', + alias : '@alias' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('CreateExecutionFlow', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:alias/executions/flow', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('CreateExecution', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:alias/executions/execution', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('AuthenticationFlows', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:flow', { + realm : '@realm', + flow: '@flow' + }); +}); + +module.factory('AuthenticationFormProviders', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/form-providers', { + realm : '@realm' + }); +}); + +module.factory('AuthenticationFormActionProviders', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/form-action-providers', { + realm : '@realm' + }); +}); + +module.factory('AuthenticatorProviders', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/authenticator-providers', { + realm : '@realm' + }); +}); + +module.factory('ClientAuthenticatorProviders', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/client-authenticator-providers', { + realm : '@realm' + }); +}); + + +module.factory('AuthenticationFlowsCopy', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:alias/copy', { + realm : '@realm', + alias : '@alias' + }); +}); + +module.factory('AuthenticationFlowsUpdate', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:flow', { + realm : '@realm', + flow : '@flow' + }, { + update : { + method : 'PUT' + } + }); +}); + + +module.factory('AuthenticationConfigDescription', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/config-description/:provider', { + realm : '@realm', + provider: '@provider' + }); +}); +module.factory('PerClientAuthenticationConfigDescription', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/per-client-config-description', { + realm : '@realm' + }); +}); + +module.factory('AuthenticationConfig', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/config/:config', { + realm : '@realm', + config: '@config' + }, { + update: { + method : 'PUT' + } + }); +}); +module.factory('AuthenticationExecutionConfig', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/executions/:execution/config', { + realm : '@realm', + execution: '@execution' + }); +}); + +module.factory('AuthenticationExecution', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/executions/:execution', { + realm : '@realm', + execution : '@execution' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('AuthenticationExecutionRaisePriority', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/executions/:execution/raise-priority', { + realm : '@realm', + execution : '@execution' + }); +}); + +module.factory('AuthenticationExecutionLowerPriority', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/authentication/executions/:execution/lower-priority', { + realm : '@realm', + execution : '@execution' + }); +}); + + + +module.service('SelectRoleDialog', function($modal) { + var dialog = {}; + + var openDialog = function(title, message, btns) { + var controller = function($scope, $modalInstance, title, message, btns) { + $scope.title = title; + $scope.message = message; + $scope.btns = btns; + + $scope.ok = function () { + $modalInstance.close(); + }; + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; + }; + + return $modal.open({ + templateUrl: resourceUrl + '/templates/kc-modal.html', + controller: controller, + resolve: { + title: function() { + return title; + }, + message: function() { + return message; + }, + btns: function() { + return btns; + } + } + }).result; + } + + var escapeHtml = function(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + }; + + dialog.confirmDelete = function(name, type, success) { + var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); + var msg = 'Are you sure you want to permanently delete the ' + type + ' ' + name + '?'; + var btns = { + ok: { + label: 'Delete', + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + } + + openDialog(title, msg, btns).then(success); + } + + dialog.confirmGenerateKeys = function(name, type, success) { + var title = 'Generate new keys for realm'; + var msg = 'Are you sure you want to permanently generate new keys for ' + name + '?'; + var btns = { + ok: { + label: 'Generate Keys', + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + } + + openDialog(title, msg, btns).then(success); + } + + dialog.confirm = function(title, message, success, cancel) { + var btns = { + ok: { + label: title, + cssClass: 'btn btn-danger' + }, + cancel: { + label: 'Cancel', + cssClass: 'btn btn-default' + } + } + + openDialog(title, message, btns).then(success, cancel); + } + + return dialog +}); + +module.factory('Group', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId', { + realm : '@realm', + userId : '@groupId' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('GroupChildren', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/children', { + realm : '@realm', + groupId : '@groupId' + }); +}); + +module.factory('GroupsCount', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/count', { + realm : '@realm' + }, + { + query: { + isArray: false, + method: 'GET', + params: {}, + transformResponse: function (data) { + return angular.fromJson(data) + } + } + }); +}); + +module.factory('Groups', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups', { + realm : '@realm' + }) +}); + +module.factory('GroupRealmRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm', { + realm : '@realm', + groupId : '@groupId' + }); +}); + +module.factory('GroupCompositeRealmRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm/composite', { + realm : '@realm', + groupId : '@groupId' + }); +}); + +module.factory('GroupAvailableRealmRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/realm/available', { + realm : '@realm', + groupId : '@groupId' + }); +}); + + +module.factory('GroupClientRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client', { + realm : '@realm', + groupId : '@groupId', + client : "@client" + }); +}); + +module.factory('GroupAvailableClientRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client/available', { + realm : '@realm', + groupId : '@groupId', + client : "@client" + }); +}); + +module.factory('GroupCompositeClientRoleMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/role-mappings/clients/:client/composite', { + realm : '@realm', + groupId : '@groupId', + client : "@client" + }); +}); + +module.factory('GroupMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/members', { + realm : '@realm', + groupId : '@groupId' + }); +}); + +module.factory('RoleList', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles', { + realm : '@realm' + }); +}); + +module.factory('RoleMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/roles/:role/users', { + realm : '@realm', + role : '@role' + }); +}); + +module.factory('ClientRoleList', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/roles', { + realm : '@realm', + client : '@client' + }); +}); + +module.factory('ClientRoleMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/clients/:client/roles/:role/users', { + realm : '@realm', + client : '@client', + role : '@role' + }); +}); + +module.factory('UserGroupMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', { + realm : '@realm', + userId : '@userId' + }); +}); + +module.factory('UserGroupMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups/:groupId', { + realm : '@realm', + userId : '@userId', + groupId : '@groupId' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('DefaultGroups', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/default-groups/:groupId', { + realm : '@realm', + groupId : '@groupId' + }, { + update : { + method : 'PUT' + } + }); +}); + +module.factory('SubComponentTypes', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/components/:componentId/sub-component-types', { + realm: '@realm', + componentId: '@componentId' + }); +}); + +module.factory('Components', function($resource, ComponentUtils) { + return $resource(authUrl + '/admin/realms/:realm/components/:componentId', { + realm : '@realm', + componentId : '@componentId' + }, { + update : { + method : 'PUT', + transformRequest: function(componentInstance) { + + if (componentInstance.config) { + ComponentUtils.removeLastEmptyValue(componentInstance.config); + } + + return angular.toJson(componentInstance); + } + }, + save : { + method : 'POST', + transformRequest: function(componentInstance) { + + if (componentInstance.config) { + ComponentUtils.removeLastEmptyValue(componentInstance.config); + } + + return angular.toJson(componentInstance); + } + } + }); +}); + +module.factory('UserStorageOperations', function($resource) { + var object = {} + object.sync = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/sync', { + realm : '@realm', + componentId : '@componentId' + }); + object.removeImportedUsers = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/remove-imported-users', { + realm : '@realm', + componentId : '@componentId' + }); + object.unlinkUsers = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/unlink-users', { + realm : '@realm', + componentId : '@componentId' + }); + object.simpleName = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/name', { + realm : '@realm', + componentId : '@componentId' + }); + return object; +}); + + +module.factory('ClientStorageOperations', function($resource) { + var object = {} + object.simpleName = $resource(authUrl + '/admin/realms/:realm/client-storage/:componentId/name', { + realm : '@realm', + componentId : '@componentId' + }); + return object; +}); + + +module.factory('ClientRegistrationPolicyProviders', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-registration-policy/providers', { + realm : '@realm', + }); +}); + +module.factory('LDAPMapperSync', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/user-storage/:parentId/mappers/:mapperId/sync', { + realm : '@realm', + componentId : '@componentId', + mapperId: '@mapperId' + }); +}); + + +module.factory('UserGroupMembershipCount', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups/count', { + realm : '@realm', + userId : '@userId' + }, + { + query: { + isArray: false, + method: 'GET', + params: {}, + transformResponse: function (data) { + return angular.fromJson(data) + } + } + }); +}); diff --git a/base/admin/resources/partials/authentication-flow-bindings.html b/base/admin/resources/partials/authentication-flow-bindings.html new file mode 100755 index 0000000..6bf39f3 --- /dev/null +++ b/base/admin/resources/partials/authentication-flow-bindings.html @@ -0,0 +1,83 @@ +
+

{{:: 'authentication' | translate}}

+ + + +
+
+ +
+
+ +
+
+ {{:: 'browser-flow.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'registration-flow.tooltip' | translate}} +
+
+ +
+
+ +
+
+ {{:: 'direct-grant-flow.tooltip' | translate}} +
+ +
+ +
+
+ +
+
+ {{:: 'reset-credentials.tooltip' | translate}} +
+ +
+ +
+
+ +
+
+ {{:: 'client-authentication.tooltip' | translate}} +
+ + +
+ +
+
+ +
+
+ {{:: 'docker-auth.tooltip' | translate}} +
+ +
+
+ + +
+
+
+ +
+ + + \ No newline at end of file diff --git a/base/admin/resources/partials/authentication-flows.html b/base/admin/resources/partials/authentication-flows.html new file mode 100755 index 0000000..eb8e721 --- /dev/null +++ b/base/admin/resources/partials/authentication-flows.html @@ -0,0 +1,72 @@ +
+

{{:: 'authentication' | translate}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +    +
+ + + + + + +
+
{{:: 'auth-type' | translate}}{{:: 'requirement' | translate}} 
+ + + {{execution.displayName|capitalize}}({{execution.alias}}) +    + + + + + +
{{:: 'no-executions-available' | translate}}
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authenticator-config.html b/base/admin/resources/partials/authenticator-config.html new file mode 100755 index 0000000..1b34406 --- /dev/null +++ b/base/admin/resources/partials/authenticator-config.html @@ -0,0 +1,52 @@ +
+ + + +

{{:: 'create-authenticator-config' | translate}}

+

+ {{config.alias|capitalize}} + {{config.id}} + +

+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ {{:: 'authenticator.alias.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/mgmt/broker-permissions.html b/base/admin/resources/partials/authz/mgmt/broker-permissions.html new file mode 100644 index 0000000..2e389ff --- /dev/null +++ b/base/admin/resources/partials/authz/mgmt/broker-permissions.html @@ -0,0 +1,40 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-role.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/mgmt/client-permissions.html b/base/admin/resources/partials/authz/mgmt/client-permissions.html new file mode 100644 index 0000000..7f29fd7 --- /dev/null +++ b/base/admin/resources/partials/authz/mgmt/client-permissions.html @@ -0,0 +1,39 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-role.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/mgmt/client-role-permissions.html b/base/admin/resources/partials/authz/mgmt/client-role-permissions.html new file mode 100644 index 0000000..c76ecec --- /dev/null +++ b/base/admin/resources/partials/authz/mgmt/client-role-permissions.html @@ -0,0 +1,40 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-role.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/mgmt/group-permissions.html b/base/admin/resources/partials/authz/mgmt/group-permissions.html new file mode 100644 index 0000000..f2be6d9 --- /dev/null +++ b/base/admin/resources/partials/authz/mgmt/group-permissions.html @@ -0,0 +1,39 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-role.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html b/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html new file mode 100644 index 0000000..e21ee63 --- /dev/null +++ b/base/admin/resources/partials/authz/mgmt/realm-role-permissions.html @@ -0,0 +1,39 @@ +
+ + + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-role.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/mgmt/users-permissions.html b/base/admin/resources/partials/authz/mgmt/users-permissions.html new file mode 100644 index 0000000..2665bba --- /dev/null +++ b/base/admin/resources/partials/authz/mgmt/users-permissions.html @@ -0,0 +1,35 @@ +
+ + + +
+
+
+ +
+ +
+ {{:: 'permissions-enabled-users.tooltip' | translate}} +
+
+
+ + + + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html b/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html new file mode 100644 index 0000000..af5aace --- /dev/null +++ b/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html @@ -0,0 +1,131 @@ +
+ + + +

{{:: 'authz-add-resource-permission' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-permission-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-permission-description.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-permission-resource-apply-to-resource-type.tooltip' | translate}} +
+
+ + +
+ +
+ {{:: 'authz-permission-resource-resource.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-permission-resource-type.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+ +
+
{{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'actions' | translate}}
{{policy.name}}{{policy.name}}{{policy.description}} + {{:: 'remove' | translate}} +
{{:: 'authz-no-policies-assigned' | translate}}
+
+ {{:: 'authz-policy-apply-policy.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-decision-strategy.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html b/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html new file mode 100644 index 0000000..17ee7cb --- /dev/null +++ b/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html @@ -0,0 +1,134 @@ +
+ + + +

{{:: 'authz-add-scope-permission' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-permission-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-permission-description.tooltip' | translate}} +
+
+ + +
+ +
+ {{:: 'authz-permission-scope-resource.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-permission-scope-scope.tooltip' | translate}} +
+
+ + +
+ +
+ {{:: 'authz-permission-scope-scope.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+ +
+
{{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'actions' | translate}}
{{policy.name}}{{policy.name}}{{policy.description}} + {{:: 'remove' | translate}} +
{{:: 'authz-no-policies-assigned' | translate}}
+
+ {{:: 'authz-policy-apply-policy.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-decision-strategy.tooltip' | translate}} +
+ +
+
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/permission/resource-server-permission-list.html b/base/admin/resources/partials/authz/permission/resource-server-permission-list.html new file mode 100644 index 0000000..40dfacd --- /dev/null +++ b/base/admin/resources/partials/authz/permission/resource-server-permission-list.html @@ -0,0 +1,118 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ {{:: 'filter' | translate}}:   +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+
{{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'type' | translate}}{{:: 'actions' | translate}}
+
+ + + +
+
+ + + {{policy.name}}{{policy.description}}{{policy.type}} + +
+
+
+ +
+
+
+
+
{{:: 'authz-associated-policies' | translate}}
+
+ {{:: 'authz-no-policies-available' | translate}} + {{dep.name}}{{$last ? '' : ', '}} +
+
+
+
+
+
{{:: 'no-results' | translate}}{{:: 'authz-no-permissions-available' | translate}}
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html new file mode 100644 index 0000000..25be65b --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-aggregate-detail.html @@ -0,0 +1,123 @@ +
+ + + +

{{:: 'authz-add-aggregated-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+
+ +
+
{{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'actions' | translate}}
{{policy.name}}{{policy.name}}{{policy.description}} + {{:: 'remove' | translate}} +
{{:: 'authz-no-policies-assigned' | translate}}
+
+ {{:: 'authz-policy-apply-policy.tooltip' | translate}} +
+
+ +
+ +
+ + {{:: 'authz-policy-decision-strategy.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html new file mode 100644 index 0000000..9c5630a --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-detail.html @@ -0,0 +1,93 @@ +
+ + + +

{{:: 'authz-add-client-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ + +
+ + +
+ {{:: 'authz-policy-client-clients.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + +
{{:: 'clientId' | translate}}{{:: 'actions' | translate}}
{{client.clientId}} + +
{{:: 'authz-no-clients-assigned' | translate}}
+
+
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-scope-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-scope-detail.html new file mode 100644 index 0000000..e48f550 --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-client-scope-detail.html @@ -0,0 +1,126 @@ + + +
+ + + +

{{:: 'authz-add-client-scope-policy' | translate}}

+

+ {{originalPolicy.name|capitalize}} +

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-client-scope-client-scopes.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + +
{{:: 'name' | translate}}{{:: 'authz-required' | translate}}{{:: 'actions' | translate}}
{{clientScope.name}} + +
{{:: 'authz-no-client-scopes-assigned' | translate}}
+
+
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html new file mode 100644 index 0000000..cc1353b --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-group-detail.html @@ -0,0 +1,126 @@ + + +
+ + + +

{{:: 'authz-add-group-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-group-claim.tooltip' | translate}} +
+
+ +
+
+
+ + +
+ {{:: 'authz-policy-user-users.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + +
{{:: 'path' | translate}}Extend to Children{{:: 'actions' | translate}}
{{group.path}} + + + +
{{:: 'authz-no-groups-assigned' | translate}}
+
+
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-js-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-js-detail.html new file mode 100644 index 0000000..172c2b6 --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-js-detail.html @@ -0,0 +1,69 @@ + +
+ + + +

{{:: 'authz-add-js-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ +
+
+
+ {{:: 'authz-policy-js-code.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html new file mode 100644 index 0000000..9448682 --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-role-detail.html @@ -0,0 +1,169 @@ + + +
+ + + +

{{:: 'authz-add-role-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-role-realm-roles.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + +
{{:: 'name' | translate}}{{:: 'authz-required' | translate}}{{:: 'actions' | translate}}
{{role.name}} + +
{{:: 'authz-no-roles-assigned' | translate}}
+
+
+
+ + +
+ +
+ {{:: 'authz-policy-role-clients.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-role-client-roles.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
{{:: 'name' | translate}}{{:: 'client' | translate}}{{:: 'authz-required' | translate}}{{:: 'actions' | translate}}
{{role.name}}{{role.container.name}} + +
{{:: 'authz-no-roles-assigned' | translate}}
+
+
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+
+
+ + +
+
+ {{policyState.page.previous}} +
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html new file mode 100644 index 0000000..4af9014 --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html @@ -0,0 +1,119 @@ + +
+ + + + +

{{:: 'authz-add-time-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ + +
+ +
+ {{:: 'authz-policy-time-not-before.tooltip' | translate}} +
+
+ + +
+ +
+ {{:: 'authz-policy-time-not-on-after.tooltip' | translate}} +
+
+ + +
+   to   +
+ {{:: 'authz-policy-time-day-month.tooltip' | translate}} +
+
+ + +
+   to   +
+ {{:: 'authz-policy-time-month.tooltip' | translate}} +
+
+ + +
+   to   +
+ {{:: 'authz-policy-time-year.tooltip' | translate}} +
+
+ + +
+   to   +
+ {{:: 'authz-policy-time-hour.tooltip' | translate}} +
+
+ + +
+   to   +
+ {{:: 'authz-policy-time-minute.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html new file mode 100644 index 0000000..80d81ac --- /dev/null +++ b/base/admin/resources/partials/authz/policy/provider/resource-server-policy-user-detail.html @@ -0,0 +1,93 @@ +
+ + + +

{{:: 'authz-add-user-policy' | translate}}

+

{{originalPolicy.name|capitalize}}

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ + +
+ + +
+ {{:: 'authz-policy-user-users.tooltip' | translate}} +
+
+ +
+ + + + + + + + + + + + + + + + +
{{:: 'username' | translate}}{{:: 'actions' | translate}}
{{user.username}} + +
{{:: 'authz-no-users-assigned' | translate}}
+
+
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+ +
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html b/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html new file mode 100644 index 0000000..19ff720 --- /dev/null +++ b/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate-result.html @@ -0,0 +1,72 @@ +
+
+ {{:: 'authz-evaluation-no-result' | translate}} +
+ {{result.resource.name}} + +
+ + +
+
+ {{result.status}} + {{result.status}} +
+
+ {{:: 'authz-evaluation-result.tooltip' | translate}} +
+
+ + +
+ {{:: 'authz-no-scopes-available' | translate}} + +
+
    +
  • + {{scope.name}} +
  • +
+
+
+ {{:: 'authz-evaluation-scopes.tooltip' | translate}} +
+
+ + +
+ {{:: 'authz-evaluation-no-policies-resource' | translate}} +
+
+
  • + + {{policyResult.policy.name}} + + {{policyResult.policy.description}} + + + decision was {{policyResult.status}} + {{policyResult.status}} + by {{policyResult.policy.decisionStrategy}} decision. {{policyResult.policy.scopes.length > 0 ? (policyResult.status == 'DENY' ? 'Denied Scopes:' : 'Granted Scopes:') : ''}} {{scope}}{{$last ? '' : ', '}}{{policyResult.policy.scopes.length > 0 ? '.' : ''}} + +
  • + +
    +
    + {{:: 'authz-evaluation-policies.tooltip' | translate}} +
    +
    +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html b/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html new file mode 100644 index 0000000..aedbdea --- /dev/null +++ b/base/admin/resources/partials/authz/policy/resource-server-policy-evaluate.html @@ -0,0 +1,267 @@ +
    + + + + + + + + + +
    +
    + +
    + +
    + {{:: 'authz-evaluation-authorization-data.tooltip' | translate}} +
    +
    + +
    +
    +
    +
    + {{:: 'authz-evaluation-identity-information' | translate}} + {{:: 'authz-evaluation-identity-information.tooltip' | translate}} + +
    + + +
    +
    + +
    +
    + {{:: 'authz-evaluation-client.tooltip' | translate}} +
    +
    + + +
    + + +
    + + {{:: 'authz-evaluation-user.tooltip' | translate}} +
    + +
    + + +
    + +
    + + {{:: 'authz-evaluation-role.tooltip' | translate}} +
    +
    +
    + {{:: 'authz-evaluation-contextual-info' | translate}} + {{:: 'authz-evaluation-contextual-info.tooltip' | translate}} + +
    + + +
    + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{getContextAttributeName(key)}} + + + + +
    + + + + + + + +
    +
    + + {{:: 'authz-evaluation-contextual-attributes.tooltip' | translate}} +
    +
    +
    + {{:: 'authz-permissions' | translate}} + {{:: 'authz-evaluation-permissions.tooltip' | translate}} + +
    + + +
    + +
    + {{:: 'authz-permission-resource-apply-to-resource-type.tooltip' | translate}} + +
    +
    + + +
    + +
    + {{:: 'authz-permission-resource-resource.tooltip' | translate}} +
    +
    + + +
    + +
    + + {{:: 'authz-permission-resource-type.tooltip' | translate}} +
    +
    + + +
    + +
    + + {{:: 'authz-permission-scope-scope.tooltip' | translate}} +
    +
    + + +
    + +
    + + {{:: 'authz-permission-scope-scope.tooltip' | translate}} +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + +
    {{:: 'authz-resource' | translate}}{{:: 'authz-scopes' | translate}}{{:: 'actions' | translate}}
    + {{:: 'authz-no-resources' | translate}} +
    {{resource.name ? resource.name : 'authz-evaluation-any-resource-with-scopes' | translate}} + {{:: 'authz-any-scope' | translate}}. + + + {{scope.name ? scope.name : scope}} {{$last ? '' : ', '}} + + + + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/policy/resource-server-policy-list.html b/base/admin/resources/partials/authz/policy/resource-server-policy-list.html new file mode 100644 index 0000000..d142c44 --- /dev/null +++ b/base/admin/resources/partials/authz/policy/resource-server-policy-list.html @@ -0,0 +1,117 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + {{:: 'filter' | translate}}:   +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    {{:: 'name' | translate}}{{:: 'description' | translate}}{{:: 'type' | translate}}{{:: 'actions' | translate}}
    +
    + + + +
    +
    + + + {{policy.name}}{{policy.description}}{{policy.type}} + +
    +
    +
    + +
    +
    +
    +
    +
    Dependent Permissions
    +
    + {{:: 'authz-no-policies-available' | translate}} + {{dep.name}}{{$last ? '' : ', '}} +
    +
    +
    +
    +
    +
    {{:: 'no-results' | translate}}{{:: 'authz-no-policies-available' | translate}}
    +
    + + diff --git a/base/admin/resources/partials/authz/resource-server-detail.html b/base/admin/resources/partials/authz/resource-server-detail.html new file mode 100644 index 0000000..5bf7d88 --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-detail.html @@ -0,0 +1,77 @@ +
    + + + + + +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + {{:: 'authz-import-config.tooltip' | translate}} +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    + +
    + {{:: 'authz-policy-enforcement-mode.tooltip' | translate}} +
    +
    + + +
    + +
    + + {{:: 'authz-server-decision-strategy.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-remote-resource-management.tooltip' | translate}} +
    +
    +
    + + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/resource-server-export-settings.html b/base/admin/resources/partials/authz/resource-server-export-settings.html new file mode 100644 index 0000000..86505db --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-export-settings.html @@ -0,0 +1,35 @@ +
    + + + + + +
    +
    +
    + +
    + + + +
    + {{:: 'authz-export-settings.tooltip' | translate}} +
    +
    +
    +
    + {{:: 'download' | translate}} + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/resource-server-list.html b/base/admin/resources/partials/authz/resource-server-list.html new file mode 100644 index 0000000..3b4130e --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-list.html @@ -0,0 +1,49 @@ +
    +

    + Resource Servers + Resource Servers are applications serving resources to their users. These resources can be a RESTFul API, web pages or any other kind of resource that must be managed and protected by a set of authorization policies. +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + Create +
    +
    +
    NamePolicy Enforcement ModeAllows Remote Resource Management ?Allows Entitlement ?
    {{server.name}}{{server.policyEnforcementMode | toCamelCase}}{{server.allowRemoteResourceManagement}}{{server.allowEntitlements}}
    No resultsNo servers available
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/resource-server-resource-detail.html b/base/admin/resources/partials/authz/resource-server-resource-detail.html new file mode 100644 index 0000000..b3d6eca --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-resource-detail.html @@ -0,0 +1,126 @@ +
    + + + +

    {{:: 'authz-add-resource' | translate}}

    +

    {{originalResource.name|capitalize}}

    + +
    +
    +
    + +
    + +
    + {{:: 'authz-resource-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-resource-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-resource-owner.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-resource-type.tooltip' | translate}} +
    +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + {{:: 'authz-resource-uri.tooltip' | translate}} +
    +
    + + +
    + +
    + + {{:: 'authz-resource-scopes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-icon-uri.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-resource-user-managed-access-enabled.tooltip' | translate}} +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{key}}{{:: 'delete' | translate}}
    {{:: 'add' | translate}}
    +
    + {{:: 'authz-resource-attributes.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/resource-server-resource-list.html b/base/admin/resources/partials/authz/resource-server-resource-list.html new file mode 100644 index 0000000..dce000e --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-resource-list.html @@ -0,0 +1,169 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + {{:: 'filter' | translate}}:   +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    {{:: 'name' | translate}}{{:: 'type' | translate}}{{:: 'authz-uris' | translate}}{{:: 'authz-owner' | translate}}{{:: 'actions' | translate}}
    +
    + + + +
    +
    + + + + {{resource.name}} + + {{resource.type}} + {{:: 'authz-no-type-defined' | translate}} + + {{:: 'authz-no-uri-defined' | translate}} + {{resource.uris[0]}} + {{resource.uris.length}} {{:: 'authz-uris' | translate}} + {{resource.owner.name}} + +
    +
    +
    + +
    +
    +
    +
    +
    {{:: 'authz-scopes' | translate}}
    +
    + {{:: 'authz-no-scopes-assigned' | translate}} + {{scope.name}}{{$last ? '' : ', '}} +
    +
    {{:: 'authz-associated-permissions' | translate}}
    +
    + {{:: 'authz-no-permission-assigned' | translate}} + {{policy.name}}{{$last ? '' : ', '}} +
    +
    {{:: 'authz-uris' | translate}}
    +
    + {{:: 'authz-no-uri-defined' | translate}} + {{uri}}{{$last ? '' : ', '}} +
    +
    +
    +
    +
    +
    {{:: 'no-results' | translate}}{{:: 'authz-no-resources-available' | + translate}} +
    +
    + + diff --git a/base/admin/resources/partials/authz/resource-server-scope-detail.html b/base/admin/resources/partials/authz/resource-server-scope-detail.html new file mode 100644 index 0000000..d296abd --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-scope-detail.html @@ -0,0 +1,50 @@ +
    + + + +

    {{:: 'authz-add-scope' | translate}}

    +

    {{originalScope.name|capitalize}}

    + +
    +
    +
    + +
    + +
    + {{:: 'authz-scope-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-scope-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'authz-icon-uri.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/authz/resource-server-scope-list.html b/base/admin/resources/partials/authz/resource-server-scope-list.html new file mode 100644 index 0000000..22c7f38 --- /dev/null +++ b/base/admin/resources/partials/authz/resource-server-scope-list.html @@ -0,0 +1,102 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    {{:: 'name' | translate}}{{:: 'actions' | translate}}
    +
    + + + +
    +
    + + + {{scope.name}} + +
    +
    +
    + +
    +
    +
    +
    +
    {{:: 'authz-resources' | translate}}
    +
    + {{:: 'authz-no-resources-assigned' | translate}} + {{resource.name}}{{$last ? '' : ', '}} +
    +
    {{:: 'authz-associated-permissions' | translate}}
    +
    + {{:: 'authz-no-permission-assigned' | translate}} + {{policy.name}}{{$last ? '' : ', '}} +
    +
    +
    +
    +
    +
    {{:: 'no-results' | translate}}{{:: 'authz-no-scopes-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/brute-force.html b/base/admin/resources/partials/brute-force.html new file mode 100755 index 0000000..6ab0092 --- /dev/null +++ b/base/admin/resources/partials/brute-force.html @@ -0,0 +1,114 @@ +
    + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'permanent-lockout.tooltip' | translate}} +
    + +
    + + +
    + +
    + {{:: 'max-login-failures.tooltip' | translate}} +
    +
    + +
    + + +
    + {{:: 'wait-increment.tooltip' | translate}} +
    +
    + + +
    + +
    + {{:: 'quick-login-check-millis.tooltip' | translate}} +
    +
    + +
    + + +
    + {{:: 'min-quick-login-wait.tooltip' | translate}} +
    +
    + +
    + + +
    + {{:: 'max-wait.tooltip' | translate}} +
    +
    + +
    + + +
    + {{:: 'failure-reset-time.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/ciba-policy.html b/base/admin/resources/partials/ciba-policy.html new file mode 100644 index 0000000..9c7ccd1 --- /dev/null +++ b/base/admin/resources/partials/ciba-policy.html @@ -0,0 +1,62 @@ +
    +

    {{:: 'authentication' | translate}}

    + + +
    + +
    + +
    +
    + +
    +
    + {{:: 'ciba-backchannel-tokendelivery-mode.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'ciba-expires-in.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'ciba-interval.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'ciba-auth-requested-user-hint.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    + +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/claims.html b/base/admin/resources/partials/claims.html new file mode 100755 index 0000000..27357ca --- /dev/null +++ b/base/admin/resources/partials/claims.html @@ -0,0 +1,62 @@ +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/client-clustering-node.html b/base/admin/resources/partials/client-clustering-node.html new file mode 100644 index 0000000..56eb9db --- /dev/null +++ b/base/admin/resources/partials/client-clustering-node.html @@ -0,0 +1,37 @@ +
    + + +

    {{:: 'add-node' | translate}}

    +

    + {{node.host|capitalize}} + +

    + +
    +
    + +
    + +
    +
    +
    + +
    + {{node.lastRegistration}} +
    +
    +
    +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-clustering.html b/base/admin/resources/partials/client-clustering.html new file mode 100644 index 0000000..72ff437 --- /dev/null +++ b/base/admin/resources/partials/client-clustering.html @@ -0,0 +1,76 @@ +
    + + + + + +
    + {{:: 'basic-configuration' | translate}} +
    +
    + +
    +
    +
    + + +
    +
    +
    + {{:: 'node-reregistration-timeout.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    + +
    + {{:: 'registered-cluster-nodes' | translate}} + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'node-host' | translate}}{{:: 'last-registration' | translate}}{{:: 'actions' | translate}}
    {{node.host}}{{node.lastRegistration}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-registered-cluster-nodes' | translate}}
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-credentials-generic.html b/base/admin/resources/partials/client-credentials-generic.html new file mode 100644 index 0000000..39e9ebc --- /dev/null +++ b/base/admin/resources/partials/client-credentials-generic.html @@ -0,0 +1,14 @@ +
    +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/client-credentials-jwt-key-export.html b/base/admin/resources/partials/client-credentials-jwt-key-export.html new file mode 100644 index 0000000..a1da8c1 --- /dev/null +++ b/base/admin/resources/partials/client-credentials-jwt-key-export.html @@ -0,0 +1,57 @@ +
    + + + +

    {{:: 'generate-private-key' | translate}}

    + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'archive-format.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-password.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'store-password.tooltip' | translate}} +
    +
    +
    + + +
    +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-credentials-jwt-key-import.html b/base/admin/resources/partials/client-credentials-jwt-key-import.html new file mode 100644 index 0000000..6ea0118 --- /dev/null +++ b/base/admin/resources/partials/client-credentials-jwt-key-import.html @@ -0,0 +1,62 @@ +
    + + + +

    {{:: 'import-client-certificate' | translate}}

    + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'archive-format.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'jwt-import.key-alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'store-password.tooltip' | translate}} +
    +
    + +
    +
    + + +
    + + {{files[0].name}} + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-credentials-jwt.html b/base/admin/resources/partials/client-credentials-jwt.html new file mode 100644 index 0000000..98c167d --- /dev/null +++ b/base/admin/resources/partials/client-credentials-jwt.html @@ -0,0 +1,89 @@ +
    + +
    + +
    +
    + +
    +
    + {{:: 'token-endpoint-auth-signing-alg.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'use-jwks-url.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'jwks-url.tooltip' | translate}} +
    + +
    + +
    + + {{:: 'certificate.tooltip' | translate}} + +
    + +
    +
    + +
    + + {{:: 'publicKey.tooltip' | translate}} + +
    + +
    +
    + +
    + + {{:: 'kid.tooltip' | translate}} + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + {{:: 'no-client-certificate-configured' | translate}} +
    +
    +
    +
    + +
    + +
    +
    + + + + +
    +
    + +
    \ No newline at end of file diff --git a/base/admin/resources/partials/client-credentials-secret-jwt.html b/base/admin/resources/partials/client-credentials-secret-jwt.html new file mode 100644 index 0000000..444bf02 --- /dev/null +++ b/base/admin/resources/partials/client-credentials-secret-jwt.html @@ -0,0 +1,39 @@ +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    + +
    +
    + {{:: 'token-endpoint-auth-signing-alg.tooltip' | translate}} +
    + +
    +
    + +
    +
    + +
    +
    diff --git a/base/admin/resources/partials/client-credentials-secret.html b/base/admin/resources/partials/client-credentials-secret.html new file mode 100644 index 0000000..7cf5bf1 --- /dev/null +++ b/base/admin/resources/partials/client-credentials-secret.html @@ -0,0 +1,17 @@ +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    diff --git a/base/admin/resources/partials/client-credentials-x509.html b/base/admin/resources/partials/client-credentials-x509.html new file mode 100644 index 0000000..c3ee5f1 --- /dev/null +++ b/base/admin/resources/partials/client-credentials-x509.html @@ -0,0 +1,21 @@ +
    +
    +
    + + {{:: 'subjectdn-tooltip' | translate}} +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    diff --git a/base/admin/resources/partials/client-credentials.html b/base/admin/resources/partials/client-credentials.html new file mode 100755 index 0000000..e6865a3 --- /dev/null +++ b/base/admin/resources/partials/client-credentials.html @@ -0,0 +1,38 @@ +
    + + + + + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'client-authenticator.tooltip' | translate}} +
    +
    +
    + +
    +
    + +
    + +
    +
    + +
    + + diff --git a/base/admin/resources/partials/client-detail.html b/base/admin/resources/partials/client-detail.html new file mode 100755 index 0000000..c7d7ee3 --- /dev/null +++ b/base/admin/resources/partials/client-detail.html @@ -0,0 +1,815 @@ +
    + + + + + +
    +
    +
    + +
    + +
    + {{:: 'client-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client.name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client.description.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'alwaysDisplayInConsole.tooltip' | translate}} +
    +
    + +
    + {{originName}} +
    + {{:: 'client-origin.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'consent-required.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client.display-on-consent-screen.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client.consent-screen-text.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'login-theme.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'client-protocol.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'access-type.tooltip' | translate}} +
    +
    + + {{:: 'standard-flow-enabled.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'implicit-flow-enabled.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'direct-access-grants-enabled.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'service-accounts-enabled.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'oauth2-device-authorization-grant-enabled.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'oidc-ciba-grant-enabled.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'authz-authorization-services-enabled.tooltip' | translate}} +
    + +
    +
    +
    + +
    + +
    + {{:: 'include-authnstatement.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'include-onetimeuse-condition.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'artifact-binding.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'sign-documents.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'sign-documents-redirect-enable-key-info-ext.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'sign-assertions.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'signature-algorithm.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'saml-signature-keyName-transformer.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'canonicalization-method.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'encrypt-assertions.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-signature-required.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'force-post-binding.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'front-channel-logout.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'force-name-id-format.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'name-id-format.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'root-url.tooltip' | translate}} +
    + +
    + + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + + {{:: 'valid-redirect-uris.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'base-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'admin-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'master-saml-processing-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'idp-sso-url-ref.urlhint' | translate}} {{samlIdpInitiatedUrl(clientEdit.attributes.saml_idp_initiated_sso_url_name)}} +
    +
    + {{:: 'idp-sso-url-ref.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'idp-sso-relay-state.tooltip' | translate}} +
    +
    + + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + + {{:: 'web-origins.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'backchannel-logout-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'backchannel-logout-session-required.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'backchannel-logout-revoke-offline-sessions.tooltip' | translate}} +
    +
    +
    + {{:: 'fine-saml-endpoint-conf' | translate}} {{:: 'fine-saml-endpoint-conf.tooltip' | translate}} +
    + +
    + +
    + {{:: 'assertion-consumer-post-binding-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'assertion-consumer-redirect-binding-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'logout-service-post-binding-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'logout-service-redir-binding-url.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'logout-service-artifact-binding-url.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'artifact-binding-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'artifact-resolution-service-url.tooltip' | translate}} +
    +
    + +
    + {{:: 'fine-oidc-endpoint-conf' | translate}} {{:: 'fine-oidc-endpoint-conf.tooltip' | translate}} + +
    + +
    +
    + +
    +
    + {{:: 'access-token-signed-response-alg.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'id-token-signed-response-alg.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'id-token-encrypted-response-alg.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'id-token-encrypted-response-enc.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'user-info-signed-response-alg.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'request-object-signature-alg.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'request-object-required.tooltip' | translate}} +
    +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + {{:: 'request-uris.tooltip' | translate}} +
    +
    + +
    + {{:: 'oidc-compatibility-modes' | translate}} {{:: 'oidc-compatibility-modes.tooltip' | translate}} +
    + +
    + +
    + {{:: 'exclude-session-state-from-auth-response.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'use-refresh-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'use-refresh-token-for-client-credentials-grant.tooltip' | translate}} +
    +
    + +
    + {{:: 'advanced-client-settings' | translate}} {{:: 'advanced-client-settings.tooltip' | translate}} + +
    + + +
    + + +
    + {{:: 'access-token-lifespan.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'saml-assertion-lifespan.tooltip' | translate}} +
    + +
    + +
    + + +
    + {{:: 'client-session-idle.tooltip' | translate}} +
    + +
    + +
    + + +
    + {{:: 'client-session-max.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-offline-session-idle.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-offline-session-max.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'oauth2-device-code-lifespan.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'oauth2-device-polling-interval.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'tls-client-certificate-bound-access-tokens.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'pkce-code-challenge-method.tooltip' | translate}} +
    +
    + +
    + {{:: 'client-flow-bindings' | translate}} {{:: 'client-flow-bindings.tooltip' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'browser-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'direct-grant-flow.tooltip' | translate}} +
    +
    + + + +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-import.html b/base/admin/resources/partials/client-import.html new file mode 100755 index 0000000..18ec93c --- /dev/null +++ b/base/admin/resources/partials/client-import.html @@ -0,0 +1,46 @@ +
    + + + +

    {{:: 'import-client' | translate}}

    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    + + {{files[0].name}} + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-initial-access-create.html b/base/admin/resources/partials/client-initial-access-create.html new file mode 100755 index 0000000..8c0cb19 --- /dev/null +++ b/base/admin/resources/partials/client-initial-access-create.html @@ -0,0 +1,63 @@ +
    + + + +

    {{:: 'add-client' | translate}}

    + +
    + +
    + + +
    + + +
    + {{:: 'expiration.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'count.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    + +
    +
    + + +
    + +
    + + {{:: 'initial-access.copyPaste.tooltip' | translate}} +
    + +
    +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-initial-access.html b/base/admin/resources/partials/client-initial-access.html new file mode 100755 index 0000000..b2e683d --- /dev/null +++ b/base/admin/resources/partials/client-initial-access.html @@ -0,0 +1,55 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    {{:: 'id' | translate}}{{:: 'created' | translate}}{{:: 'expires' | translate}}{{:: 'count' | translate}}{{:: 'remainingCount' | translate}}{{:: 'actions' | translate}}
    {{ia.id}}{{(ia.timestamp * 1000)|date:('dateFormat' | translate)}} {{(ia.timestamp * 1000)|date:('timeFormat' | translate)}}{{((ia.timestamp + ia.expiration) * 1000)|date:('dateFormat' | translate)}} {{((ia.timestamp + ia.expiration) * 1000)|date:('timeFormat' | translate)}}{{ia.count}}{{ia.remainingCount}}{{:: 'delete' | translate}}
    {{:: 'no-results' | translate}}{{:: 'no-initial-access-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-installation.html b/base/admin/resources/partials/client-installation.html new file mode 100755 index 0000000..d03fdd2 --- /dev/null +++ b/base/admin/resources/partials/client-installation.html @@ -0,0 +1,36 @@ +
    + + + + + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + {{:: 'download' | translate}} + +
    +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-keys.html b/base/admin/resources/partials/client-keys.html new file mode 100755 index 0000000..c412e27 --- /dev/null +++ b/base/admin/resources/partials/client-keys.html @@ -0,0 +1,146 @@ +
    + + + + + +
    +
    + {{:: 'import-keys-and-cert' | translate}} {{:: 'import-keys-and-cert.tooltip' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'archive-format.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-password.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'store-password.tooltip' | translate}} +
    +
    + + +
    +
    + + +
    +
    +
    + {{:: 'download-keys-and-cert' | translate}} Client key pair, cert, and realm certificate will be stuffed into a PKCS12 or Java keystore that you can use in your clients. +
    + +
    +
    + +
    +
    + Java keystore or PKCS12 archive format. +
    +
    + +
    + +
    + Archive alias for your private key and certificate. +
    +
    + +
    + +
    + Password to access the private key in the archive +
    +
    + +
    + +
    + Realm certificate is stored in archive too. This is the alias to it. +
    +
    + +
    + +
    + Password to access the archive itself +
    +
    +
    + +
    +
    +
    +
    + Keys and Certificate Keys and cert in PEM format. +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-list.html b/base/admin/resources/partials/client-list.html new file mode 100755 index 0000000..6640d5a --- /dev/null +++ b/base/admin/resources/partials/client-list.html @@ -0,0 +1,68 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-mappers-add.html b/base/admin/resources/partials/client-mappers-add.html new file mode 100755 index 0000000..6537779 --- /dev/null +++ b/base/admin/resources/partials/client-mappers-add.html @@ -0,0 +1,53 @@ +
    + + + +

    {{:: 'add-builtin-protocol-mapper' | translate}}

    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {{:: 'name' | translate}}{{:: 'category' | translate}}{{:: 'type' | translate}}{{:: 'add' | translate}}
    {{mapper.name}}{{mapperTypes[mapper.protocolMapper].category}}{{mapperTypes[mapper.protocolMapper].name}}
    {{:: 'no-mappers-available' | translate}}
    + +
    + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-mappers.html b/base/admin/resources/partials/client-mappers.html new file mode 100755 index 0000000..83123c2 --- /dev/null +++ b/base/admin/resources/partials/client-mappers.html @@ -0,0 +1,55 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'name' | translate}}{{:: 'category' | translate}}{{:: 'type' | translate}}{{:: 'priority-order' | translate}}{{:: 'actions' | translate}}
    {{mapper.name}}{{mapperTypes[mapper.protocolMapper].category}}{{mapperTypes[mapper.protocolMapper].name}}{{mapperTypes[mapper.protocolMapper].priority}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-mappers-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-offline-sessions.html b/base/admin/resources/partials/client-offline-sessions.html new file mode 100644 index 0000000..3d2aaf7 --- /dev/null +++ b/base/admin/resources/partials/client-offline-sessions.html @@ -0,0 +1,59 @@ +
    + + + + + +
    +
    +
    + +
    + +
    + {{:: 'offline-tokens.tooltip' | translate}} +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'user' | translate}}{{:: 'from-ip' | translate}}{{:: 'token-issued' | translate}}{{:: 'last-refresh' | translate}}
    +
    + + + +
    +
    {{session.username}}{{session.ipAddress}}{{session.start | date:'medium'}}{{session.lastAccess | date:'medium'}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-protocol-mapper-detail.html b/base/admin/resources/partials/client-protocol-mapper-detail.html new file mode 100755 index 0000000..b2af24a --- /dev/null +++ b/base/admin/resources/partials/client-protocol-mapper-detail.html @@ -0,0 +1,13 @@ +
    + + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-reg-policies.html b/base/admin/resources/partials/client-reg-policies.html new file mode 100644 index 0000000..9ca912f --- /dev/null +++ b/base/admin/resources/partials/client-reg-policies.html @@ -0,0 +1,106 @@ + + +
    + + + + +
    + +
    + {{:: 'anonymous-policies' | translate}}{{:: 'anonymous-policies.tooltip' | translate}} + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    {{:: 'policy-name' | translate}}{{:: 'provider-id' | translate}}{{:: 'actions' | translate}}
    {{instance.name}}{{instance.providerId}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-client-reg-policies-configured' | translate}}
    +
    + +
    + {{:: 'auth-policies' | translate}}{{:: 'auth-policies.tooltip' | translate}} + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    {{:: 'policy-name' | translate}}{{:: 'provider-id' | translate}}{{:: 'actions' | translate}}
    {{instance.name}}{{instance.providerId}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-client-reg-policies-configured' | translate}}
    +
    + + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-reg-policy-detail.html b/base/admin/resources/partials/client-reg-policy-detail.html new file mode 100644 index 0000000..91a8ed9 --- /dev/null +++ b/base/admin/resources/partials/client-reg-policy-detail.html @@ -0,0 +1,68 @@ + + +
    + + + +
    +
    + {{headerTitle}}{{:: providerType.helpText | translate}} +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'client-reg-policy.name.tooltip' | translate}} +
    +
    + +
    + +
    + {{providerType.helpText}} +
    + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-reg-trusted-host-create.html b/base/admin/resources/partials/client-reg-trusted-host-create.html new file mode 100644 index 0000000..cb07613 --- /dev/null +++ b/base/admin/resources/partials/client-reg-trusted-host-create.html @@ -0,0 +1,55 @@ + + +
    + + + +

    {{:: 'add-client-reg-trusted-host' | translate}}

    + +
    + +
    + +
    + +
    + {{:: 'client-reg-hostname.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-reg-count.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-reg-trusted-host-detail.html b/base/admin/resources/partials/client-reg-trusted-host-detail.html new file mode 100644 index 0000000..5644fc1 --- /dev/null +++ b/base/admin/resources/partials/client-reg-trusted-host-detail.html @@ -0,0 +1,64 @@ + + +
    + + + +

    {{hostName}}

    + +
    + +
    + +
    + +
    + {{:: 'client-reg-hostname.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-reg-count.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-reg-remainingCount.tooltip' | translate}} +
    + +
    +
    + + + +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-registration-access-token.html b/base/admin/resources/partials/client-registration-access-token.html new file mode 100644 index 0000000..327b70d --- /dev/null +++ b/base/admin/resources/partials/client-registration-access-token.html @@ -0,0 +1,18 @@ +
    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    + {{:: 'registrationAccessToken.tooltip' | translate}} +
    +
    +
    diff --git a/base/admin/resources/partials/client-revocation.html b/base/admin/resources/partials/client-revocation.html new file mode 100755 index 0000000..95db767 --- /dev/null +++ b/base/admin/resources/partials/client-revocation.html @@ -0,0 +1,30 @@ +
    + + + + + +
    +
    +
    + +
    + +
    + {{:: 'client-revoke.not-before.tooltip' | translate}} +
    +
    +
    +
    + + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-role-attributes.html b/base/admin/resources/partials/client-role-attributes.html new file mode 100755 index 0000000..47ef416 --- /dev/null +++ b/base/admin/resources/partials/client-role-attributes.html @@ -0,0 +1,45 @@ +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{key}}{{:: 'delete' | translate}}
    {{:: 'add' | translate}}
    + +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-role-detail.html b/base/admin/resources/partials/client-role-detail.html new file mode 100755 index 0000000..8877dbe --- /dev/null +++ b/base/admin/resources/partials/client-role-detail.html @@ -0,0 +1,140 @@ +
    + + + + + +
    + +
    +
    + + +
    + +
    +
    +
    + + +
    + + +
    +
    +
    + +
    + +
    + {{:: 'composite-roles.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + {{:: 'composite-roles' | translate}} + +
    + + +
    +
    +
    + + {{:: 'composite.available-realm-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'composite.associated-realm-roles.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + +
    + + +
    + + +
    +
    +
    + + {{:: 'available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.associated-roles.tooltip' | translate}} + + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-role-list.html b/base/admin/resources/partials/client-role-list.html new file mode 100755 index 0000000..4feff0a --- /dev/null +++ b/base/admin/resources/partials/client-role-list.html @@ -0,0 +1,64 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    {{:: 'role-name' | translate}}{{:: 'composite' | translate}}{{:: 'description' | translate}}{{:: 'actions' | translate}}
    {{role.name}}{{role.description}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-results' | translate}}{{:: 'no-client-roles-available' | translate}}
    +
    + + + +
    +
    +
    + + diff --git a/base/admin/resources/partials/client-role-users.html b/base/admin/resources/partials/client-role-users.html new file mode 100644 index 0000000..5349171 --- /dev/null +++ b/base/admin/resources/partials/client-role-users.html @@ -0,0 +1,52 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'username' | translate}}{{:: 'last-name' | translate}}{{:: 'first-name' | translate}}{{:: 'email' | translate}}
    +
    + + + +
    +
    {{user.username}}{{user.lastName}}{{user.firstName}}{{user.email}}{{:: 'edit' | translate}}
    {{:: 'no-role-members' | translate}}{{:: 'no-role-members' | translate}}
    + +
    + + diff --git a/base/admin/resources/partials/client-saml-key-export.html b/base/admin/resources/partials/client-saml-key-export.html new file mode 100755 index 0000000..1c8f737 --- /dev/null +++ b/base/admin/resources/partials/client-saml-key-export.html @@ -0,0 +1,63 @@ +
    + + + +

    {{:: 'export-saml-key' | translate}} {{client.clientId|capitalize}}

    + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'archive-format.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-password.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'realm-certificate-alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'store-password.tooltip' | translate}} +
    +
    +
    + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-saml-key-import.html b/base/admin/resources/partials/client-saml-key-import.html new file mode 100755 index 0000000..5b14c24 --- /dev/null +++ b/base/admin/resources/partials/client-saml-key-import.html @@ -0,0 +1,62 @@ +
    + + + +

    {{:: 'import-saml-key' | translate}} {{client.clientId|capitalize}}

    + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'archive-format.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'key-alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'store-password.tooltip' | translate}} +
    +
    + +
    +
    + + +
    + + {{files[0].name}} + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-saml-keys.html b/base/admin/resources/partials/client-saml-keys.html new file mode 100755 index 0000000..776349d --- /dev/null +++ b/base/admin/resources/partials/client-saml-keys.html @@ -0,0 +1,66 @@ +
    + + + + + +
    +
    + {{:: 'signing-key' | translate}} {{:: 'saml-signing-key' | translate}} +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    + + + +
    +
    +
    +
    + {{:: 'encryption-key' | translate}} {{:: 'saml-encryption-key.tooltip' | translate}} +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-detail.html b/base/admin/resources/partials/client-scope-detail.html new file mode 100755 index 0000000..88c3216 --- /dev/null +++ b/base/admin/resources/partials/client-scope-detail.html @@ -0,0 +1,84 @@ +
    + + + + + +
    +
    +
    + +
    + +
    + {{:: 'client-scope.name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-scope.description.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'client-scope.protocol.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-scope.display-on-consent-screen.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-scope.consent-screen-text.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-scope.include-in-token-scope.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-scope.gui-order.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-list.html b/base/admin/resources/partials/client-scope-list.html new file mode 100755 index 0000000..1f945fc --- /dev/null +++ b/base/admin/resources/partials/client-scope-list.html @@ -0,0 +1,60 @@ +
    +

    + {{:: 'client-scopes' | translate}} + {{:: 'client-scopes.tooltip' | translate}} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    {{:: 'name' | translate}}{{:: 'protocol' | translate}}{{:: 'gui-order' | translate}}{{:: 'actions' | translate}}
    {{clientScope.name}}{{clientScope.protocol}}{{clientScope.attributes['gui.order']}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-results' | translate}}{{:: 'no-clients-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-mappers-add.html b/base/admin/resources/partials/client-scope-mappers-add.html new file mode 100755 index 0000000..413c065 --- /dev/null +++ b/base/admin/resources/partials/client-scope-mappers-add.html @@ -0,0 +1,53 @@ +
    + + + +

    {{:: 'add-builtin-protocol-mapper' | translate}}

    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {{:: 'name' | translate}}{{:: 'category' | translate}}{{:: 'type' | translate}}{{:: 'add' | translate}}
    {{mapper.name}}{{mapperTypes[mapper.protocolMapper].category}}{{mapperTypes[mapper.protocolMapper].name}}
    {{:: 'no-mappers-available' | translate}}
    + +
    + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-mappers.html b/base/admin/resources/partials/client-scope-mappers.html new file mode 100755 index 0000000..3b95910 --- /dev/null +++ b/base/admin/resources/partials/client-scope-mappers.html @@ -0,0 +1,55 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'name' | translate}}{{:: 'category' | translate}}{{:: 'type' | translate}}{{:: 'priority-order' | translate}}{{:: 'actions' | translate}}
    {{mapper.name}}{{mapperTypes[mapper.protocolMapper].category}}{{mapperTypes[mapper.protocolMapper].name}}{{mapperTypes[mapper.protocolMapper].priority}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-mappers-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-mappings.html b/base/admin/resources/partials/client-scope-mappings.html new file mode 100755 index 0000000..1343f1b --- /dev/null +++ b/base/admin/resources/partials/client-scope-mappings.html @@ -0,0 +1,127 @@ +
    + + + + + +

    {{client.clientId}} {{:: 'scope-mappings' | translate}}

    +

    +
    +
    +
    + + {{:: 'full-scope-allowed.tooltip' | translate}} +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + + {{:: 'scope.available-roles.tooltip' | translate}} + + + +
    +
    + + {{:: 'assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'realm.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    + +
    + +
    + + +
    + +
    +
    +
    + + {{:: 'assign.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-protocol-mapper-detail.html b/base/admin/resources/partials/client-scope-protocol-mapper-detail.html new file mode 100755 index 0000000..0f6cb5c --- /dev/null +++ b/base/admin/resources/partials/client-scope-protocol-mapper-detail.html @@ -0,0 +1,13 @@ +
    + + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scope-scope-mappings.html b/base/admin/resources/partials/client-scope-scope-mappings.html new file mode 100755 index 0000000..3180826 --- /dev/null +++ b/base/admin/resources/partials/client-scope-scope-mappings.html @@ -0,0 +1,116 @@ +
    + + + + + +

    {{clientScope.name}} {{:: 'scope-mappings' | translate}}

    +

    + +
    +
    + +
    +
    +
    + + {{:: 'scope.available-roles.tooltip' | translate}} + + + +
    +
    + + {{:: 'assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'realm.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    + +
    + +
    + + +
    + +
    +
    +
    + + {{:: 'assign.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scopes-evaluate.html b/base/admin/resources/partials/client-scopes-evaluate.html new file mode 100644 index 0000000..e8702ad --- /dev/null +++ b/base/admin/resources/partials/client-scopes-evaluate.html @@ -0,0 +1,268 @@ + + +
    + + + + + + + +
    +
    +
    + +
    + +
    + {{:: 'scope-parameter.tooltip' | translate}} +
    +
    + +
    + + {{:: 'client-scopes.evaluate.scopes.tooltip' | translate}} + +
    +
    +
    + + {{:: 'client-scopes.evaluate.scopes.available.tooltip' | translate}} + + +
    +
    + + {{:: 'client-scopes.evaluate.scopes.assigned.tooltip' | translate}} + + +
    +
    + + {{:: 'client-scopes.evaluate.scopes.effective.tooltip' | translate}} + +
    +
    +
    +
    + +
    + + +
    + + +
    + + {{:: 'client-scopes.evaluate.user.tooltip' | translate}} +
    + +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {{:: 'name' | translate}}{{:: 'parent-client-scope' | translate}}{{:: 'category' | translate}}{{:: 'type' | translate}}{{:: 'priority-order' | translate}}
    {{mapper.mapperName}}{{mapper.containerName}}{{mapperTypes[mapper.protocolMapper].category}}{{mapperTypes[mapper.protocolMapper].name}}{{mapperTypes[mapper.protocolMapper].priority}}
    {{:: 'no-mappers-available' | translate}}
    + + + +
    +
    + +
    +
    +
    + + {{:: 'client-scopes.evaluate.not-granted-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client-scopes.evaluate.granted-realm-effective-roles.tooltip' | translate}} + +
    +
    +
    +
    + +
    + +
    + + +
    + +
    +
    +
    + + {{:: 'client-scopes.evaluate.not-granted-roles.tooltip' | translate}} + +
    +
    + + {{:: 'client-scopes.evaluate.granted-realm-effective-roles.tooltip' | translate}} + +
    +
    +
    +
    +
    + + + +
    +
    +
    + +
    +
    +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scopes-realm-default.html b/base/admin/resources/partials/client-scopes-realm-default.html new file mode 100644 index 0000000..e30570a --- /dev/null +++ b/base/admin/resources/partials/client-scopes-realm-default.html @@ -0,0 +1,99 @@ +
    +

    + {{:: 'default-client-scopes' | translate}} + {{:: 'default-client-scopes.tooltip' | translate}} +

    + + + +
    +
    + + {{:: 'default-client-scopes.default.tooltip' | translate}} + +
    +
    +
    + + {{:: 'default-client-scopes.default.available.tooltip' | translate}} + + +
    +
    + + {{:: 'default-client-scopes.default.assigned.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + + {{:: 'default-client-scopes.optional.tooltip' | translate}} + +
    +
    +
    + + {{:: 'default-client-scopes.optional.available.tooltip' | translate}} + + +
    +
    + + {{:: 'default-client-scopes.optional.assigned.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-scopes-setup.html b/base/admin/resources/partials/client-scopes-setup.html new file mode 100644 index 0000000..db39cf0 --- /dev/null +++ b/base/admin/resources/partials/client-scopes-setup.html @@ -0,0 +1,123 @@ + + +
    + + + + + + + +
    +
    + + {{:: 'client-scopes.default.tooltip' | translate}} + +
    +
    +
    + + {{:: 'client-scopes.default.available.tooltip' | translate}} + + +
    +
    + + {{:: 'client-scopes.default.assigned.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + + {{:: 'client-scopes.optional.tooltip' | translate}} + +
    +
    +
    + + {{:: 'client-scopes.optional.available.tooltip' | translate}} + + +
    +
    + + {{:: 'client-scopes.optional.assigned.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-service-account-roles.html b/base/admin/resources/partials/client-service-account-roles.html new file mode 100644 index 0000000..bc17f1b --- /dev/null +++ b/base/admin/resources/partials/client-service-account-roles.html @@ -0,0 +1,127 @@ +
    + + + + + +

    {{client.clientId}} {{:: 'service-accounts' | translate}}

    +

    + +
    +
    + +
    +
    +
    + + {{:: 'service-account.available-roles.tooltip' | translate}} + + + +
    +
    + + {{:: 'service-account.assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'realm.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    + +
    + +
    + + +
    + +
    +
    +
    + + {{:: 'assign.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'client.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    +
    + +
    + +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-sessions.html b/base/admin/resources/partials/client-sessions.html new file mode 100755 index 0000000..5dbb2af --- /dev/null +++ b/base/admin/resources/partials/client-sessions.html @@ -0,0 +1,57 @@ +
    + + + + + +
    +
    +
    + +
    + +
    + {{:: 'active-sessions.tooltip' | translate}} +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'user' | translate}}{{:: 'from-ip' | translate}}{{:: 'session-start' | translate}}
    +
    + + + +
    +
    {{session.username}}{{session.ipAddress}}{{session.start | date:'medium'}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-storage-generic.html b/base/admin/resources/partials/client-storage-generic.html new file mode 100755 index 0000000..90ed088 --- /dev/null +++ b/base/admin/resources/partials/client-storage-generic.html @@ -0,0 +1,207 @@ +
    + + +
    +
    + {{:: 'required-settings' | translate}} +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'client-storage.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'console-display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'priority.tooltip' | translate}} +
    + + + +
    + +
    + {{:: 'client-storage-cache-policy' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'clientStorage.cachePolicy.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'clientStorage.cachePolicy.evictionDay.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'clientStorage.cachePolicy.evictionHour.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'clientStorage.cachePolicy.evictionMinute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'clientStorage.cachePolicy.maxLifespan.tooltip' | translate}} +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/client-storage-list.html b/base/admin/resources/partials/client-storage-list.html new file mode 100755 index 0000000..c111eaa --- /dev/null +++ b/base/admin/resources/partials/client-storage-list.html @@ -0,0 +1,67 @@ +
    + + + +
    +
    + +
    +

    + {{:: 'client-storage' | translate}} +

    + {{:: 'client-storage-list-no-entries' | translate}} +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    {{:: 'id' | translate}}{{:: 'enabled' | translate}}{{:: 'provider-name' | translate}}{{:: 'priority' | translate}}{{:: 'actions' | translate}}
    {{getInstanceName(instance)}}{{isProviderEnabled(instance)}}{{getInstanceProvider(instance) | capitalize}}{{getInstancePriority(instance)}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-client-storage-providers-configured' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/create-client.html b/base/admin/resources/partials/create-client.html new file mode 100755 index 0000000..0c47b31 --- /dev/null +++ b/base/admin/resources/partials/create-client.html @@ -0,0 +1,72 @@ +
    + + + + + +
    +
    +
    + + +
    + + +
    + +
    + + +
    +
    + +
    + +
    + +
    + {{:: 'client-id.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'client-protocol.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'root-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'master-saml-processing-url.tooltip' | translate}} +
    +
    +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/create-execution.html b/base/admin/resources/partials/create-execution.html new file mode 100755 index 0000000..d68dc9c --- /dev/null +++ b/base/admin/resources/partials/create-execution.html @@ -0,0 +1,31 @@ +
    + +
    +

    {{:: 'create-authenticator-execution' | translate}}

    +

    {{:: 'create-authenticator-execution' | translate}}

    +

    {{:: 'create-form-action-execution' | translate}}

    +
    + +
    +
    + +
    +
    + +
    +
    + {{provider.description}} +
    +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/create-flow-execution.html b/base/admin/resources/partials/create-flow-execution.html new file mode 100755 index 0000000..195a7ff --- /dev/null +++ b/base/admin/resources/partials/create-flow-execution.html @@ -0,0 +1,55 @@ +
    +

    {{:: 'create-execution-flow' | translate}}

    + + + +
    +
    + +
    + +
    + {{:: 'flow.alias.tooltip' | translate}} +
    +
    + + +
    + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'flow-type.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{provider.description}} +
    +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/create-flow.html b/base/admin/resources/partials/create-flow.html new file mode 100755 index 0000000..efd34e9 --- /dev/null +++ b/base/admin/resources/partials/create-flow.html @@ -0,0 +1,43 @@ +
    +

    {{:: 'create-top-level-form' | translate}}

    + + + +
    +
    + +
    + +
    + {{:: 'flow.alias.tooltip' | translate}} +
    +
    + + +
    + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'top-level-flow-type.tooltip' | translate}} +
    +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/create-group.html b/base/admin/resources/partials/create-group.html new file mode 100755 index 0000000..255cf32 --- /dev/null +++ b/base/admin/resources/partials/create-group.html @@ -0,0 +1,25 @@ +
    +
    +

    {{:: 'create-group' | translate}}

    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/default-groups.html b/base/admin/resources/partials/default-groups.html new file mode 100755 index 0000000..8d0866e --- /dev/null +++ b/base/admin/resources/partials/default-groups.html @@ -0,0 +1,91 @@ +
    + + +
    +
    + + +
    +
    +
    + + + + + + + + + + + +
    +
    + + {{:: 'default-groups.tooltip' | translate}} + +
    + +
    +
    +
    + +
    +
    +
    + + + + + + + + + + + +
    +
    +
    + + {{:: 'available-groups.tooltip' | translate}} +
    +
    +
    + +
    + +
    +
    +
    +   + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/defense-headers.html b/base/admin/resources/partials/defense-headers.html new file mode 100755 index 0000000..51acdf1 --- /dev/null +++ b/base/admin/resources/partials/defense-headers.html @@ -0,0 +1,71 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'x-frame-options-tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'content-sec-policy-tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'content-sec-policy-report-only-tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'content-type-options-tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'robots-tag-tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'x-xss-protection-tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'strict-transport-security-tooltip' | translate}} +
    +
    +
    +
    + + +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/forbidden.html b/base/admin/resources/partials/forbidden.html new file mode 100755 index 0000000..b40f2e7 --- /dev/null +++ b/base/admin/resources/partials/forbidden.html @@ -0,0 +1,7 @@ +
    +
    +

    Forbidden

    +

    You don't have access to the requested resource.

    + Go to the home page » +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/group-attributes.html b/base/admin/resources/partials/group-attributes.html new file mode 100755 index 0000000..e12c553 --- /dev/null +++ b/base/admin/resources/partials/group-attributes.html @@ -0,0 +1,41 @@ +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{key}}{{:: 'delete' | translate}}
    {{:: 'add' | translate}}
    + +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/group-detail.html b/base/admin/resources/partials/group-detail.html new file mode 100755 index 0000000..8fd6461 --- /dev/null +++ b/base/admin/resources/partials/group-detail.html @@ -0,0 +1,28 @@ +
    + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/group-list.html b/base/admin/resources/partials/group-list.html new file mode 100755 index 0000000..3412e6a --- /dev/null +++ b/base/admin/resources/partials/group-list.html @@ -0,0 +1,50 @@ +
    + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + + diff --git a/base/admin/resources/partials/group-members.html b/base/admin/resources/partials/group-members.html new file mode 100755 index 0000000..ffdf362 --- /dev/null +++ b/base/admin/resources/partials/group-members.html @@ -0,0 +1,48 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'username' | translate}}{{:: 'last-name' | translate}}{{:: 'first-name' | translate}}{{:: 'email' | translate}}
    +
    + + + +
    +
    {{user.username}}{{user.lastName}}{{user.firstName}}{{user.email}}{{:: 'edit' | translate}}
    {{:: 'no-group-members' | translate}}{{:: 'no-group-members' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/group-role-mappings.html b/base/admin/resources/partials/group-role-mappings.html new file mode 100755 index 0000000..fc3f6ee --- /dev/null +++ b/base/admin/resources/partials/group-role-mappings.html @@ -0,0 +1,111 @@ +
    + + + + +
    +
    + + +
    +
    +
    + + + + {{:: 'group.add-selected.tooltip' | translate}} +
    +
    + + {{:: 'group.assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'group.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    + +
    + +
    + + +
    +
    +
    +
    + + {{:: 'group.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'group.assigned-roles-client.tooltip' | translate}} + + +
    +
    + + {{:: 'group.effective-roles-client.tooltip' | translate}} + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/home.html b/base/admin/resources/partials/home.html new file mode 100755 index 0000000..dc2471b --- /dev/null +++ b/base/admin/resources/partials/home.html @@ -0,0 +1,4 @@ +
    +
    +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/identity-provider-mapper-detail.html b/base/admin/resources/partials/identity-provider-mapper-detail.html new file mode 100755 index 0000000..7e62b14 --- /dev/null +++ b/base/admin/resources/partials/identity-provider-mapper-detail.html @@ -0,0 +1,84 @@ +
    + + +

    {{mapper.name|capitalize}}

    +

    {{:: 'add-identity-provider-mapper' | translate}}

    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'mapper.name.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode-override.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{mapperType.helpText}} +
    +
    + +
    + +
    + {{mapperType.helpText}} +
    + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/identity-provider-mappers.html b/base/admin/resources/partials/identity-provider-mappers.html new file mode 100755 index 0000000..c7c136b --- /dev/null +++ b/base/admin/resources/partials/identity-provider-mappers.html @@ -0,0 +1,49 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    {{:: 'name' | translate}}{{:: 'category' | translate}}{{:: 'type' | translate}}
    {{mapper.name}}{{mapperTypes[mapper.identityProviderMapper].category}}{{mapperTypes[mapper.identityProviderMapper].name}}
    {{:: 'no-mappers-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/menu.html b/base/admin/resources/partials/menu.html new file mode 100755 index 0000000..dc41323 --- /dev/null +++ b/base/admin/resources/partials/menu.html @@ -0,0 +1,26 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/partials/modal/realm-events-admin-auth.html b/base/admin/resources/partials/modal/realm-events-admin-auth.html new file mode 100644 index 0000000..c8da93e --- /dev/null +++ b/base/admin/resources/partials/modal/realm-events-admin-auth.html @@ -0,0 +1,8 @@ +
    + + + + + +
    {{:: 'realm' | translate}}{{event.authDetails.realmId}}
    {{:: 'client' | translate}}{{event.authDetails.clientId}}
    {{:: 'user' | translate}}{{event.authDetails.userId}}
    {{:: 'ip-address' | translate}}{{event.authDetails.ipAddress}}
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/modal/realm-events-admin-representation.html b/base/admin/resources/partials/modal/realm-events-admin-representation.html new file mode 100644 index 0000000..837a164 --- /dev/null +++ b/base/admin/resources/partials/modal/realm-events-admin-representation.html @@ -0,0 +1,3 @@ +
    +
    
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/modal/role-selector.html b/base/admin/resources/partials/modal/role-selector.html new file mode 100755 index 0000000..b4dd43f --- /dev/null +++ b/base/admin/resources/partials/modal/role-selector.html @@ -0,0 +1,39 @@ + +
    +
    +
    + + {{:: 'realm-roles.tooltip' | translate}} + + +
    +
    + + + + + +
    +
    +
    diff --git a/base/admin/resources/partials/modal/unregistered-required-action-selector.html b/base/admin/resources/partials/modal/unregistered-required-action-selector.html new file mode 100755 index 0000000..bc9995f --- /dev/null +++ b/base/admin/resources/partials/modal/unregistered-required-action-selector.html @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/partials/modal/user-credential-data.html b/base/admin/resources/partials/modal/user-credential-data.html new file mode 100644 index 0000000..342b3f6 --- /dev/null +++ b/base/admin/resources/partials/modal/user-credential-data.html @@ -0,0 +1,8 @@ +
    + + + + + +
    {{key}}{{credentialData[key]}}
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/modal/view-key.html b/base/admin/resources/partials/modal/view-key.html new file mode 100644 index 0000000..5d53d3f --- /dev/null +++ b/base/admin/resources/partials/modal/view-key.html @@ -0,0 +1,18 @@ + + +
    {{key}}
    \ No newline at end of file diff --git a/base/admin/resources/partials/modal/view-object.html b/base/admin/resources/partials/modal/view-object.html new file mode 100644 index 0000000..ee50aee --- /dev/null +++ b/base/admin/resources/partials/modal/view-object.html @@ -0,0 +1,3 @@ +
    +
    
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/notfound.html b/base/admin/resources/partials/notfound.html new file mode 100755 index 0000000..62a1a5b --- /dev/null +++ b/base/admin/resources/partials/notfound.html @@ -0,0 +1,7 @@ +
    +
    +

    +

    {{:: 'resource-not-found.instruction' | translate}}

    + +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/otp-policy.html b/base/admin/resources/partials/otp-policy.html new file mode 100755 index 0000000..0fdb946 --- /dev/null +++ b/base/admin/resources/partials/otp-policy.html @@ -0,0 +1,88 @@ +
    +

    {{:: 'authentication' | translate}}

    + + + +
    +
    + +
    +
    + +
    +
    + {{:: 'otp-type.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'otp-hash-algorithm.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'otp.number-of-digits.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'otp.look-ahead-window.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'otp.initial-counter.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'otp-token-period.tooltip' | translate}} +
    + +
    + +
    + {{realm.otpSupportedApplications.join(', ')}} +
    + {{:: 'otp-supported-applications.tooltip' | translate}} +
    + + +
    +
    + + +
    +
    +
    + +
    + + + diff --git a/base/admin/resources/partials/pagenotfound.html b/base/admin/resources/partials/pagenotfound.html new file mode 100755 index 0000000..c15a051 --- /dev/null +++ b/base/admin/resources/partials/pagenotfound.html @@ -0,0 +1,7 @@ +
    +
    +

    +

    {{:: 'page-not-found.instruction' | translate}}

    + +
    +
    \ No newline at end of file diff --git a/base/admin/resources/partials/partial-export.html b/base/admin/resources/partials/partial-export.html new file mode 100644 index 0000000..1ceed8c --- /dev/null +++ b/base/admin/resources/partials/partial-export.html @@ -0,0 +1,34 @@ +
    + +

    + {{:: 'partial-export' | translate}} + {{:: 'partial-export.tooltip' | translate}} +

    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/partial-import.html b/base/admin/resources/partials/partial-import.html new file mode 100644 index 0000000..4df1207 --- /dev/null +++ b/base/admin/resources/partials/partial-import.html @@ -0,0 +1,130 @@ +
    + +

    + {{:: 'partial-import' | translate}} + {{:: 'partial-import.tooltip' | translate}} +

    + +
    +
    +
    + + +
    + + +
    + +
    + + +
    +
    + +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + +
    +
    + {{:: 'if-resource-exists.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + +
    + {{successMessage()}} + + + + + + + + + + + + + + + + + + + +
    {{:: 'action' | translate}}{{:: 'type' | translate}}{{:: 'name' | translate}}{{:: 'id' | translate}}
    {{result.action}}{{result.action}}{{result.action}}{{result.resourceType}}{{result.resourceName}}{{result.id}}
    + +
    + + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/password-policy.html b/base/admin/resources/partials/password-policy.html new file mode 100755 index 0000000..f211dba --- /dev/null +++ b/base/admin/resources/partials/password-policy.html @@ -0,0 +1,51 @@ +
    +

    {{:: 'authentication' | translate}}

    + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    {{:: 'policy-type' | translate}}{{:: 'policy-value' | translate}}{{:: 'actions' | translate}}
    {{p.displayName}} + + {{:: 'delete' | translate}}
    + +
    +
    + + +
    +
    +
    + +
    + + + diff --git a/base/admin/resources/partials/protocol-mapper-detail.html b/base/admin/resources/partials/protocol-mapper-detail.html new file mode 100755 index 0000000..e555aaf --- /dev/null +++ b/base/admin/resources/partials/protocol-mapper-detail.html @@ -0,0 +1,64 @@ +

    {{:: 'create-protocol-mapper' | translate}}

    +

    {{model.mapper.name|capitalize}}

    + +
    + +
    +
    + +
    + +
    + {{:: 'protocol.tooltip' | translate}} +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'mapper.name.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{model.mapperType.helpText}} +
    +
    + +
    + +
    + {{model.mapperType.helpText}} +
    + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    diff --git a/base/admin/resources/partials/realm-cache-settings.html b/base/admin/resources/partials/realm-cache-settings.html new file mode 100755 index 0000000..53a7987 --- /dev/null +++ b/base/admin/resources/partials/realm-cache-settings.html @@ -0,0 +1,30 @@ +
    + + +
    +
    + +
    + +
    + + {{:: 'realm-cache-clear.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'user-cache-clear.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'keys-cache-clear.tooltip' | translate}} +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-create.html b/base/admin/resources/partials/realm-create.html new file mode 100755 index 0000000..f156c3b --- /dev/null +++ b/base/admin/resources/partials/realm-create.html @@ -0,0 +1,45 @@ +
    + +

    {{:: 'add-realm' | translate}}

    + +
    +
    +
    + + +
    + + +
    + +
    + + +
    +
    + +
    + + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-default-roles.html b/base/admin/resources/partials/realm-default-roles.html new file mode 100755 index 0000000..41cb09b --- /dev/null +++ b/base/admin/resources/partials/realm-default-roles.html @@ -0,0 +1,88 @@ +
    +

    {{:: 'roles' | translate}}

    + + +
    +
    + + +
    +
    +
    + + {{:: 'default.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'realm-default-roles.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + +
    + + +
    +
    +
    +
    + + {{:: 'default.available-roles-client.tooltip' | translate}} + + +
    +
    + + {{:: 'client-default-roles.tooltip' | translate}} + + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-detail.html b/base/admin/resources/partials/realm-detail.html new file mode 100755 index 0000000..6719a98 --- /dev/null +++ b/base/admin/resources/partials/realm-detail.html @@ -0,0 +1,82 @@ +
    + + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    + {{:: 'realm-detail.frontendUrl.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'realm-detail.hostname.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'realm-detail.enabled.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'realm-detail.userManagedAccess.tooltip' | translate}} +
    + +
    + + + {{:: 'realm-detail.protocol-endpoints.tooltip' | translate}} +
    + +
    +
    + + +
    + +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-events-admin.html b/base/admin/resources/partials/realm-events-admin.html new file mode 100755 index 0000000..d727976 --- /dev/null +++ b/base/admin/resources/partials/realm-events-admin.html @@ -0,0 +1,134 @@ +
    +

    + {{:: 'admin-events' | translate}} + {{:: 'admin-events.tooltip' | translate}} +

    + + +

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + + {{:: 'resource-path.tooltip' | translate}} +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + {{:: 'authentication-details' | translate}} + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    {{:: 'time' | translate}}{{:: 'operation-type' | translate}}{{:: 'resource-type' | translate}}{{:: 'resource-path' | translate}}{{:: 'details' | translate}}
    + + + +
    {{event.time|date:('dateFormat' | translate)}}
    {{event.time|date:('timeFormat' | translate)}}
    {{event.operationType}}{{event.resourceType}}{{event.resourcePath}} + + +
    +
    +
    + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-events-config.html b/base/admin/resources/partials/realm-events-config.html new file mode 100755 index 0000000..b47d6e1 --- /dev/null +++ b/base/admin/resources/partials/realm-events-config.html @@ -0,0 +1,106 @@ +
    +

    + {{:: 'events-config' | translate}} + {{:: 'events-config.tooltip' | translate}} +

    + + +
    +

    {{:: 'events-config' | translate}}

    + +
    + +
    +
    + + {{:: 'event-listeners.tooltip' | translate}} +
    + +
    +
    +
    + +
    + {{:: 'login-events-settings' | translate}} + +
    + + {{:: 'login.save-events.tooltip' | translate}} +
    + +
    +
    + +
    + + {{:: 'saved-types.tooltip' | translate}} +
    + +
    +
    + +
    + + {{:: 'clear-events.tooltip' | translate}} +
    + +
    +
    +
    + + {{:: 'events.expiration.tooltip' | translate}} +
    + + +
    +
    +
    + + +
    + {{:: 'admin-events-settings' | translate}} + +
    + + {{:: 'admin.save-events.tooltip' | translate}} +
    + +
    +
    + +
    + + {{:: 'include-representation.tooltip' | translate}} +
    + +
    +
    + +
    + + {{:: 'clear-admin-events.tooltip' | translate}} +
    + +
    +
    + +
    + +
    +
    + + +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-events.html b/base/admin/resources/partials/realm-events.html new file mode 100755 index 0000000..91c7aab --- /dev/null +++ b/base/admin/resources/partials/realm-events.html @@ -0,0 +1,124 @@ +
    +

    + {{:: 'events' | translate}} + {{:: 'events.tooltip' | translate}} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    {{:: 'time' | translate}}{{:: 'event-type' | translate}}{{:: 'details' | translate}}
    + + + +
    {{event.time|date:('dateFormat' | translate)}}
    {{event.time|date:('timeFormat' | translate)}}
    {{event.type}} + + + + + + + + + + + + + +
    {{:: 'client' | translate}}{{event.clientId}}
    {{:: 'user' | translate}}{{event.userId}}
    {{:: 'ip-address' | translate}}{{event.ipAddress}}
    {{:: 'error' | translate}}{{event.error}}
    {{:: 'details' | translate}} + + + + + + +
    {{key}}{{value}}
    +
    {{:: 'representation' | translate}} + +
    {{event.representation}}
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-bitbucket.html b/base/admin/resources/partials/realm-identity-provider-bitbucket.html new file mode 100755 index 0000000..fab751e --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-bitbucket.html @@ -0,0 +1,142 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'bitbucket.key.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'bitbucket.secret.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'bitbucket.default-scopes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'link-only.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-facebook-ext.html b/base/admin/resources/partials/realm-identity-provider-facebook-ext.html new file mode 100755 index 0000000..c8f111a --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-facebook-ext.html @@ -0,0 +1,7 @@ +
    + +
    + +
    + {{:: 'identity-provider.facebook-fetchedFields.tooltip' | translate}} +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-facebook.html b/base/admin/resources/partials/realm-identity-provider-facebook.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-facebook.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-github-ext.html b/base/admin/resources/partials/realm-identity-provider-github-ext.html new file mode 100755 index 0000000..e69de29 diff --git a/base/admin/resources/partials/realm-identity-provider-github.html b/base/admin/resources/partials/realm-identity-provider-github.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-github.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-gitlab.html b/base/admin/resources/partials/realm-identity-provider-gitlab.html new file mode 100755 index 0000000..00c0e94 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-gitlab.html @@ -0,0 +1,142 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'gitlab.application-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gitlab.application-secret.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gitlab.default-scopes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'link-only.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-google-ext.html b/base/admin/resources/partials/realm-identity-provider-google-ext.html new file mode 100755 index 0000000..4f313a8 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-google-ext.html @@ -0,0 +1,21 @@ +
    + +
    + +
    + {{:: 'identity-provider.google-hostedDomain.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.google-userIp.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.google-offlineAccess.tooltip' | translate}} +
    diff --git a/base/admin/resources/partials/realm-identity-provider-google.html b/base/admin/resources/partials/realm-identity-provider-google.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-google.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-instagram-ext.html b/base/admin/resources/partials/realm-identity-provider-instagram-ext.html new file mode 100644 index 0000000..e69de29 diff --git a/base/admin/resources/partials/realm-identity-provider-instagram.html b/base/admin/resources/partials/realm-identity-provider-instagram.html new file mode 100644 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-instagram.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html b/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html new file mode 100755 index 0000000..d380749 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-linkedin-ext.html b/base/admin/resources/partials/realm-identity-provider-linkedin-ext.html new file mode 100755 index 0000000..e69de29 diff --git a/base/admin/resources/partials/realm-identity-provider-linkedin.html b/base/admin/resources/partials/realm-identity-provider-linkedin.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-linkedin.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-microsoft-ext.html b/base/admin/resources/partials/realm-identity-provider-microsoft-ext.html new file mode 100755 index 0000000..e69de29 diff --git a/base/admin/resources/partials/realm-identity-provider-microsoft.html b/base/admin/resources/partials/realm-identity-provider-microsoft.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-microsoft.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-oidc.html b/base/admin/resources/partials/realm-identity-provider-oidc.html new file mode 100755 index 0000000..e9b28e2 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -0,0 +1,388 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'identity-provider.alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'link-only.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    +
    + {{:: 'openid-connect-config' | translate}} {{:: 'openid-connect-config.tooltip' | translate}} +
    + +
    + +
    + {{:: 'authorization-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'loginHint.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'uiLocales.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'token-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.logout-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'backchannel-logout.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.disableUserInfo.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'user-info-url.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'client-auth.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.client-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-secret.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'client-assertion-signing-algorithm.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'issuer.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.default-scopes.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'prompt.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'accepts-prompt-none-forward-from-client.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.validate-signatures.tooltip' | translate}} +
    + +
    + +
    + +
    + +
    + {{:: 'identity-provider.use-jwks-url.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'identity-provider.jwks-url.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'identity-provider.validating-public-key.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'identity-provider.validating-public-key-id.tooltip' | translate}} +
    + +
    + +
    + +
    + +
    + {{:: 'pkce-enabled.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'pkce-method.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.forwarded-query-parameters.tooltip' | translate}} +
    +
    +
    + {{:: 'import-external-idp-config' | translate}} {{:: 'import-external-idp-config.tooltip' | translate}} +
    + +
    + +
    + {{:: 'identity-provider.import-from-url.tooltip' | translate}} +
    +
    + +
    + +
    +
    +
    + + {{:: 'identity-provider.import-from-file.tooltip' | translate}} +
    +
    + + +
    + + {{files[0].name}} + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + + diff --git a/base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html b/base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html new file mode 100644 index 0000000..b1c27de --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-openshift-v3-ext.html @@ -0,0 +1,7 @@ +
    + +
    + +
    + {{:: 'openshift.base-url.tooltip' | translate}} +
    diff --git a/base/admin/resources/partials/realm-identity-provider-openshift-v3.html b/base/admin/resources/partials/realm-identity-provider-openshift-v3.html new file mode 100755 index 0000000..eb42ba1 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-openshift-v3.html @@ -0,0 +1,164 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'identity-provider.alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'social.client-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'social.client-secret.tooltip' | translate}} +
    +
    +
    + +
    + +
    + {{:: 'social.default-scopes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.disableUserInfo.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'link-only.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-openshift-v4-ext.html b/base/admin/resources/partials/realm-identity-provider-openshift-v4-ext.html new file mode 100644 index 0000000..46254be --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-openshift-v4-ext.html @@ -0,0 +1,7 @@ +
    + +
    + +
    + {{:: 'openshift4.base-url.tooltip' | translate}} +
    diff --git a/base/admin/resources/partials/realm-identity-provider-openshift-v4.html b/base/admin/resources/partials/realm-identity-provider-openshift-v4.html new file mode 100755 index 0000000..9e14154 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-openshift-v4.html @@ -0,0 +1,164 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'identity-provider.alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'social.client-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'social.client-secret.tooltip' | translate}} +
    +
    +
    + +
    + +
    + {{:: 'social.default-scopes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.disableUserInfo.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'linkOnly.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-paypal-ext.html b/base/admin/resources/partials/realm-identity-provider-paypal-ext.html new file mode 100644 index 0000000..692a078 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-paypal-ext.html @@ -0,0 +1,7 @@ +
    + +
    + +
    + {{:: 'identity-provider.paypal-sandbox.tooltip' | translate}} +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-paypal.html b/base/admin/resources/partials/realm-identity-provider-paypal.html new file mode 100644 index 0000000..62e97ba --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-paypal.html @@ -0,0 +1 @@ +
    diff --git a/base/admin/resources/partials/realm-identity-provider-saml.html b/base/admin/resources/partials/realm-identity-provider-saml.html new file mode 100755 index 0000000..fb1a38d --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-saml.html @@ -0,0 +1,414 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'identity-provider.alias.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'link-only.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    + + + {{:: 'identity-provider.saml.protocol-endpoints.saml.tooltip' | translate}} +
    +
    +
    + {{:: 'saml-config' | translate}} {{:: 'identity-provider.saml-config.tooltip' | translate}} + +
    + +
    + +
    + {{:: 'identity-provider.saml.entity-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.single-signon-service-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.single-logout-service-url.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'backchannel-logout.tooltip' | translate}} +
    +
    + +
    + + +
    + {{:: 'nameid-policy-format.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.principal-type.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.principal-attribute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.allow-create.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'http-post-binding-response.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'http-post-binding-for-authn-request.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'http-post-binding-logout.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'want-authn-requests-signed.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'want-assertions-signed.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'want-assertions-encrypted.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'signature-algorithm.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'saml-signature-keyName-transformer.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.force-authentication.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.validate-signature.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'validating-x509-certificate.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.saml.sign-sp-metadata.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'saml.loginHint.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.allowed-clock-skew.tooltip' | translate}} +
    +
    +
    + {{:: 'identity-provider.saml.requested-authncontext' | translate}} {{:: 'identity-provider.saml.requested-authncontext.tooltip' | translate}} + +
    + +
    +
    + +
    +
    + {{:: 'identity-provider.saml.authncontext-comparison-type.tooltip' | translate}} +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + {{:: 'identity-provider.saml.authncontext-class-ref.tooltip' | translate}} +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + {{:: 'identity-provider.saml.authncontext-decl-ref.tooltip' | translate}} +
    +
    +
    + {{:: 'import-external-idp-config' | translate}} {{:: 'import-external-idp-config.tooltip' | translate}} +
    + +
    + +
    + {{:: 'saml.import-from-url.tooltip' | translate}} +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    + + {{files[0].name}} + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/realm-identity-provider-social.html b/base/admin/resources/partials/realm-identity-provider-social.html new file mode 100755 index 0000000..b745634 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-social.html @@ -0,0 +1,157 @@ +
    + + + + +
    +
    +
    + +
    + +
    + {{:: 'redirect-uri.tooltip' | translate}} +
    +
    +
    +
    + +
    + +
    + {{:: 'social.client-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'social.client-secret.tooltip' | translate}} +
    +
    +
    + +
    + +
    + {{:: 'social.default-scopes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.store-tokens.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.stored-tokens-readable.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'accepts-prompt-none-forward-from-client.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider.disableUserInfo.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'link-only.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'hide-on-login-page.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'gui-order.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'first-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'post-broker-login-flow.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sync-mode.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html b/base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html new file mode 100755 index 0000000..5e478f6 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-stackoverflow-ext.html @@ -0,0 +1,7 @@ +
    + +
    + +
    + {{:: 'stackoverflow.key.tooltip' | translate}} +
    diff --git a/base/admin/resources/partials/realm-identity-provider-stackoverflow.html b/base/admin/resources/partials/realm-identity-provider-stackoverflow.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-stackoverflow.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider-twitter-ext.html b/base/admin/resources/partials/realm-identity-provider-twitter-ext.html new file mode 100755 index 0000000..e69de29 diff --git a/base/admin/resources/partials/realm-identity-provider-twitter.html b/base/admin/resources/partials/realm-identity-provider-twitter.html new file mode 100755 index 0000000..a4630ac --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider-twitter.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/base/admin/resources/partials/realm-identity-provider.html b/base/admin/resources/partials/realm-identity-provider.html new file mode 100755 index 0000000..3422752 --- /dev/null +++ b/base/admin/resources/partials/realm-identity-provider.html @@ -0,0 +1,81 @@ +
    +

    {{:: 'identity-providers' | translate}}

    +
    +
    + +
    +

    + {{:: 'identity-providers' | translate}} +

    +

    + Through Identity Brokering it's easy to allow users to authenticate to Keycloak using external Identity Providers or Social Networks.
    We have built-in support for OpenID Connect and SAML 2.0 as well as a number of social networks such as Google, GitHub, Facebook and Twitter. +

    +

    To get started select a provider from the dropdown below:

    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'name' | translate}}{{:: 'provider' | translate}}{{:: 'enabled' | translate}}{{:: 'hidden' | translate}}{{:: 'link-only-column' | translate}}{{:: 'gui-order' | translate}}{{:: 'actions' | translate}}
    + + {{identityProvider.displayName}} + {{identityProvider.provider.name}} + {{identityProvider.alias}} + + {{identityProvider.providerId}}{{identityProvider.config.guiOrder}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-keys-disabled.html b/base/admin/resources/partials/realm-keys-disabled.html new file mode 100755 index 0000000..242c841 --- /dev/null +++ b/base/admin/resources/partials/realm-keys-disabled.html @@ -0,0 +1,70 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {{:: 'algorithm' | translate}}{{:: 'type' | translate}}{{:: 'kid' | translate}}{{:: 'priority' | translate}}{{:: 'provider' | translate}}{{:: 'publicKeys' | translate}}
    {{key.algorithm}}{{key.type}}{{key.kid}}{{key.providerPriority}}{{key.provider.name}}{{:: 'publicKey' | translate}}{{:: 'certificate' | translate}}
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-keys-generic.html b/base/admin/resources/partials/realm-keys-generic.html new file mode 100755 index 0000000..dd2017a --- /dev/null +++ b/base/admin/resources/partials/realm-keys-generic.html @@ -0,0 +1,69 @@ + + +
    + + + + + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'console-display-name.tooltip' | translate}} +
    + + +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-keys-passive.html b/base/admin/resources/partials/realm-keys-passive.html new file mode 100755 index 0000000..65f934a --- /dev/null +++ b/base/admin/resources/partials/realm-keys-passive.html @@ -0,0 +1,70 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {{:: 'algorithm' | translate}}{{:: 'type' | translate}}{{:: 'kid' | translate}}{{:: 'priority' | translate}}{{:: 'provider' | translate}}{{:: 'publicKeys' | translate}}
    {{key.algorithm}}{{key.type}}{{key.kid}}{{key.providerPriority}}{{key.provider.name}}{{:: 'publicKey' | translate}}{{:: 'certificate' | translate}}
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-keys-providers.html b/base/admin/resources/partials/realm-keys-providers.html new file mode 100755 index 0000000..c303a38 --- /dev/null +++ b/base/admin/resources/partials/realm-keys-providers.html @@ -0,0 +1,75 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    {{:: 'name' | translate}}{{:: 'provider' | translate}}{{:: 'providerHelpText' | translate}}{{:: 'priority' | translate}}{{:: 'actions' | translate}}
    {{instance.name}}{{instance.providerId}}{{instance.provider.helpText}}{{instance.config['priority'][0]}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    + +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-keys.html b/base/admin/resources/partials/realm-keys.html new file mode 100755 index 0000000..3f73682 --- /dev/null +++ b/base/admin/resources/partials/realm-keys.html @@ -0,0 +1,71 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {{:: 'algorithm' | translate}}{{:: 'type' | translate}}{{:: 'kid' | translate}}{{:: 'priority' | translate}}{{:: 'provider' | translate}}{{:: 'publicKeys' | translate}}
    {{key.algorithm}}{{key.type}}{{key.kid}}{{key.providerPriority}}{{key.provider.name}}{{:: 'publicKey' | translate}}{{:: 'certificate' | translate}}
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-list.html b/base/admin/resources/partials/realm-list.html new file mode 100755 index 0000000..099fe6d --- /dev/null +++ b/base/admin/resources/partials/realm-list.html @@ -0,0 +1,20 @@ +
    + +

    {{:: 'realms' | translate}}

    + + + + + + + + + + + + +
    {{:: 'realm' | translate}}
    {{r.realm}}
    +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-localization-detail.html b/base/admin/resources/partials/realm-localization-detail.html new file mode 100644 index 0000000..c790256 --- /dev/null +++ b/base/admin/resources/partials/realm-localization-detail.html @@ -0,0 +1,50 @@ +
    + + + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-localization-upload.html b/base/admin/resources/partials/realm-localization-upload.html new file mode 100644 index 0000000..a15352c --- /dev/null +++ b/base/admin/resources/partials/realm-localization-upload.html @@ -0,0 +1,37 @@ +
    + + + + +
    +
    + +
    + +
    +
    +
    + +
    +
    + + +
    + + {{files[0].name}} + +
    +
    +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-localization.html b/base/admin/resources/partials/realm-localization.html new file mode 100644 index 0000000..b282fa2 --- /dev/null +++ b/base/admin/resources/partials/realm-localization.html @@ -0,0 +1,61 @@ +
    + + + + +
    +
    + +
    + +
    +
    + {{:: 'no-localizations-configured' | translate}} +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{key}}{{value}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-login-settings.html b/base/admin/resources/partials/realm-login-settings.html new file mode 100755 index 0000000..c5a084a --- /dev/null +++ b/base/admin/resources/partials/realm-login-settings.html @@ -0,0 +1,87 @@ +
    + + +
    +
    +
    + +
    + +
    + {{:: 'registrationAllowed.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'registrationEmailAsUsername.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'editUsernameAllowed.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'resetPasswordAllowed.tooltip' |translate}} +
    +
    + +
    + +
    + {{:: 'rememberMe.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'verifyEmail.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'loginWithEmailAllowed.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'duplicateEmailsAllowed.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'sslRequired.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-role-users.html b/base/admin/resources/partials/realm-role-users.html new file mode 100644 index 0000000..11cbbc6 --- /dev/null +++ b/base/admin/resources/partials/realm-role-users.html @@ -0,0 +1,50 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'username' | translate}}{{:: 'last-name' | translate}}{{:: 'first-name' | translate}}{{:: 'email' | translate}}
    +
    + + + +
    +
    {{user.username}}{{user.lastName}}{{user.firstName}}{{user.email}}{{:: 'edit' | translate}}
    {{:: 'no-role-members' | translate}}{{:: 'no-role-members' | translate}}
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-smtp.html b/base/admin/resources/partials/realm-smtp.html new file mode 100755 index 0000000..7533071 --- /dev/null +++ b/base/admin/resources/partials/realm-smtp.html @@ -0,0 +1,96 @@ +
    + + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'fromDisplayName.tooltip' | translate}} +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'replyToDisplayName.tooltip' | translate}} +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'envelopeFrom.tooltip' | translate}} +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'smtp-password.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-theme-settings.html b/base/admin/resources/partials/realm-theme-settings.html new file mode 100755 index 0000000..be74a2a --- /dev/null +++ b/base/admin/resources/partials/realm-theme-settings.html @@ -0,0 +1,97 @@ +
    + + +
    +
    +
    + +
    +
    + +
    +
    + {{:: 'login-theme.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{ 'account-theme.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'select-theme-admin-console' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'select-theme-email' | translate}} +
    +
    + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/base/admin/resources/partials/realm-tokens.html b/base/admin/resources/partials/realm-tokens.html new file mode 100755 index 0000000..c85c68f --- /dev/null +++ b/base/admin/resources/partials/realm-tokens.html @@ -0,0 +1,385 @@ +
    + + +
    + +
    + + +
    + +
    + + {{:: 'default-signature-algorithm.tooltip' | translate}} + +
    + +
    + + +
    + +
    + + {{:: 'revoke-refresh-token.tooltip' | translate}} + +
    + +
    + + +
    + +
    + + {{:: 'refresh-token-max-reuse.tooltip' | translate}} + +
    + +
    + + +
    + + +
    + {{:: 'sso-session-idle.tooltip' | translate}} + +
    + +
    + + +
    + + +
    + {{:: 'sso-session-max.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'sso-session-idle-remember-me.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'sso-session-max-remember-me.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'offline-session-idle.tooltip' | translate}} +
    + + +
    + +
    + +
    + {{:: 'offline-session-max-limited.tooltip' | translate}} +
    +
    + +
    + + +
    + {{:: 'offline-session-max.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-offline-session-idle.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'client-offline-session-max.tooltip' | translate}} +
    + +
    + +
    + + +
    + {{:: 'client-session-idle.tooltip' | translate}} +
    + +
    + +
    + + +
    + {{:: 'client-session-max.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'access-token-lifespan.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'access-token-lifespan-for-implicit-flow.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'client-login-timeout.tooltip' | translate}} +
    + +
    + + +
    + + +
    + {{:: 'login-timeout.tooltip' | translate}} +
    + +
    + + +
    + + +
    + + {{:: 'login-action-timeout.tooltip' | translate}} + +
    + +
    + + +
    + + +
    + + {{:: 'action-token-generated-by-user-lifespan.tooltip' | translate}} + +
    + +
    + + +
    + + +
    + + {{:: 'action-token-generated-by-admin-lifespan.tooltip' | translate}} + +
    + +
    + +
    + + + + +
    + + {{:: 'action-token-generated-by-user.tooltip' | translate}} + +
    + +
    + + +
    + + +
    + {{:: 'oauth2-device-code-lifespan.tooltip' | translate}} +
    + +
    + + +
    + +
    + {{:: 'oauth2-device-polling-interval.tooltip' | translate}} +
    + +
    +
    + + +
    +
    + +
    + +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/required-actions.html b/base/admin/resources/partials/required-actions.html new file mode 100755 index 0000000..ab16456 --- /dev/null +++ b/base/admin/resources/partials/required-actions.html @@ -0,0 +1,38 @@ +
    +

    {{:: 'authentication' | translate}}

    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    {{:: 'required-action' | translate}}{{:: 'enabled' | translate}}{{:: 'default-action' | translate}}
    + + + {{requiredAction.name}} +
    {{:: 'no-required-actions-configured' | translate}}
    + +
    + + diff --git a/base/admin/resources/partials/role-attributes.html b/base/admin/resources/partials/role-attributes.html new file mode 100755 index 0000000..b9cbbd7 --- /dev/null +++ b/base/admin/resources/partials/role-attributes.html @@ -0,0 +1,41 @@ +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{key}}{{:: 'delete' | translate}}
    {{:: 'add' | translate}}
    + +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/role-detail.html b/base/admin/resources/partials/role-detail.html new file mode 100755 index 0000000..7595f65 --- /dev/null +++ b/base/admin/resources/partials/role-detail.html @@ -0,0 +1,135 @@ +
    + + + + + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + +
    + +
    + {{:: 'composite-roles.tooltip' | translate}} +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    + {{:: 'composite-roles' | translate}} +
    + + +
    +
    +
    + + {{:: 'composite.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'composite.associated-roles.tooltip' | translate}} + + +
    +
    +
    +
    + +
    + +
    + + +
    + + +
    +
    +
    + + {{:: 'composite.available-roles-client.tooltip' | translate}} + + +
    +
    + + {{:: 'composite.associated-roles-client.tooltip' | translate}} + + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/role-list.html b/base/admin/resources/partials/role-list.html new file mode 100755 index 0000000..6329ccc --- /dev/null +++ b/base/admin/resources/partials/role-list.html @@ -0,0 +1,63 @@ +
    +

    Roles

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    {{:: 'role-name' | translate}}{{:: 'composite' | translate}}{{:: 'description' | translate}}{{:: 'actions' | translate}}
    {{role.name}}{{role.description}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-results' | translate}}{{:: 'no-realm-roles-available' | translate}}
    +
    + + + +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/role-mappings.html b/base/admin/resources/partials/role-mappings.html new file mode 100755 index 0000000..184a849 --- /dev/null +++ b/base/admin/resources/partials/role-mappings.html @@ -0,0 +1,119 @@ +
    + + + + +
    +
    + + +
    +
    +
    + + + + {{:: 'user.add-selected.tooltip' | translate}} +
    +
    + + {{:: 'user.assigned-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'user.effective-roles.tooltip' | translate}} + +
    +
    +
    +
    + +
    + +
    + + +
    + + +
    +
    +
    + + {{:: 'user.available-roles.tooltip' | translate}} + + +
    +
    + + {{:: 'user.assigned-roles-client.tooltip' | translate}} + + +
    +
    + + {{:: 'user.effective-roles-client.tooltip' | translate}} + +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/server-info-providers.html b/base/admin/resources/partials/server-info-providers.html new file mode 100755 index 0000000..be741c1 --- /dev/null +++ b/base/admin/resources/partials/server-info-providers.html @@ -0,0 +1,55 @@ +
    +

    + {{:: 'server-info' | translate}} + +

    + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    {{:: 'spi' | translate}}{{:: 'providers' | translate}}
    {{spi.name}} +
    + {{providerName}} + + + + + + + +
    {{key}}{{value}}
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/server-info.html b/base/admin/resources/partials/server-info.html new file mode 100755 index 0000000..6d24419 --- /dev/null +++ b/base/admin/resources/partials/server-info.html @@ -0,0 +1,135 @@ +
    +

    + {{:: 'server-info' | translate}} + +

    + + + + + + + + + + + + + + + + +
    {{:: 'server-version' | translate}}{{serverInfo.systemInfo.version}}
    {{:: 'server-time' | translate}}{{serverInfo.systemInfo.serverTime}}
    {{:: 'server-uptime' | translate}}{{serverInfo.systemInfo.uptime}}
    + + + {{:: 'profile' | translate}} + + + + + + + + + + + + + + + + + + +
    {{:: 'server-profile' | translate}}{{serverInfo.profileInfo.name | capitalize}}
    + {{:: 'server-disabled' | translate}} + {{:: 'server-disabled.tooltip' | translate}} + {{serverInfo.profileInfo.disabledFeatures.sort().join(', ')}}
    + {{:: 'server-preview' | translate}} + {{:: 'server-preview.tooltip' | translate}} + {{serverInfo.profileInfo.previewFeatures.sort().join(', ')}}
    + {{:: 'server-experimental' | translate}} + {{:: 'server-experimental.tooltip' | translate}} + {{serverInfo.profileInfo.experimentalFeatures.sort().join(', ')}}
    + +
    + {{:: 'memory' | translate}} + + + + + + + + + + + + + +
    {{:: 'total-memory' | translate}}{{serverInfo.memoryInfo.totalFormated}}
    {{:: 'free-memory' | translate}}{{serverInfo.memoryInfo.freeFormated}} ({{serverInfo.memoryInfo.freePercentage}}%)
    {{:: 'used-memory' | translate}}{{serverInfo.memoryInfo.usedFormated}}
    +
    + +
    + {{:: 'system' | translate}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'current-working-directory' | translate}}{{serverInfo.systemInfo.userDir}}
    {{:: 'java-version' | translate}}{{serverInfo.systemInfo.javaVersion}}
    {{:: 'java-vendor' | translate}}{{serverInfo.systemInfo.javaVendor}}
    {{:: 'java-runtime' | translate}}{{serverInfo.systemInfo.javaRuntime}}
    {{:: 'java-vm' | translate}}{{serverInfo.systemInfo.javaVm}}
    {{:: 'java-vm-version' | translate}}{{serverInfo.systemInfo.javaVmVersion}}
    {{:: 'java-home' | translate}}{{serverInfo.systemInfo.javaHome}}
    {{:: 'user-name' | translate}}{{serverInfo.systemInfo.userName}}
    {{:: 'user-timezone' | translate}}{{serverInfo.systemInfo.userTimezone}}
    {{:: 'user-locale' | translate}}{{serverInfo.systemInfo.userLocale}}
    {{:: 'system-encoding' | translate}}{{serverInfo.systemInfo.fileEncoding}}
    {{:: 'operating-system' | translate}}{{serverInfo.systemInfo.osName}} {{serverInfo.systemInfo.osVersion}}
    {{:: 'os-architecture' | translate}}{{serverInfo.systemInfo.osArchitecture}}
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/session-realm.html b/base/admin/resources/partials/session-realm.html new file mode 100755 index 0000000..98b2284 --- /dev/null +++ b/base/admin/resources/partials/session-realm.html @@ -0,0 +1,34 @@ +
    +

    {{:: 'sessions' | translate}}

    + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'client' | translate}}{{:: 'active-sessions' | translate}}{{:: 'offline-sessions' | translate}}
    {{data.clientId}}{{data.active}}{{data.offline}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/session-revocation.html b/base/admin/resources/partials/session-revocation.html new file mode 100755 index 0000000..03c6864 --- /dev/null +++ b/base/admin/resources/partials/session-revocation.html @@ -0,0 +1,30 @@ +
    +

    {{:: 'sessions' | translate}}

    + + + +
    +
    +
    + +
    + +
    + {{:: 'not-before.tooltip' | translate}} +
    +
    + +
    +
    + + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/user-attributes.html b/base/admin/resources/partials/user-attributes.html new file mode 100755 index 0000000..10e3d8f --- /dev/null +++ b/base/admin/resources/partials/user-attributes.html @@ -0,0 +1,41 @@ +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{key}}{{:: 'delete' | translate}}
    {{:: 'add' | translate}}
    + +
    +
    + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/user-consents.html b/base/admin/resources/partials/user-consents.html new file mode 100644 index 0000000..76eb0a8 --- /dev/null +++ b/base/admin/resources/partials/user-consents.html @@ -0,0 +1,41 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'client' | translate}}{{:: 'granted-client-scopes' | translate}}{{:: 'additional-grants' | translate}}{{:: 'consent-created-date' | translate}}{{:: 'consent-last-updated-date' | translate}}{{:: 'action' | translate}}
    {{consent.clientId}} + + , {{clientScope}} + + + + , {{additionalGrant.key}} + + {{consent.createdDate | date :'short'}}{{consent.lastUpdatedDate | date :'short'}}{{:: 'revoke' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-credentials.html b/base/admin/resources/partials/user-credentials.html new file mode 100755 index 0000000..737b4b1 --- /dev/null +++ b/base/admin/resources/partials/user-credentials.html @@ -0,0 +1,173 @@ +
    + + + + +
    + +
    + + {{:: 'supported-user-storage-credential-types' | translate}} + {{:: 'supported-user-storage-credential-types.tooltip' | translate}} + + + + + + + + + + + + + +
    + +
    + + {{:: 'manage-credentials' | translate}} + {{:: 'manage-credentials.tooltip' | translate}} + + + {{:: 'manage-credentials' | translate}} + + + + + + + + + + + + + + + + + + + + +
    + +
    + {{ (hasPassword ? 'reset-password' : 'set-password') | translate }} +
    + +
    + +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    + {{:: 'credentials.temporary.tooltip' | translate}} +
    + +
    +
    + +
    +
    +
    + +
    + {{:: 'disable-credentials' | translate}} +
    + + +
    + +
    + {{:: 'credentials.disableable.tooltip' | translate}} +
    +
    + + +
    + +
    + {{:: 'credentials.disable.tooltip' | translate}} +
    +
    + +
    + {{:: 'credential-reset-actions' | translate}} +
    + + +
    + +
    + {{:: 'credentials.reset-actions.tooltip' | translate}} +
    +
    + + +
    + + +
    + {{:: 'credential-reset-actions-timeout.tooltip' | translate}} +
    +
    + + +
    + +
    + {{:: 'credentials.reset-actions-email.tooltip' | translate}} +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/user-detail.html b/base/admin/resources/partials/user-detail.html new file mode 100755 index 0000000..d7b4623 --- /dev/null +++ b/base/admin/resources/partials/user-detail.html @@ -0,0 +1,186 @@ +
    + + + + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + {{user.createdTimestamp|date:('dateFormat' | translate)}} {{user.createdTimestamp|date:('timeFormat' | translate)}} +
    +
    + +
    + +
    + + +
    +
    + + +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    + +
    + +
    + +
    + {{:: 'user-enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'user-temporarily-locked.tooltip' | translate}} +
    + +
    +
    +
    + + + {{:: 'user-link.tooltip' | translate}} +
    +
    + + + {{:: 'user-origin.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'email-verified.tooltip' | translate}} +
    +
    + +
    + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +
    +
    +
    {{:: 'groups-joining-path' | translate}}{{:: 'actions' | translate}}
    {{group.path}} + {{:: 'remove' | translate}} +
    {{:: 'groups-joining-no-selected' | translate}}
    +
    + {{:: 'groups-joining.tooltip' | translate}} +
    +
    + + +
    + +
    + {{:: 'required-user-actions.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    +
    + +
    + + +
    + +
    + {{:: 'impersonate-user.tooltip' | translate}} +
    + +
    + +
    +
    + + +
    + +
    + + +
    +
    + +
    +
    + + diff --git a/base/admin/resources/partials/user-federated-identity-detail.html b/base/admin/resources/partials/user-federated-identity-detail.html new file mode 100644 index 0000000..aa5d049 --- /dev/null +++ b/base/admin/resources/partials/user-federated-identity-detail.html @@ -0,0 +1,53 @@ +
    + + +

    {{:: 'add-identity-provider-link' | translate}}

    + + + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + +
    + {{:: 'identity-provider-user-id.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'identity-provider-username.tooltip' | translate}} +
    + +
    + + +
    +
    + + +
    +
    + +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-federated-identity-list.html b/base/admin/resources/partials/user-federated-identity-list.html new file mode 100644 index 0000000..82f96f6 --- /dev/null +++ b/base/admin/resources/partials/user-federated-identity-list.html @@ -0,0 +1,41 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'identity-provider-alias' | translate}}{{:: 'provider-user-id' | translate}}{{:: 'provider-username' | translate}}{{:: 'action' | translate}}
    {{identity.identityProvider}}{{identity.userId}}{{identity.userName}}{{:: 'remove' | translate}}
    {{:: 'no-identity-provider-links-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-federation.html b/base/admin/resources/partials/user-federation.html new file mode 100755 index 0000000..3e702f3 --- /dev/null +++ b/base/admin/resources/partials/user-federation.html @@ -0,0 +1,69 @@ +
    +

    + {{:: 'user-federation' | translate}} +

    + +
    +
    + +
    +

    + {{:: 'user-federation' | translate}} +

    +

    Keycloak can federate external user databases. Out of the box we have support for LDAP and Active Directory.

    +

    To get started select a provider from the dropdown below:

    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    {{:: 'id' | translate}}{{:: 'enabled' | translate}}{{:: 'provider-name' | translate}}{{:: 'priority' | translate}}{{:: 'actions' | translate}}
    {{getInstanceName(instance)}}{{isProviderEnabled(instance)}}{{getInstanceProvider(instance) | capitalize}}{{getInstancePriority(instance)}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-user-federation-providers-configured' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-group-membership.html b/base/admin/resources/partials/user-group-membership.html new file mode 100755 index 0000000..abfb237 --- /dev/null +++ b/base/admin/resources/partials/user-group-membership.html @@ -0,0 +1,114 @@ +
    + + + + +
    +
    + + +
    +
    +
    + + + + + + + + + + + +
    +
    +
    + + {{:: 'group-membership.tooltip' | translate}} +
    +
    +
    + +
    + +
    +
    +
    +   + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + + + + + + + +
    + +
    +
    + + {{:: 'membership.available-groups.tooltip' | translate}} +
    +
    +
    + +
    + +
    +
    +
    +   + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/user-list.html b/base/admin/resources/partials/user-list.html new file mode 100755 index 0000000..1fc707c --- /dev/null +++ b/base/admin/resources/partials/user-list.html @@ -0,0 +1,69 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + +
    + + {{:: 'add-user' | translate}} +
    +
    +
    {{:: 'id' | translate}}{{:: 'username' | translate}}{{:: 'email' | translate}}{{:: 'last-name' | translate}}{{:: 'first-name' | translate}}{{:: 'actions' | translate}}
    +
    + + + +
    +
    {{user.id}}{{user.username}}{{user.email}}{{user.lastName}}{{user.firstName}}{{:: 'edit' | translate}}{{:: 'impersonate' | translate}}{{:: 'delete' | translate}}
    {{:: 'users.instruction' | translate}}{{:: 'no-results' | translate}}{{:: 'no-users-available' | translate}}
    +
    + + diff --git a/base/admin/resources/partials/user-offline-sessions.html b/base/admin/resources/partials/user-offline-sessions.html new file mode 100644 index 0000000..0194bca --- /dev/null +++ b/base/admin/resources/partials/user-offline-sessions.html @@ -0,0 +1,35 @@ +
    + + + + + + + + + + + + + + + + + + + +
    {{:: 'ip-address' | translate}}{{:: 'started' | translate}}{{:: 'last-refresh' | translate}}
    {{session.ipAddress}}{{session.start | date:'medium'}}{{session.lastAccess | date:'medium'}}
    + +
    +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-sessions.html b/base/admin/resources/partials/user-sessions.html new file mode 100755 index 0000000..02c1959 --- /dev/null +++ b/base/admin/resources/partials/user-sessions.html @@ -0,0 +1,43 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    {{:: 'ip-address' | translate}}{{:: 'started' | translate}}{{:: 'last-access' | translate}}{{:: 'clients' | translate}}{{:: 'action' | translate}}
    {{session.ipAddress}}{{session.start | date:'medium'}}{{session.lastAccess | date:'medium'}} + + + {{:: 'logout' | translate}}
    +
    + + diff --git a/base/admin/resources/partials/user-storage-generic.html b/base/admin/resources/partials/user-storage-generic.html new file mode 100755 index 0000000..2b2456c --- /dev/null +++ b/base/admin/resources/partials/user-storage-generic.html @@ -0,0 +1,246 @@ +
    + + + + +
    +
    + {{:: 'required-settings' | translate}} +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'user-storage.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'console-display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'priority.tooltip' | translate}} +
    + + + +
    + +
    + {{:: 'sync-settings' | translate}} +
    + +
    + +
    + {{:: 'periodic-full-sync.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'full-sync-period.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'periodic-changed-users-sync.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'changed-users-sync-period.tooltip' | translate}} +
    +
    + + +
    + {{:: 'user-storage-cache-policy' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}} +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + + + + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-storage-kerberos.html b/base/admin/resources/partials/user-storage-kerberos.html new file mode 100644 index 0000000..b7fa6ef --- /dev/null +++ b/base/admin/resources/partials/user-storage-kerberos.html @@ -0,0 +1,264 @@ +
    + + + + +
    +
    + {{:: 'required-settings' | translate}} +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'user-storage.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'console-display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'priority.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'kerberos-realm.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'server-principal.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'keytab.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'debug.tooltip' | translate}} +
    + +
    + +
    + +
    + {{:: 'allow-password-authentication.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'edit-mode.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'update-profile-first-login.tooltip' | translate}} +
    + +
    + +
    + {{:: 'user-storage-cache-policy' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}} +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-storage-ldap-mapper-detail.html b/base/admin/resources/partials/user-storage-ldap-mapper-detail.html new file mode 100644 index 0000000..88de0ff --- /dev/null +++ b/base/admin/resources/partials/user-storage-ldap-mapper-detail.html @@ -0,0 +1,64 @@ +
    + + +

    {{mapper.name|capitalize}}

    +

    {{:: 'add-user-federation-mapper' | translate}}

    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'mapper.name.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{mapperType.helpText}} +
    +
    + +
    + +
    + {{mapperType.helpText}} +
    + + + +
    + +
    +
    + + + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-storage-ldap-mappers.html b/base/admin/resources/partials/user-storage-ldap-mappers.html new file mode 100644 index 0000000..794e83e --- /dev/null +++ b/base/admin/resources/partials/user-storage-ldap-mappers.html @@ -0,0 +1,46 @@ +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    {{:: 'name' | translate}}{{:: 'type' | translate}}
    {{mapper.name}}{{mapper.providerId}}
    {{:: 'no-mappers-available' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/user-storage-ldap.html b/base/admin/resources/partials/user-storage-ldap.html new file mode 100755 index 0000000..6e25022 --- /dev/null +++ b/base/admin/resources/partials/user-storage-ldap.html @@ -0,0 +1,567 @@ +
    + + + + +
    +
    + {{:: 'required-settings' | translate}} +
    + +
    + +
    +
    +
    + +
    + +
    + {{:: 'user-storage.enabled.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'console-display-name.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'priority.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.import-enabled.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'ldap.edit-mode.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.sync-registrations.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + +
    +
    + {{:: 'ldap.vendor.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'username-ldap-attribute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'rdn-ldap-attribute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'uuid-ldap-attribute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.user-object-classes.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-url.tooltip' | translate}} + +
    +
    + +
    + +
    + {{:: 'ldap.users-dn.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.custom-user-ldap-filter.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'ldap.search-scope.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'ldap.authentication-type.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.bind-dn.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.bind-credential.tooltip' | translate}} + +
    +
    + +
    + {{:: 'advanced-ldap-settings' | translate}} +
    + +
    + +
    + {{:: 'ldap.startTls.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.usePasswordModifyExtendedOp.tooltip' | translate}} +
    + + {{:: 'ldap-query-supported-extensions.tooltip' | translate}} +
    +
    +
    + +
    + +
    + {{:: 'ldap.validate-password-policy.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'trust-email.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'ldap.use-truststore-spi.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-timeout.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.read-timeout.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.pagination.tooltip' | translate}} +
    +
    + +
    + {{:: 'connection-pooling' | translate}} +
    + +
    + +
    + {{:: 'ldap.connection-pooling.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.authentication.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.debug.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.initsize.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.maxsize.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.prefsize.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.protocol.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.connection-pooling.timeout.tooltip' | translate}} +
    +
    + +
    + {{:: 'kerberos-integration' | translate}} +
    + +
    + +
    + {{:: 'ldap.allow-kerberos-authentication.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'kerberos-realm.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'server-principal.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'keytab.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'debug.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.use-kerberos-for-password-authentication.tooltip' | translate}} +
    +
    + +
    + {{:: 'sync-settings' | translate}} +
    + +
    + +
    + {{:: 'ldap.batch-size.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.periodic-full-sync.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'full-sync-period.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.periodic-changed-users-sync.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'ldap.changed-users-sync-period.tooltip' | translate}} +
    +
    + +
    + {{:: 'user-storage-cache-policy' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionDay.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionHour.tooltip' | translate}} +
    +
    + +
    +
    + +
    +
    + {{:: 'userStorage.cachePolicy.evictionMinute.tooltip' | translate}} +
    +
    + +
    + +
    + {{:: 'userStorage.cachePolicy.maxLifespan.tooltip' | translate}} +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + + + + + +
    +
    +
    +
    + + diff --git a/base/admin/resources/partials/user-storage.html b/base/admin/resources/partials/user-storage.html new file mode 100755 index 0000000..93ca5a9 --- /dev/null +++ b/base/admin/resources/partials/user-storage.html @@ -0,0 +1,45 @@ +
    +

    + {{:: 'user-storage' | translate}} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    +
    {{:: 'id' | translate}}{{:: 'provider-name' | translate}}{{:: 'enabled' | translate}}{{:: 'priority' | translate}}{{:: 'actions' | translate}}
    {{instance.name}}{{instance.providerId|capitalize}}{{instance.config['enabled'][0]}}{{instance.config['priority'][0]}}{{:: 'edit' | translate}}{{:: 'delete' | translate}}
    {{:: 'no-user-storage-providers-configured' | translate}}
    +
    + + \ No newline at end of file diff --git a/base/admin/resources/partials/webauthn-policy-passwordless.html b/base/admin/resources/partials/webauthn-policy-passwordless.html new file mode 100644 index 0000000..dbb1e0e --- /dev/null +++ b/base/admin/resources/partials/webauthn-policy-passwordless.html @@ -0,0 +1,177 @@ + + +
    +

    {{:: 'authentication' | translate}}

    + + + + +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-rp-entity-name.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-signature-algorithms.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-rp-id.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-attestation-conveyance-preference.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-authenticator-attachment.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-require-resident-key.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-user-verification-requirement.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-create-timeout.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-avoid-same-authenticator-register.tooltip' | translate}} +
    + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + {{:: 'webauthn-acceptable-aaguids.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    + +
    + + + diff --git a/base/admin/resources/partials/webauthn-policy.html b/base/admin/resources/partials/webauthn-policy.html new file mode 100644 index 0000000..432eb3d --- /dev/null +++ b/base/admin/resources/partials/webauthn-policy.html @@ -0,0 +1,159 @@ +
    +

    {{:: 'authentication' | translate}}

    + + + + +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-rp-entity-name.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-signature-algorithms.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-rp-id.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-attestation-conveyance-preference.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-authenticator-attachment.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-require-resident-key.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-user-verification-requirement.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-create-timeout.tooltip' | translate}} +
    + +
    + +
    +
    + +
    +
    + {{:: 'webauthn-avoid-same-authenticator-register.tooltip' | translate}} +
    + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + {{:: 'webauthn-acceptable-aaguids.tooltip' | translate}} +
    + +
    +
    + + +
    +
    +
    + +
    + + + diff --git a/base/admin/resources/templates/authz/kc-authz-modal.html b/base/admin/resources/templates/authz/kc-authz-modal.html new file mode 100644 index 0000000..18acf86 --- /dev/null +++ b/base/admin/resources/templates/authz/kc-authz-modal.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/templates/authz/kc-tabs-resource-server.html b/base/admin/resources/templates/authz/kc-tabs-resource-server.html new file mode 100755 index 0000000..bd20270 --- /dev/null +++ b/base/admin/resources/templates/authz/kc-tabs-resource-server.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-component-config.html b/base/admin/resources/templates/kc-component-config.html new file mode 100755 index 0000000..3f29ee7 --- /dev/null +++ b/base/admin/resources/templates/kc-component-config.html @@ -0,0 +1,67 @@ +
    +
    + + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    + +
    +
    + + + +
    +
    + +
    +
    + {{config[option.name]}} +
    +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    + + {{:: option.helpText | translate}} +
    +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-copy.html b/base/admin/resources/templates/kc-copy.html new file mode 100755 index 0000000..d92b2d4 --- /dev/null +++ b/base/admin/resources/templates/kc-copy.html @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-dropdown.html b/base/admin/resources/templates/kc-dropdown.html new file mode 100644 index 0000000..13874ca --- /dev/null +++ b/base/admin/resources/templates/kc-dropdown.html @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-edit.html b/base/admin/resources/templates/kc-edit.html new file mode 100644 index 0000000..0b98b29 --- /dev/null +++ b/base/admin/resources/templates/kc-edit.html @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-menu.html b/base/admin/resources/templates/kc-menu.html new file mode 100755 index 0000000..cfa7f4f --- /dev/null +++ b/base/admin/resources/templates/kc-menu.html @@ -0,0 +1,64 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-modal-message.html b/base/admin/resources/templates/kc-modal-message.html new file mode 100755 index 0000000..fc32708 --- /dev/null +++ b/base/admin/resources/templates/kc-modal-message.html @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-modal.html b/base/admin/resources/templates/kc-modal.html new file mode 100644 index 0000000..25eee96 --- /dev/null +++ b/base/admin/resources/templates/kc-modal.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-paging.html b/base/admin/resources/templates/kc-paging.html new file mode 100644 index 0000000..653e4a5 --- /dev/null +++ b/base/admin/resources/templates/kc-paging.html @@ -0,0 +1,25 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-provider-config.html b/base/admin/resources/templates/kc-provider-config.html new file mode 100755 index 0000000..6069641 --- /dev/null +++ b/base/admin/resources/templates/kc-provider-config.html @@ -0,0 +1,91 @@ +
    +
    + + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    + +
    +
    + {{config[option.name]}} +
    +
    + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + +
    {{:: 'key' | translate}}{{:: 'value' | translate}}{{:: 'actions' | translate}}
    {{mapEntry['key']}}{{:: 'delete' | translate}} +
    {{:: 'add' | translate}} +
    +
    + + {{:: option.helpText | translate}} +
    +
    diff --git a/base/admin/resources/templates/kc-switch.html b/base/admin/resources/templates/kc-switch.html new file mode 100644 index 0000000..7f28598 --- /dev/null +++ b/base/admin/resources/templates/kc-switch.html @@ -0,0 +1,12 @@ + +
    + + +
    +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-authentication.html b/base/admin/resources/templates/kc-tabs-authentication.html new file mode 100755 index 0000000..98ad23f --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-authentication.html @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-client-role.html b/base/admin/resources/templates/kc-tabs-client-role.html new file mode 100755 index 0000000..20f05a9 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-client-role.html @@ -0,0 +1,17 @@ +
    +

    {{:: 'add-role' | translate}}

    +

    {{role.name|capitalize}}

    + + +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-client-scope.html b/base/admin/resources/templates/kc-tabs-client-scope.html new file mode 100755 index 0000000..72f59e9 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-client-scope.html @@ -0,0 +1,20 @@ +
    + +

    {{:: 'add-client-scope' | translate}}

    +

    + {{clientScope.name|capitalize}} + +

    + + +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-client.html b/base/admin/resources/templates/kc-tabs-client.html new file mode 100755 index 0000000..008f32b --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-client.html @@ -0,0 +1,63 @@ +
    + +

    {{:: 'add-client' | translate}}

    +

    + {{client.clientId|capitalize}} + +

    + + +
    diff --git a/base/admin/resources/templates/kc-tabs-clients.html b/base/admin/resources/templates/kc-tabs-clients.html new file mode 100755 index 0000000..8cf318a --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-clients.html @@ -0,0 +1,16 @@ +
    +

    + {{:: 'clients' | translate}} +

    + + +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-group-list.html b/base/admin/resources/templates/kc-tabs-group-list.html new file mode 100755 index 0000000..5b67035 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-group-list.html @@ -0,0 +1,11 @@ +
    +

    + {{:: 'user-groups' | translate}} +

    + + +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-group.html b/base/admin/resources/templates/kc-tabs-group.html new file mode 100755 index 0000000..da08252 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-group.html @@ -0,0 +1,17 @@ +
    +

    + {{group.name|capitalize}} + +

    + + +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-identity-provider.html b/base/admin/resources/templates/kc-tabs-identity-provider.html new file mode 100644 index 0000000..94b2604 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-identity-provider.html @@ -0,0 +1,16 @@ +
    +

    + {{identityProvider.displayName}} + {{provider.name}} + {{identityProvider.alias}} + + +

    +

    {{:: 'add-identity-provider' | translate}}

    + + +
    diff --git a/base/admin/resources/templates/kc-tabs-ldap.html b/base/admin/resources/templates/kc-tabs-ldap.html new file mode 100644 index 0000000..a4f17a8 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-ldap.html @@ -0,0 +1,12 @@ +
    +

    + {{instance.name|capitalize}} + +

    +

    {{:: 'add-user-federation-provider' | translate}}

    + + +
    diff --git a/base/admin/resources/templates/kc-tabs-realm.html b/base/admin/resources/templates/kc-tabs-realm.html new file mode 100755 index 0000000..22b66ce --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-realm.html @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-role.html b/base/admin/resources/templates/kc-tabs-role.html new file mode 100755 index 0000000..f00515b --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-role.html @@ -0,0 +1,16 @@ +
    +

    {{role.name|capitalize}}

    +

    {{:: 'add-role' | translate}}

    + + +
    \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-user-storage.html b/base/admin/resources/templates/kc-tabs-user-storage.html new file mode 100644 index 0000000..03e1ddc --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-user-storage.html @@ -0,0 +1,11 @@ +
    +

    + {{instance.name|capitalize}} + +

    +

    {{:: 'add-user-storage-provider' | translate}}

    + + +
    diff --git a/base/admin/resources/templates/kc-tabs-user.html b/base/admin/resources/templates/kc-tabs-user.html new file mode 100755 index 0000000..9c372e3 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-user.html @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/base/admin/resources/templates/kc-tabs-users.html b/base/admin/resources/templates/kc-tabs-users.html new file mode 100755 index 0000000..e5771a0 --- /dev/null +++ b/base/admin/resources/templates/kc-tabs-users.html @@ -0,0 +1,11 @@ +
    +

    {{:: 'users' | translate}}

    + + +
    \ No newline at end of file diff --git a/base/email/html/email-test.ftl b/base/email/html/email-test.ftl new file mode 100644 index 0000000..3a52272 --- /dev/null +++ b/base/email/html/email-test.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("emailTestBodyHtml",realmName))?no_esc} + + diff --git a/base/email/html/email-verification-with-code.ftl b/base/email/html/email-verification-with-code.ftl new file mode 100644 index 0000000..66e8925 --- /dev/null +++ b/base/email/html/email-verification-with-code.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("emailVerificationBodyCodeHtml",code))?no_esc} + + diff --git a/base/email/html/email-verification.ftl b/base/email/html/email-verification.ftl new file mode 100644 index 0000000..dacabd2 --- /dev/null +++ b/base/email/html/email-verification.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("emailVerificationBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + + diff --git a/base/email/html/event-login_error.ftl b/base/email/html/event-login_error.ftl new file mode 100644 index 0000000..022c024 --- /dev/null +++ b/base/email/html/event-login_error.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("eventLoginErrorBodyHtml",event.date,event.ipAddress))?no_esc} + + diff --git a/base/email/html/event-remove_totp.ftl b/base/email/html/event-remove_totp.ftl new file mode 100644 index 0000000..9a56ed3 --- /dev/null +++ b/base/email/html/event-remove_totp.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("eventRemoveTotpBodyHtml",event.date, event.ipAddress))?no_esc} + + diff --git a/base/email/html/event-update_password.ftl b/base/email/html/event-update_password.ftl new file mode 100644 index 0000000..27825c7 --- /dev/null +++ b/base/email/html/event-update_password.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("eventUpdatePasswordBodyHtml",event.date, event.ipAddress))?no_esc} + + diff --git a/base/email/html/event-update_totp.ftl b/base/email/html/event-update_totp.ftl new file mode 100644 index 0000000..3ed37c3 --- /dev/null +++ b/base/email/html/event-update_totp.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("eventUpdateTotpBodyHtml",event.date, event.ipAddress))?no_esc} + + diff --git a/base/email/html/executeActions.ftl b/base/email/html/executeActions.ftl new file mode 100755 index 0000000..4c837bc --- /dev/null +++ b/base/email/html/executeActions.ftl @@ -0,0 +1,9 @@ +<#outputformat "plainText"> +<#assign requiredActionsText><#if requiredActions??><#list requiredActions><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, + + + + +${kcSanitize(msg("executeActionsBodyHtml",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration)))?no_esc} + + diff --git a/base/email/html/identity-provider-link.ftl b/base/email/html/identity-provider-link.ftl new file mode 100644 index 0000000..8b67968 --- /dev/null +++ b/base/email/html/identity-provider-link.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("identityProviderLinkBodyHtml", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration)))?no_esc} + + \ No newline at end of file diff --git a/base/email/html/password-reset.ftl b/base/email/html/password-reset.ftl new file mode 100755 index 0000000..b2840b6 --- /dev/null +++ b/base/email/html/password-reset.ftl @@ -0,0 +1,5 @@ + + +${kcSanitize(msg("passwordResetBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + + \ No newline at end of file diff --git a/base/email/messages/messages_en.properties b/base/email/messages/messages_en.properties new file mode 100755 index 0000000..b83d00f --- /dev/null +++ b/base/email/messages/messages_en.properties @@ -0,0 +1,51 @@ +emailVerificationSubject=Verify email +emailVerificationBody=Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {3}.\n\nIf you didn''t create this account, just ignore this message. +emailVerificationBodyHtml=

    Someone has created a {2} account with this email address. If this was you, click the link below to verify your email address

    Link to e-mail address verification

    This link will expire within {3}.

    If you didn''t create this account, just ignore this message.

    +emailTestSubject=[KEYCLOAK] - SMTP test message +emailTestBody=This is a test message +emailTestBodyHtml=

    This is a test message

    +identityProviderLinkSubject=Link {0} +identityProviderLinkBody=Someone wants to link your "{1}" account with "{0}" account of user {2} . If this was you, click the link below to link accounts\n\n{3}\n\nThis link will expire within {5}.\n\nIf you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}. +identityProviderLinkBodyHtml=

    Someone wants to link your {1} account with {0} account of user {2} . If this was you, click the link below to link accounts

    Link to confirm account linking

    This link will expire within {5}.

    If you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.

    +passwordResetSubject=Reset password +passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {3}.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed. +passwordResetBodyHtml=

    Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.

    Link to reset credentials

    This link will expire within {3}.

    If you don''t want to reset your credentials, just ignore this message and nothing will be changed.

    +executeActionsSubject=Update Your Account +executeActionsBody=Your administrator has just requested that you update your {2} account by performing the following action(s): {3}. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {4}.\n\nIf you are unaware that your administrator has requested this, just ignore this message and nothing will be changed. +executeActionsBodyHtml=

    Your administrator has just requested that you update your {2} account by performing the following action(s): {3}. Click on the link below to start this process.

    Link to account update

    This link will expire within {4}.

    If you are unaware that your administrator has requested this, just ignore this message and nothing will be changed.

    +eventLoginErrorSubject=Login error +eventLoginErrorBody=A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an administrator. +eventLoginErrorBodyHtml=

    A failed login attempt was detected to your account on {0} from {1}. If this was not you, please contact an administrator.

    +eventRemoveTotpSubject=Remove OTP +eventRemoveTotpBody=OTP was removed from your account on {0} from {1}. If this was not you, please contact an administrator. +eventRemoveTotpBodyHtml=

    OTP was removed from your account on {0} from {1}. If this was not you, please contact an administrator.

    +eventUpdatePasswordSubject=Update password +eventUpdatePasswordBody=Your password was changed on {0} from {1}. If this was not you, please contact an administrator. +eventUpdatePasswordBodyHtml=

    Your password was changed on {0} from {1}. If this was not you, please contact an administrator.

    +eventUpdateTotpSubject=Update OTP +eventUpdateTotpBody=OTP was updated for your account on {0} from {1}. If this was not you, please contact an administrator. +eventUpdateTotpBodyHtml=

    OTP was updated for your account on {0} from {1}. If this was not you, please contact an administrator.

    + +requiredAction.CONFIGURE_TOTP=Configure OTP +requiredAction.terms_and_conditions=Terms and Conditions +requiredAction.UPDATE_PASSWORD=Update Password +requiredAction.UPDATE_PROFILE=Update Profile +requiredAction.VERIFY_EMAIL=Verify Email + +# units for link expiration timeout formatting +linkExpirationFormatter.timePeriodUnit.seconds=seconds +linkExpirationFormatter.timePeriodUnit.seconds.1=second +linkExpirationFormatter.timePeriodUnit.minutes=minutes +linkExpirationFormatter.timePeriodUnit.minutes.1=minute +#for language which have more unit plural forms depending on the value (eg. Czech and other Slavic langs) you can override unit text for some other values like this: +#linkExpirationFormatter.timePeriodUnit.minutes.2=minuty +#linkExpirationFormatter.timePeriodUnit.minutes.3=minuty +#linkExpirationFormatter.timePeriodUnit.minutes.4=minuty +linkExpirationFormatter.timePeriodUnit.hours=hours +linkExpirationFormatter.timePeriodUnit.hours.1=hour +linkExpirationFormatter.timePeriodUnit.days=days +linkExpirationFormatter.timePeriodUnit.days.1=day + +emailVerificationBodyCode=Please verify your email address by entering in the following code.\n\n{0}\n\n. +emailVerificationBodyCodeHtml=

    Please verify your email address by entering in the following code.

    {0}

    + diff --git a/base/email/text/email-test.ftl b/base/email/text/email-test.ftl new file mode 100644 index 0000000..f1becdd --- /dev/null +++ b/base/email/text/email-test.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailTestBody", realmName)} \ No newline at end of file diff --git a/base/email/text/email-verification-with-code.ftl b/base/email/text/email-verification-with-code.ftl new file mode 100644 index 0000000..4ffb7d8 --- /dev/null +++ b/base/email/text/email-verification-with-code.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailVerificationBodyCode",code)} \ No newline at end of file diff --git a/base/email/text/email-verification.ftl b/base/email/text/email-verification.ftl new file mode 100644 index 0000000..9e39696 --- /dev/null +++ b/base/email/text/email-verification.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailVerificationBody",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/base/email/text/event-login_error.ftl b/base/email/text/event-login_error.ftl new file mode 100644 index 0000000..bfb4036 --- /dev/null +++ b/base/email/text/event-login_error.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventLoginErrorBody",event.date,event.ipAddress)} \ No newline at end of file diff --git a/base/email/text/event-remove_totp.ftl b/base/email/text/event-remove_totp.ftl new file mode 100644 index 0000000..a7e3b68 --- /dev/null +++ b/base/email/text/event-remove_totp.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventRemoveTotpBody",event.date, event.ipAddress)} \ No newline at end of file diff --git a/base/email/text/event-update_password.ftl b/base/email/text/event-update_password.ftl new file mode 100644 index 0000000..2ec7ea0 --- /dev/null +++ b/base/email/text/event-update_password.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventUpdatePasswordBody",event.date, event.ipAddress)} \ No newline at end of file diff --git a/base/email/text/event-update_totp.ftl b/base/email/text/event-update_totp.ftl new file mode 100644 index 0000000..14778b5 --- /dev/null +++ b/base/email/text/event-update_totp.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventUpdateTotpBody",event.date, event.ipAddress)} \ No newline at end of file diff --git a/base/email/text/executeActions.ftl b/base/email/text/executeActions.ftl new file mode 100755 index 0000000..6610c7a --- /dev/null +++ b/base/email/text/executeActions.ftl @@ -0,0 +1,4 @@ +<#ftl output_format="plainText"> +<#assign requiredActionsText><#if requiredActions??><#list requiredActions><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, <#else> + +${msg("executeActionsBody",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/base/email/text/identity-provider-link.ftl b/base/email/text/identity-provider-link.ftl new file mode 100644 index 0000000..ed9d246 --- /dev/null +++ b/base/email/text/identity-provider-link.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("identityProviderLinkBody", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/base/email/text/password-reset.ftl b/base/email/text/password-reset.ftl new file mode 100755 index 0000000..27405c9 --- /dev/null +++ b/base/email/text/password-reset.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("passwordResetBody",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/base/login/cli_splash.ftl b/base/login/cli_splash.ftl new file mode 100644 index 0000000..cd9ebbb --- /dev/null +++ b/base/login/cli_splash.ftl @@ -0,0 +1,7 @@ + _ __ _ _ +| |/ /___ _ _ ___| | ___ __ _| | __ +| ' // _ \ | | |/ __| |/ _ \ / _` | |/ / +| . \ __/ |_| | (__| | (_) | (_| | < +|_|\_\___|\__, |\___|_|\___/ \__,_|_|\_\ + |___/ + diff --git a/base/login/code.ftl b/base/login/code.ftl new file mode 100755 index 0000000..6830fc4 --- /dev/null +++ b/base/login/code.ftl @@ -0,0 +1,19 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + <#if code.success> + ${msg("codeSuccessTitle")} + <#else> + ${msg("codeErrorTitle", code.error)} + + <#elseif section = "form"> +
    + <#if code.success> +

    ${msg("copyCodeInstruction")}

    + + <#else> +

    ${code.error}

    + +
    + + diff --git a/base/login/delete-account-confirm.ftl b/base/login/delete-account-confirm.ftl new file mode 100644 index 0000000..6aa93f0 --- /dev/null +++ b/base/login/delete-account-confirm.ftl @@ -0,0 +1,33 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + + <#if section = "header"> + ${msg("deleteAccountConfirm")} + + <#elseif section = "form"> + +
    + +
    + + ${msg("irreversibleAction")} +
    + +

    ${msg("deletingImplies")}

    +
      +
    • ${msg("loggingOutImmediately")}
    • +
    • ${msg("errasingData")}
    • +
    + + + +
    + + <#if triggered_from_aia> + + +
    +
    + + + \ No newline at end of file diff --git a/base/login/error.ftl b/base/login/error.ftl new file mode 100755 index 0000000..a909e0d --- /dev/null +++ b/base/login/error.ftl @@ -0,0 +1,13 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=false; section> + <#if section = "header"> + ${msg("errorTitle")} + <#elseif section = "form"> +
    +

    ${message.summary?no_esc}

    + <#if client?? && client.baseUrl?has_content> +

    ${kcSanitize(msg("backToApplication"))?no_esc}

    + +
    + + \ No newline at end of file diff --git a/base/login/info.ftl b/base/login/info.ftl new file mode 100755 index 0000000..8da0cb7 --- /dev/null +++ b/base/login/info.ftl @@ -0,0 +1,24 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=false; section> + <#if section = "header"> + <#if messageHeader??> + ${messageHeader} + <#else> + ${message.summary} + + <#elseif section = "form"> +
    +

    ${message.summary}<#if requiredActions??><#list requiredActions>: <#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, <#else>

    + <#if skipLink??> + <#else> + <#if pageRedirectUri?has_content> +

    ${kcSanitize(msg("backToApplication"))?no_esc}

    + <#elseif actionUri?has_content> +

    ${kcSanitize(msg("proceedWithAction"))?no_esc}

    + <#elseif (client.baseUrl)?has_content> +

    ${kcSanitize(msg("backToApplication"))?no_esc}

    + + +
    + + \ No newline at end of file diff --git a/base/login/login-config-totp-text.ftl b/base/login/login-config-totp-text.ftl new file mode 100755 index 0000000..d609182 --- /dev/null +++ b/base/login/login-config-totp-text.ftl @@ -0,0 +1,31 @@ +<#ftl output_format="plainText"> +${msg("loginTotpIntro")} + +${msg("loginTotpStep1")} + +<#list totp.policy.supportedApplications as app> +* ${app} + + +${msg("loginTotpManualStep2")} + + ${totp.totpSecretEncoded} + + +${msg("loginTotpManualStep3")} + +- ${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)} +- ${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()} +- ${msg("loginTotpDigits")}: ${totp.policy.digits} +<#if totp.policy.type = "totp"> +- ${msg("loginTotpInterval")}: ${totp.policy.period} + +<#elseif totp.policy.type = "hotp"> +- ${msg("loginTotpCounter")}: ${totp.policy.initialCounter} + + + +Enter in your one time password so we can verify you have installed it correctly. + + + diff --git a/base/login/login-config-totp.ftl b/base/login/login-config-totp.ftl new file mode 100755 index 0000000..c82948d --- /dev/null +++ b/base/login/login-config-totp.ftl @@ -0,0 +1,108 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayRequiredFields=false displayMessage=!messagesPerField.existsError('totp','userLabel'); section> + + <#if section = "header"> + ${msg("loginTotpTitle")} + <#elseif section = "form"> +
      +
    1. +

      ${msg("loginTotpStep1")}

      + +
        + <#list totp.policy.supportedApplications as app> +
      • ${app}
      • + +
      +
    2. + + <#if mode?? && mode = "manual"> +
    3. +

      ${msg("loginTotpManualStep2")}

      +

      ${totp.totpSecretEncoded}

      +

      ${msg("loginTotpScanBarcode")}

      +
    4. +
    5. +

      ${msg("loginTotpManualStep3")}

      +

      +

        +
      • ${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}
      • +
      • ${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}
      • +
      • ${msg("loginTotpDigits")}: ${totp.policy.digits}
      • + <#if totp.policy.type = "totp"> +
      • ${msg("loginTotpInterval")}: ${totp.policy.period}
      • + <#elseif totp.policy.type = "hotp"> +
      • ${msg("loginTotpCounter")}: ${totp.policy.initialCounter}
      • + +
      +

      +
    6. + <#else> +
    7. +

      ${msg("loginTotpStep2")}

      + Figure: Barcode
      +

      ${msg("loginTotpUnableToScan")}

      +
    8. + +
    9. +

      ${msg("loginTotpStep3")}

      +

      ${msg("loginTotpStep3DeviceName")}

      +
    10. +
    + +
    +
    +
    + * +
    +
    + + + <#if messagesPerField.existsError('totp')> + + ${kcSanitize(messagesPerField.get('totp'))?no_esc} + + + +
    + + <#if mode??> +
    + +
    +
    + <#if totp.otpCredentials?size gte 1>* +
    + +
    + + + <#if messagesPerField.existsError('userLabel')> + + ${kcSanitize(messagesPerField.get('userLabel'))?no_esc} + + +
    +
    + + <#if isAppInitiatedAction??> + + + <#else> + + +
    + + \ No newline at end of file diff --git a/base/login/login-idp-link-confirm.ftl b/base/login/login-idp-link-confirm.ftl new file mode 100644 index 0000000..c3537c5 --- /dev/null +++ b/base/login/login-idp-link-confirm.ftl @@ -0,0 +1,13 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("confirmLinkIdpTitle")} + <#elseif section = "form"> +
    +
    + + +
    +
    + + diff --git a/base/login/login-idp-link-email.ftl b/base/login/login-idp-link-email.ftl new file mode 100644 index 0000000..0020178 --- /dev/null +++ b/base/login/login-idp-link-email.ftl @@ -0,0 +1,16 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("emailLinkIdpTitle", idpDisplayName)} + <#elseif section = "form"> +

    + ${msg("emailLinkIdp1", idpDisplayName, brokerContext.username, realm.displayName)} +

    +

    + ${msg("emailLinkIdp2")} ${msg("doClickHere")} ${msg("emailLinkIdp3")} +

    +

    + ${msg("emailLinkIdp4")} ${msg("doClickHere")} ${msg("emailLinkIdp5")} +

    + + \ No newline at end of file diff --git a/base/login/login-oauth-grant.ftl b/base/login/login-oauth-grant.ftl new file mode 100755 index 0000000..8c59276 --- /dev/null +++ b/base/login/login-oauth-grant.ftl @@ -0,0 +1,41 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout bodyClass="oauth"; section> + <#if section = "header"> + <#if client.name?has_content> + ${msg("oauthGrantTitle",advancedMsg(client.name))} + <#else> + ${msg("oauthGrantTitle",client.clientId)} + + <#elseif section = "form"> +
    +

    ${msg("oauthGrantRequest")}

    +
      + <#if oauth.clientScopesRequested??> + <#list oauth.clientScopesRequested as clientScope> +
    • + ${advancedMsg(clientScope.consentScreenText)} +
    • + + +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    + + diff --git a/base/login/login-oauth2-device-verify-user-code.ftl b/base/login/login-oauth2-device-verify-user-code.ftl new file mode 100644 index 0000000..dfb625f --- /dev/null +++ b/base/login/login-oauth2-device-verify-user-code.ftl @@ -0,0 +1,31 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("oauth2DeviceVerificationTitle")} + <#elseif section = "form"> +
    +
    +
    + +
    + +
    + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/base/login/login-otp.ftl b/base/login/login-otp.ftl new file mode 100755 index 0000000..a43778d --- /dev/null +++ b/base/login/login-otp.ftl @@ -0,0 +1,58 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section> + <#if section="header"> + ${msg("doLogIn")} + <#elseif section="form"> +
    + <#if otpLogin.userOtpCredentials?size gt 1> +
    +
    + <#list otpLogin.userOtpCredentials as otpCredential> + checked="checked"> + + +
    +
    + + +
    +
    + +
    + +
    + + + <#if messagesPerField.existsError('totp')> + + ${kcSanitize(messagesPerField.get('totp'))?no_esc} + + +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + \ No newline at end of file diff --git a/base/login/login-page-expired.ftl b/base/login/login-page-expired.ftl new file mode 100644 index 0000000..2b470e0 --- /dev/null +++ b/base/login/login-page-expired.ftl @@ -0,0 +1,11 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("pageExpiredTitle")} + <#elseif section = "form"> +

    + ${msg("pageExpiredMsg1")} ${msg("doClickHere")} .
    + ${msg("pageExpiredMsg2")} ${msg("doClickHere")} . +

    + + diff --git a/base/login/login-password.ftl b/base/login/login-password.ftl new file mode 100755 index 0000000..e9a7211 --- /dev/null +++ b/base/login/login-password.ftl @@ -0,0 +1,43 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password'); section> + <#if section = "header"> + ${msg("doLogIn")} + <#elseif section = "form"> +
    +
    +
    +
    +
    + + + <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
    + +
    +
    +
    +
    + <#if realm.resetPasswordAllowed> + ${msg("doForgotPassword")} + +
    +
    + +
    + +
    +
    +
    +
    + + + diff --git a/base/login/login-reset-password.ftl b/base/login/login-reset-password.ftl new file mode 100755 index 0000000..561d7d2 --- /dev/null +++ b/base/login/login-reset-password.ftl @@ -0,0 +1,40 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayInfo=true displayMessage=!messagesPerField.existsError('username'); section> + <#if section = "header"> + ${msg("emailForgotTitle")} + <#elseif section = "form"> +
    +
    +
    + +
    +
    + <#if auth?has_content && auth.showUsername()> + + <#else> + + + + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
    +
    + +
    + <#elseif section = "info" > + ${msg("emailInstruction")} + + diff --git a/base/login/login-update-password.ftl b/base/login/login-update-password.ftl new file mode 100755 index 0000000..b884d75 --- /dev/null +++ b/base/login/login-update-password.ftl @@ -0,0 +1,71 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section> + <#if section = "header"> + ${msg("updatePasswordTitle")} + <#elseif section = "form"> +
    + + + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('password-confirm')> + + ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} + + + +
    +
    + +
    +
    +
    + <#if isAppInitiatedAction??> +
    + +
    + +
    +
    + +
    + <#if isAppInitiatedAction??> + + + <#else> + + +
    +
    +
    + + \ No newline at end of file diff --git a/base/login/login-update-profile.ftl b/base/login/login-update-profile.ftl new file mode 100755 index 0000000..3a8610a --- /dev/null +++ b/base/login/login-update-profile.ftl @@ -0,0 +1,97 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','email','firstName','lastName'); section> + <#if section = "header"> + ${msg("loginProfileTitle")} + <#elseif section = "form"> +
    + <#if user.editUsernameAllowed> +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('email')> + + ${kcSanitize(messagesPerField.get('email'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('firstName')> + + ${kcSanitize(messagesPerField.get('firstName'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('lastName')> + + ${kcSanitize(messagesPerField.get('lastName'))?no_esc} + + +
    +
    + +
    +
    +
    +
    +
    + +
    + <#if isAppInitiatedAction??> + + + <#else> + + +
    +
    +
    + + diff --git a/base/login/login-username.ftl b/base/login/login-username.ftl new file mode 100755 index 0000000..add4f4c --- /dev/null +++ b/base/login/login-username.ftl @@ -0,0 +1,92 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username') displayInfo=(realm.password && realm.registrationAllowed && !registrationDisabled??); section> + <#if section = "header"> + ${msg("loginAccountTitle")} + <#elseif section = "form"> +
    +
    + <#if realm.password> +
    +
    + + + <#if usernameEditDisabled??> + + <#else> + + + + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
    + +
    +
    + <#if realm.rememberMe && !usernameEditDisabled??> +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + + <#if realm.password && social.providers??> +
    +
    +

    ${msg("identity-provider-login-label")}

    + + +
    + + + <#elseif section = "info" > + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
    + ${msg("noAccount")} ${msg("doRegister")} +
    + + + + diff --git a/base/login/login-verify-email-code-text.ftl b/base/login/login-verify-email-code-text.ftl new file mode 100644 index 0000000..87abcd7 --- /dev/null +++ b/base/login/login-verify-email-code-text.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("console-verify-email",email, code)} \ No newline at end of file diff --git a/base/login/login-verify-email.ftl b/base/login/login-verify-email.ftl new file mode 100755 index 0000000..0d0cd86 --- /dev/null +++ b/base/login/login-verify-email.ftl @@ -0,0 +1,14 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayInfo=true; section> + <#if section = "header"> + ${msg("emailVerifyTitle")} + <#elseif section = "form"> +

    ${msg("emailVerifyInstruction1")}

    + <#elseif section = "info"> +

    + ${msg("emailVerifyInstruction2")} +
    + ${msg("doClickHere")} ${msg("emailVerifyInstruction3")} +

    + + \ No newline at end of file diff --git a/base/login/login-x509-info.ftl b/base/login/login-x509-info.ftl new file mode 100644 index 0000000..0228b06 --- /dev/null +++ b/base/login/login-x509-info.ftl @@ -0,0 +1,55 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("doLogIn")} + <#elseif section = "form"> + +
    +
    + +
    + +
    + <#if x509.formData.subjectDN??> +
    + +
    + <#else> +
    + +
    + +
    + +
    + + <#if x509.formData.isUserEnabled??> +
    + +
    +
    + +
    + + +
    + +
    +
    +
    +
    +
    + +
    +
    + + <#if x509.formData.isUserEnabled??> + + +
    +
    +
    +
    + + + diff --git a/base/login/login.ftl b/base/login/login.ftl new file mode 100755 index 0000000..dcace58 --- /dev/null +++ b/base/login/login.ftl @@ -0,0 +1,99 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section> + <#if section = "header"> + ${msg("loginAccountTitle")} + <#elseif section = "form"> +
    +
    + <#if realm.password> +
    +
    + + + <#if usernameEditDisabled??> + + <#else> + + + <#if messagesPerField.existsError('username','password')> + + ${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc} + + + +
    + +
    + + + +
    + +
    +
    + <#if realm.rememberMe && !usernameEditDisabled??> +
    + +
    + +
    +
    + <#if realm.resetPasswordAllowed> + ${msg("doForgotPassword")} + +
    + +
    + +
    + value="${auth.selectedCredential}"/> + +
    +
    + +
    + + <#if realm.password && social.providers??> +
    +
    +

    ${msg("identity-provider-login-label")}

    + + +
    + + +
    + <#elseif section = "info" > + <#if realm.password && realm.registrationAllowed && !registrationDisabled??> +
    +
    + ${msg("noAccount")} ${msg("doRegister")} +
    +
    + + + + diff --git a/base/login/messages/messages_en.properties b/base/login/messages/messages_en.properties new file mode 100755 index 0000000..397437a --- /dev/null +++ b/base/login/messages/messages_en.properties @@ -0,0 +1,415 @@ +doLogIn=Sign In +doRegister=Register +doCancel=Cancel +doSubmit=Submit +doBack=Back +doYes=Yes +doNo=No +doContinue=Continue +doIgnore=Ignore +doAccept=Accept +doDecline=Decline +doForgotPassword=Forgot Password? +doClickHere=Click here +doImpersonate=Impersonate +doTryAgain=Try again +doTryAnotherWay=Try Another Way +doConfirmDelete=Confirm deletion +errorDeletingAccount=Error happened while deleting account +deletingAccountForbidden=You do not have enough permissions to delete your own account, contact admin. +kerberosNotConfigured=Kerberos Not Configured +kerberosNotConfiguredTitle=Kerberos Not Configured +bypassKerberosDetail=Either you are not logged in by Kerberos or your browser is not set up for Kerberos login. Please click continue to login in through other means +kerberosNotSetUp=Kerberos is not set up. You cannot login. +registerTitle=Register +loginAccountTitle=Sign in to your account +loginTitle=Sign in to {0} +loginTitleHtml={0} +impersonateTitle={0} Impersonate User +impersonateTitleHtml={0} Impersonate User +realmChoice=Realm +unknownUser=Unknown user +loginTotpTitle=Mobile Authenticator Setup +loginProfileTitle=Update Account Information +loginTimeout=Your login attempt timed out. Login will start from the beginning. +oauthGrantTitle=Grant Access to {0} +oauthGrantTitleHtml={0} +errorTitle=We are sorry... +errorTitleHtml=We are sorry ... +emailVerifyTitle=Email verification +emailForgotTitle=Forgot Your Password? +updatePasswordTitle=Update password +codeSuccessTitle=Success code +codeErrorTitle=Error code\: {0} +displayUnsupported=Requested display type unsupported +browserRequired=Browser required to login +browserContinue=Browser required to complete login +browserContinuePrompt=Open browser and continue login? [y/n]: +browserContinueAnswer=y + + +termsTitle=Terms and Conditions +termsText=

    Terms and conditions to be defined

    +termsPlainText=Terms and conditions to be defined. + +recaptchaFailed=Invalid Recaptcha +recaptchaNotConfigured=Recaptcha is required, but not configured +consentDenied=Consent denied. + +noAccount=New user? +username=Username +usernameOrEmail=Username or email +firstName=First name +givenName=Given name +fullName=Full name +lastName=Last name +familyName=Family name +email=Email +password=Password +passwordConfirm=Confirm password +passwordNew=New Password +passwordNewConfirm=New Password confirmation +rememberMe=Remember me +authenticatorCode=One-time code +address=Address +street=Street +locality=City or Locality +region=State, Province, or Region +postal_code=Zip or Postal code +country=Country +emailVerified=Email verified +website=Web page +phoneNumber=Phone number +phoneNumberVerified=Phone number verified +gender=Gender +birthday=Birthdate +zoneinfo=Time zone +gssDelegationCredential=GSS Delegation Credential +logoutOtherSessions=Sign out from other devices + +profileScopeConsentText=User profile +emailScopeConsentText=Email address +addressScopeConsentText=Address +phoneScopeConsentText=Phone number +offlineAccessScopeConsentText=Offline Access +samlRoleListScopeConsentText=My Roles +rolesScopeConsentText=User roles + +restartLoginTooltip=Restart login + +loginTotpIntro=You need to set up a One Time Password generator to access this account +loginTotpStep1=Install one of the following applications on your mobile: +loginTotpStep2=Open the application and scan the barcode: +loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup. +loginTotpStep3DeviceName=Provide a Device Name to help you manage your OTP devices. +loginTotpManualStep2=Open the application and enter the key: +loginTotpManualStep3=Use the following configuration values if the application allows setting them: +loginTotpUnableToScan=Unable to scan? +loginTotpScanBarcode=Scan barcode? +loginCredential=Credential +loginOtpOneTime=One-time code +loginTotpType=Type +loginTotpAlgorithm=Algorithm +loginTotpDigits=Digits +loginTotpInterval=Interval +loginTotpCounter=Counter +loginTotpDeviceName=Device Name + +loginTotp.totp=Time-based +loginTotp.hotp=Counter-based + +loginChooseAuthenticator=Select login method + +oauthGrantRequest=Do you grant these access privileges? +inResource=in + +oauth2DeviceVerificationTitle=Device Login +verifyOAuth2DeviceUserCode=Enter the code provided by your device and click Submit +oauth2DeviceInvalidUserCodeMessage=Invalid code, please try again. +oauth2DeviceExpiredUserCodeMessage=The code has expired. Please go back to your device and try connecting again. +oauth2DeviceVerificationCompleteHeader=Device Login Successful +oauth2DeviceVerificationCompleteMessage=You may close this browser window and go back to your device. +oauth2DeviceVerificationFailedHeader=Device Login Failed +oauth2DeviceVerificationFailedMessage=You may close this browser window and go back to your device and try connecting again. +oauth2DeviceConsentDeniedMessage=Consent denied for connecting the device. +oauth2DeviceAuthorizationGrantDisabledMessage=Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client. + +emailVerifyInstruction1=An email with instructions to verify your email address has been sent to you. +emailVerifyInstruction2=Haven''t received a verification code in your email? +emailVerifyInstruction3=to re-send the email. + +emailLinkIdpTitle=Link {0} +emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you. +emailLinkIdp2=Haven''t received a verification code in your email? +emailLinkIdp3=to re-send the email. +emailLinkIdp4=If you already verified the email in different browser +emailLinkIdp5=to continue. + +backToLogin=« Back to Login + +emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password. + +copyCodeInstruction=Please copy this code and paste it into your application: + +pageExpiredTitle=Page has expired +pageExpiredMsg1=To restart the login process +pageExpiredMsg2=To continue the login process + +personalInfo=Personal Info: +role_admin=Admin +role_realm-admin=Realm Admin +role_create-realm=Create realm +role_create-client=Create client +role_view-realm=View realm +role_view-users=View users +role_view-applications=View applications +role_view-clients=View clients +role_view-events=View events +role_view-identity-providers=View identity providers +role_manage-realm=Manage realm +role_manage-users=Manage users +role_manage-applications=Manage applications +role_manage-identity-providers=Manage identity providers +role_manage-clients=Manage clients +role_manage-events=Manage events +role_view-profile=View profile +role_manage-account=Manage account +role_manage-account-links=Manage account links +role_read-token=Read token +role_offline-access=Offline access +client_account=Account +client_account-console=Account Console +client_security-admin-console=Security Admin Console +client_admin-cli=Admin CLI +client_realm-management=Realm Management +client_broker=Broker + +requiredFields=Required fields + +invalidUserMessage=Invalid username or password. +invalidUsernameMessage=Invalid username. +invalidUsernameOrEmailMessage=Invalid username or email. +invalidPasswordMessage=Invalid password. +invalidEmailMessage=Invalid email address. +accountDisabledMessage=Account is disabled, contact your administrator. +accountTemporarilyDisabledMessage=Account is temporarily disabled; contact your administrator or retry later. +expiredCodeMessage=Login timeout. Please sign in again. +expiredActionMessage=Action expired. Please continue with login now. +expiredActionTokenNoSessionMessage=Action expired. +expiredActionTokenSessionExistsMessage=Action expired. Please start again. + +missingFirstNameMessage=Please specify first name. +missingLastNameMessage=Please specify last name. +missingEmailMessage=Please specify email. +missingUsernameMessage=Please specify username. +missingPasswordMessage=Please specify password. +missingTotpMessage=Please specify authenticator code. +missingTotpDeviceNameMessage=Please specify device name. +notMatchPasswordMessage=Passwords don''t match. + +invalidPasswordExistingMessage=Invalid existing password. +invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted. +invalidPasswordConfirmMessage=Password confirmation doesn''t match. +invalidTotpMessage=Invalid authenticator code. + +usernameExistsMessage=Username already exists. +emailExistsMessage=Email already exists. + +federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account. +federatedIdentityUnavailableMessage=User {0} authenticated with identity provider {1} does not exists. Please contact your administrator. + +confirmLinkIdpTitle=Account already exists +federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue? +federatedIdentityConfirmReauthenticateMessage=Authenticate to link your account with {0} +nestedFirstBrokerFlowMessage=The {0} user {1} is not linked to any known user. +confirmLinkIdpReviewProfile=Review profile +confirmLinkIdpContinue=Add to existing account + +configureTotpMessage=You need to set up Mobile Authenticator to activate your account. +updateProfileMessage=You need to update your user profile to activate your account. +updatePasswordMessage=You need to change your password to activate your account. +resetPasswordMessage=You need to change your password. +verifyEmailMessage=You need to verify your email address to activate your account. +linkIdpMessage=You need to verify your email address to link your account with {0}. + +emailSentMessage=You should receive an email shortly with further instructions. +emailSendErrorMessage=Failed to send email, please try again later. + +accountUpdatedMessage=Your account has been updated. +accountPasswordUpdatedMessage=Your password has been updated. + +delegationCompleteHeader=Login Successful +delegationCompleteMessage=You may close this browser window and go back to your console application. +delegationFailedHeader=Login Failed +delegationFailedMessage=You may close this browser window and go back to your console application and try logging in again. + +noAccessMessage=No access + +invalidPasswordMinLengthMessage=Invalid password: minimum length {0}. +invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits. +invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters. +invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters. +invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters. +invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username. +invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email. +invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s). +invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords. +invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies. + +failedToProcessResponseMessage=Failed to process response +httpsRequiredMessage=HTTPS required +realmNotEnabledMessage=Realm not enabled +invalidRequestMessage=Invalid Request +failedLogout=Logout failed +unknownLoginRequesterMessage=Unknown login requester +loginRequesterNotEnabledMessage=Login requester not enabled +bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login +standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client. +implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client. +invalidRedirectUriMessage=Invalid redirect uri +unsupportedNameIdFormatMessage=Unsupported NameIDFormat +invalidRequesterMessage=Invalid requester +registrationNotAllowedMessage=Registration not allowed +resetCredentialNotAllowedMessage=Reset Credential not allowed + +permissionNotApprovedMessage=Permission not approved. +noRelayStateInResponseMessage=No relay state in response from identity provider. +insufficientPermissionMessage=Insufficient permissions to link identities. +couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider. +couldNotObtainTokenMessage=Could not obtain token from identity provider. +unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider. +unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider. +identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider. +couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider. +unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider. +invalidAccessCodeMessage=Invalid access code. +sessionNotActiveMessage=Session not active. +invalidCodeMessage=An error occurred, please login again through your application. +identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider +identityProviderMissingStateMessage=Missing state parameter in response from identity provider. +identityProviderNotFoundMessage=Could not find an identity provider with the identifier. +identityProviderLinkSuccess=You successfully verified your email. Please go back to your original browser and continue there with the login. +staleCodeMessage=This page is no longer valid, please go back to your application and sign in again +realmSupportsNoCredentialsMessage=Realm does not support any credential type. +credentialSetupRequired=Cannot login, credential setup required. +identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with. +emailVerifiedMessage=Your email address has been verified. +staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email. +identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. +confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account. +confirmEmailAddressVerification=Confirm validity of e-mail address {0}. +confirmExecutionOfActions=Perform the following action(s) + +locale_ca=Catal\u00E0 +locale_cs=\u010Ce\u0161tina +locale_da=Dansk +locale_de=Deutsch +locale_en=English +locale_es=Espa\u00F1ol +locale_fr=Fran\u00E7ais +locale_hu=Magyar +locale_it=Italiano +locale_ja=\u65E5\u672C\u8A9E +locale_lt=Lietuvi\u0173 +locale_nl=Nederlands +locale_no=Norsk +locale_pl=Polski +locale_pt_BR=Portugu\u00EAs (Brasil) +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 +locale_sk=Sloven\u010Dina +locale_sv=Svenska +locale_tr=T\u00FCrk\u00E7e +locale_zh-CN=\u4E2D\u6587\u7B80\u4F53 + +backToApplication=« Back to Application +missingParameterMessage=Missing parameters\: {0} +clientNotFoundMessage=Client not found. +clientDisabledMessage=Client disabled. +invalidParameterMessage=Invalid parameter\: {0} +alreadyLoggedIn=You are already logged in. +differentUserAuthenticated=You are already authenticated as different user ''{0}'' in this session. Please sign out first. +brokerLinkingSessionExpired=Requested broker account linking, but current session is no longer valid. +proceedWithAction=» Click here to proceed + +requiredAction.CONFIGURE_TOTP=Configure OTP +requiredAction.terms_and_conditions=Terms and Conditions +requiredAction.UPDATE_PASSWORD=Update Password +requiredAction.UPDATE_PROFILE=Update Profile +requiredAction.VERIFY_EMAIL=Verify Email + +doX509Login=You will be logged in as\: +clientCertificate=X509 client certificate\: +noCertificate=[No Certificate] + + +pageNotFound=Page not found +internalServerError=An internal server error has occurred + +console-username=Username: +console-password=Password: +console-otp=One Time Password: +console-new-password=New Password: +console-confirm-password=Confirm Password: +console-update-password=Update of your password is required. +console-verify-email=You need to verify your email address. We sent an email to {0} that contains a verification code. Please enter this code into the input below. +console-email-code=Email Code: +console-accept-terms=Accept Terms? [y/n]: +console-accept=y + +# Openshift messages +openshift.scope.user_info=User information +openshift.scope.user_check-access=User access information +openshift.scope.user_full=Full Access +openshift.scope.list-projects=List projects + +# SAML authentication +saml.post-form.title=Authentication Redirect +saml.post-form.message=Redirecting, please wait. +saml.post-form.js-disabled=JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue. +saml.artifactResolutionServiceInvalidResponse=Unable to resolve artifact. + +#authenticators +otp-display-name=Authenticator Application +otp-help-text=Enter a verification code from authenticator application. +password-display-name=Password +password-help-text=Sign in by entering your password. +auth-username-form-display-name=Username +auth-username-form-help-text=Start sign in by entering your username +auth-username-password-form-display-name=Username and password +auth-username-password-form-help-text=Sign in by entering your username and password. + +# WebAuthn +webauthn-display-name=Security Key +webauthn-help-text=Use your security key to sign in. +webauthn-passwordless-display-name=Security Key +webauthn-passwordless-help-text=Use your security key for passwordless sign in. +webauthn-login-title=Security Key login +webauthn-registration-title=Security Key Registration +webauthn-available-authenticators=Available authenticators +webauthn-unsupported-browser-text=WebAuthn is not supported by this browser. Try another one or contact your administrator. + +# WebAuthn Error +webauthn-error-title=Security Key Error +webauthn-error-registration=Failed to register your Security key.
    {0} +webauthn-error-api-get=Failed to authenticate by the Security key.
    {0} +webauthn-error-different-user=First authenticated user is not the one authenticated by the Security key. +webauthn-error-auth-verification=Security key authentication result is invalid.
    {0} +webauthn-error-register-verification=Security key registration result is invalid.
    {0} +webauthn-error-user-not-found=Unknown user authenticated by the Security key. + +# Identity provider +identity-provider-redirector=Connect with another Identity Provider +identity-provider-login-label=Or sign in with + +finalDeletionConfirmation=If you delete your account, it cannot be restored. To keep your account, click Cancel. +irreversibleAction=This action is irreversible +deleteAccountConfirm=Delete account confirmation + +deletingImplies=Deleting your account implies: +errasingData=Erasing all your data +loggingOutImmediately=Logging you out immediately +accountUnusable=Any subsequent use of the application will not be possible with this account +userDeletedSuccessfully=User deleted successfully + +access-denied=Access denied \ No newline at end of file diff --git a/base/login/register.ftl b/base/login/register.ftl new file mode 100755 index 0000000..db50984 --- /dev/null +++ b/base/login/register.ftl @@ -0,0 +1,141 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section> + <#if section = "header"> + ${msg("registerTitle")} + <#elseif section = "form"> +
    +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('firstName')> + + ${kcSanitize(messagesPerField.get('firstName'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('lastName')> + + ${kcSanitize(messagesPerField.get('lastName'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('email')> + + ${kcSanitize(messagesPerField.get('email'))?no_esc} + + +
    +
    + + <#if !realm.registrationEmailAsUsername> +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
    +
    + + + <#if passwordRequired??> +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
    +
    + +
    +
    + +
    +
    + + + <#if messagesPerField.existsError('password-confirm')> + + ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} + + +
    +
    + + + <#if recaptchaRequired??> +
    +
    +
    +
    +
    + + + +
    + + \ No newline at end of file diff --git a/base/login/resources/js/base64url.js b/base/login/resources/js/base64url.js new file mode 100644 index 0000000..64555bf --- /dev/null +++ b/base/login/resources/js/base64url.js @@ -0,0 +1,114 @@ +// for embedded scripts, quoted and modified from https://github.com/swansontec/rfc4648.js by William Swanson +'use strict'; +var base64url = base64url || {}; +(function(base64url) { + + function parse (string, encoding, opts = {}) { + // Build the character lookup table: + if (!encoding.codes) { + encoding.codes = {}; + for (let i = 0; i < encoding.chars.length; ++i) { + encoding.codes[encoding.chars[i]] = i; + } + } + + // The string must have a whole number of bytes: + if (!opts.loose && (string.length * encoding.bits) & 7) { + throw new SyntaxError('Invalid padding'); + } + + // Count the padding bytes: + let end = string.length; + while (string[end - 1] === '=') { + --end; + + // If we get a whole number of bytes, there is too much padding: + if (!opts.loose && !(((string.length - end) * encoding.bits) & 7)) { + throw new SyntaxError('Invalid padding'); + } + } + + // Allocate the output: + const out = new (opts.out || Uint8Array)(((end * encoding.bits) / 8) | 0); + + // Parse the data: + let bits = 0; // Number of bits currently in the buffer + let buffer = 0; // Bits waiting to be written out, MSB first + let written = 0; // Next byte to write + for (let i = 0; i < end; ++i) { + // Read one character from the string: + const value = encoding.codes[string[i]]; + if (value === void 0) { + throw new SyntaxError('Invalid character ' + string[i]); + } + + // Append the bits to the buffer: + buffer = (buffer << encoding.bits) | value; + bits += encoding.bits; + + // Write out some bits if the buffer has a byte's worth: + if (bits >= 8) { + bits -= 8; + out[written++] = 0xff & (buffer >> bits); + } + } + + // Verify that we have received just enough bits: + if (bits >= encoding.bits || 0xff & (buffer << (8 - bits))) { + throw new SyntaxError('Unexpected end of data'); + } + + return out + } + + function stringify (data, encoding, opts = {}) { + const { pad = true } = opts; + const mask = (1 << encoding.bits) - 1; + let out = ''; + + let bits = 0; // Number of bits currently in the buffer + let buffer = 0; // Bits waiting to be written out, MSB first + for (let i = 0; i < data.length; ++i) { + // Slurp data into the buffer: + buffer = (buffer << 8) | (0xff & data[i]); + bits += 8; + + // Write out as much as we can: + while (bits > encoding.bits) { + bits -= encoding.bits; + out += encoding.chars[mask & (buffer >> bits)]; + } + } + + // Partial character: + if (bits) { + out += encoding.chars[mask & (buffer << (encoding.bits - bits))]; + } + + // Add padding characters until we hit a byte boundary: + if (pad) { + while ((out.length * encoding.bits) & 7) { + out += '='; + } + } + + return out + } + + const encoding = { + chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', + bits: 6 + } + + base64url.decode = function (string, opts) { + return parse(string, encoding, opts); + } + + base64url.encode = function (data, opts) { + return stringify(data, encoding, opts) + } + + return base64url; +}(base64url)); + + diff --git a/base/login/saml-post-form.ftl b/base/login/saml-post-form.ftl new file mode 100644 index 0000000..94b0c30 --- /dev/null +++ b/base/login/saml-post-form.ftl @@ -0,0 +1,25 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "header"> + ${msg("saml.post-form.title")} + <#elseif section = "form"> + +

    ${msg("saml.post-form.message")}

    +
    + <#if samlPost.SAMLRequest??> + + + <#if samlPost.SAMLResponse??> + + + <#if samlPost.relayState??> + + + + +
    + + diff --git a/base/login/select-authenticator.ftl b/base/login/select-authenticator.ftl new file mode 100644 index 0000000..c4097db --- /dev/null +++ b/base/login/select-authenticator.ftl @@ -0,0 +1,43 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayInfo=false; section> + <#if section = "header" || section = "show-username"> + + <#if section = "header"> + ${msg("loginChooseAuthenticator")} + + <#elseif section = "form"> + +
    +
    + <#list auth.authenticationSelections as authenticationSelection> +
    + +
    + +
    +
    +
    + ${msg('${authenticationSelection.displayName}')} +
    +
    + ${msg('${authenticationSelection.helpText}')} +
    +
    +
    +
    + +
    +
    + + +
    +
    + + + + diff --git a/base/login/template.ftl b/base/login/template.ftl new file mode 100644 index 0000000..fa1d05c --- /dev/null +++ b/base/login/template.ftl @@ -0,0 +1,152 @@ +<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false showAnotherWayIfPresent=true> + + + + + + + + + <#if properties.meta?has_content> + <#list properties.meta?split(' ') as meta> + + + + ${msg("loginTitle",(realm.displayName!''))} + + <#if properties.stylesCommon?has_content> + <#list properties.stylesCommon?split(' ') as style> + + + + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(' ') as script> + + + + <#if scripts??> + <#list scripts as script> + + + + + + +
    +
    +
    ${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}
    +
    +
    +
    + <#if realm.internationalizationEnabled && locale.supported?size gt 1> +
    +
    +
    + ${locale.current} +
      + <#list locale.supported as l> +
    • + ${l.label} +
    • + +
    +
    +
    +
    + + <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> + <#if displayRequiredFields> +
    +
    + * ${msg("requiredFields")} +
    +
    +

    <#nested "header">

    +
    +
    + <#else> +

    <#nested "header">

    + + <#else> + <#if displayRequiredFields> +
    +
    + * ${msg("requiredFields")} +
    +
    + <#nested "show-username"> +
    + + + + +
    +
    +
    + <#else> + <#nested "show-username"> +
    + + + + +
    + + +
    +
    +
    + + <#-- App-initiated actions should not see warning messages about the need to complete the action --> + <#-- during login. --> + <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)> +
    +
    + <#if message.type = 'success'> + <#if message.type = 'warning'> + <#if message.type = 'error'> + <#if message.type = 'info'> +
    + ${kcSanitize(message.summary)?no_esc} +
    + + + <#nested "form"> + + <#if auth?has_content && auth.showTryAnotherWayLink() && showAnotherWayIfPresent> +
    + +
    + + + <#if displayInfo> +
    +
    + <#nested "info"> +
    +
    + +
    +
    + +
    +
    + + + diff --git a/base/login/terms.ftl b/base/login/terms.ftl new file mode 100755 index 0000000..687b192 --- /dev/null +++ b/base/login/terms.ftl @@ -0,0 +1,15 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=false; section> + <#if section = "header"> + ${msg("termsTitle")} + <#elseif section = "form"> +
    + ${kcSanitize(msg("termsText"))?no_esc} +
    +
    + + +
    +
    + + diff --git a/base/login/webauthn-authenticate.ftl b/base/login/webauthn-authenticate.ftl new file mode 100644 index 0000000..c42174f --- /dev/null +++ b/base/login/webauthn-authenticate.ftl @@ -0,0 +1,115 @@ + <#import "template.ftl" as layout> + <@layout.registrationLayout showAnotherWayIfPresent=false; section> + <#if section = "title"> + title + <#elseif section = "header"> + ${kcSanitize(msg("webauthn-login-title"))?no_esc} + <#elseif section = "form"> + +
    +
    + + + + + + +
    +
    + + <#if authenticators??> +
    + <#list authenticators.authenticators as authenticator> + + +
    + + + + + + <#elseif section = "info"> + + + diff --git a/base/login/webauthn-error.ftl b/base/login/webauthn-error.ftl new file mode 100644 index 0000000..ed904f9 --- /dev/null +++ b/base/login/webauthn-error.ftl @@ -0,0 +1,55 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=true; section> + <#if section = "header"> + ${kcSanitize(msg("webauthn-error-title"))?no_esc} + <#elseif section = "form"> + + + +
    + + +
    + + <#if authenticators??> + + + + + + + + <#list authenticators.authenticators as authenticator> + + + + + +
    ${kcSanitize(msg("webauthn-available-authenticators"))?no_esc}
    + ${kcSanitize(authenticator.label)?no_esc} +
    + + + + + <#if isAppInitiatedAction??> +
    + +
    + + + + \ No newline at end of file diff --git a/base/login/webauthn-register.ftl b/base/login/webauthn-register.ftl new file mode 100644 index 0000000..4a30722 --- /dev/null +++ b/base/login/webauthn-register.ftl @@ -0,0 +1,174 @@ + <#import "template.ftl" as layout> + <@layout.registrationLayout; section> + <#if section = "title"> + title + <#elseif section = "header"> + + ${kcSanitize(msg("webauthn-registration-title"))?no_esc} + <#elseif section = "form"> + +
    +
    + + + + + +
    +
    + + + + + + <#if !isSetRetry?has_content && isAppInitiatedAction?has_content> + +
    + +
    + <#else> + + + + + \ No newline at end of file diff --git a/keycloak.v2/account/index.ftl b/keycloak.v2/account/index.ftl new file mode 100644 index 0000000..c5975c7 --- /dev/null +++ b/keycloak.v2/account/index.ftl @@ -0,0 +1,279 @@ + + + + ${msg("accountManagementTitle")} + + + + + + + + + <#if properties.favIcon?has_content> + + <#else> + + + + + + <#if properties.developmentMode?has_content && properties.developmentMode == "true"> + + + + + + + <#if properties.extensions?has_content> + <#list properties.extensions?split(' ') as script> + <#if properties.developmentMode?has_content && properties.developmentMode == "true"> + + <#else> + + + + + + <#if properties.scripts?has_content> + <#list properties.scripts?split(' ') as script> + + + + + + + <#if properties.styles?has_content> + <#list properties.styles?split(' ') as style> + + + + + + + + + + + + + + + +
    +
    + <#if properties.logo?has_content> + Logo + <#else> + Logo + +

    ${msg("loadingMessage")}

    +
    + + + + +
    +
    +
    +
    +
    + + + + + + + diff --git a/keycloak.v2/account/messages/messages_en.properties b/keycloak.v2/account/messages/messages_en.properties new file mode 100644 index 0000000..c7087c9 --- /dev/null +++ b/keycloak.v2/account/messages/messages_en.properties @@ -0,0 +1,121 @@ +# Put new messages for Account Console Here +# Feel free to use any existing messages from the base theme +pageNotFound=Page Not Found +forbidden=Forbidden +needAccessRights=You do not have access rights to this request. Contact your administrator. +invalidRoute={0} is not a valid route. +actionRequiresIDP=This action requires redirection to your identity provider. +actionNotDefined=No Action defined +continue=Continue +refreshPage=Refresh the page +done=Done +cancel=Cancel +remove=Remove +update=Update +loadingMessage=Account Console loading ... +unknownUser=Anonymous +fullName={0} {1} + +selectLocale=Select a locale +doSignIn=Sign In + +# Device Activity Page +signedInDevices=Signed In Devices +signedInDevicesExplanation=Sign out any device that is unfamiliar. +signOutWarning=Sign out the session? +signOutAllDevices=Sign Out All Devices +signOutAllDevicesWarning=This action will sign out all the devices that have signed in to your account, including the current device you are using. +recentlyUsedDevices=Recently Used Devices +recentlyUsedDevicesExplanation=Devices used in the last month, but not currently logged in. +lastAccess=Last Access +unknownOperatingSystem=Unknown Operating System +currentDevice=Current Device +currentSession=Current Session +signedOutSession=Signed out {0}/{1} +lastAccessedOn=Last accessed on +clients=Clients +startedAt=Started at +expiresAt=Expires at +ipAddress=IP Address + +# Resources Page +resourceName=Resource Name +nextPage=Next +previousPage=Previous +firstPage=First Page +resourceSharedWith=Resource is shared with {0} +and=\ and {0} other users +add=Add +share=Share +edit=Edit +close=Close +unShare=Unshare all +shareSuccess=Resource successfully shared. +unShareSuccess=Resource successfully un-shared. +updateSuccess=Resource successfully updated. +resourceAlreadyShared=Resource is already shared with this user. +resourceNotShared=This resource is not shared. +permissionRequests=Permission requests +permissions=Permissions +unShareAllConfirm=Are you sure you want to completely remove all shares? +userNotFound=No user found with name or email {0} + +# Linked Accounts Page +linkedAccountsTitle=Linked Accounts +linkedAccountsIntroMessage=Manage logins through third-party accounts. +linkedLoginProviders=Linked Login Providers +unlinkedLoginProviders=Unlinked Login Providers +linkedEmpty=No Linked Providers +unlinkedEmpty=No Unlinked Providers +socialLogin=Social Login +systemDefined=System Defined +link=Link Account +unLink=Unlink Account + +# Signing In Page +signingIn=Signing In +signingInSubMessage=Configure ways to sign in. +credentialCreatedAt=Created +successRemovedMessage={0} was removed. +stopUsingCred=Stop using {0}? +removeCred=Remove {0} +setUpNew=Set up {0} +notSetUp={0} is not set up. +two-factor=Two-Factor Authentication +passwordless=Passwordless +unknown=Unknown +password-display-name=Password +password-help-text=Log in by entering your password. +password=My Password +otp-display-name=Authenticator Application +otp-help-text=Enter a verification code from authenticator application. +webauthn-display-name=Security Key +webauthn-help-text=Use your security key to log in. +webauthn-passwordless-display-name=Security Key +webauthn-passwordless-help-text=Use your security key for passwordless log in. +basic-authentication=Basic Authentication +invalidRequestMessage=Invalid Request + +# Applications page +applicationsPageTitle=Applications +internalApp=Internal +thirdPartyApp=Third-party +offlineAccess=Offline Access +inUse=In use +notInUse=Not in use +applicationDetails=Application Details +client=Client +description=Description +baseUrl=URL +accessGrantedOn=Access granted on +removeButton=Remove access +removeModalTitle=Remove Access +removeModalMessage=This will remove the currently granted access permission for {0}. You will need to grant access again if you want to use this app. +confirmButton=Confirm +infoMessage=By clicking 'Remove Access', you will remove granted permissions of this application. This application will no longer use your information. + +#Delete Account page +doDelete=Delete +deleteAccountSummary=Deleting your account will erase all your data and log you out immediately. +deleteAccount=Delete Account +deleteAccountWarning=This is irreversible. All your data will be permanently destroyed, and irretrievable. \ No newline at end of file diff --git a/keycloak.v2/account/resources/.gitignore b/keycloak.v2/account/resources/.gitignore new file mode 100644 index 0000000..a36187a --- /dev/null +++ b/keycloak.v2/account/resources/.gitignore @@ -0,0 +1,14 @@ +# ignore typescript-generated files +*.js +*.js.map + +# ignore log files +*.log + +# Don't ignore these +!WelcomePageScripts.js +!content.json + +public/app.css +public/base.css +public/assets/ \ No newline at end of file diff --git a/keycloak.v2/account/resources/content.json b/keycloak.v2/account/resources/content.json new file mode 100644 index 0000000..ee40981 --- /dev/null +++ b/keycloak.v2/account/resources/content.json @@ -0,0 +1,60 @@ +[ + { + "id": "personal-info", + "path": "personal-info", + "icon": "pf-icon-user", + "label": "personalInfoHtmlTitle", + "descriptionLabel": "personalInfoIntroMessage", + "modulePath": "/content/account-page/AccountPage.js", + "componentName": "AccountPage" + }, + { + "id": "security", + "icon": "pf-icon-security", + "label": "accountSecurityTitle", + "descriptionLabel": "accountSecurityIntroMessage", + "content": [ + { + "id": "signingin", + "path": "security/signingin", + "label": "signingIn", + "modulePath": "/content/signingin-page/SigningInPage.js", + "componentName": "SigningInPage" + }, + { + "id": "device-activity", + "path": "security/device-activity", + "label": "device-activity", + "modulePath": "/content/device-activity-page/DeviceActivityPage.js", + "componentName": "DeviceActivityPage" + }, + { + "id": "linked-accounts", + "path": "security/linked-accounts", + "label": "linkedAccountsHtmlTitle", + "modulePath": "/content/linked-accounts-page/LinkedAccountsPage.js", + "componentName": "LinkedAccountsPage", + "hidden": "!features.isLinkedAccountsEnabled" + } + ] + }, + { + "id": "applications", + "icon": "pf-icon-applications", + "path": "applications", + "label": "applications", + "descriptionLabel": "applicationsIntroMessage", + "modulePath": "/content/applications-page/ApplicationsPage.js", + "componentName": "ApplicationsPage" + }, + { + "id": "resources", + "icon": "pf-icon-repository", + "path": "resources", + "label": "resources", + "descriptionLabel": "resourceIntroMessage", + "modulePath": "/content/my-resources-page/MyResourcesPage.js", + "componentName": "MyResourcesPage", + "hidden": "!features.isMyResourcesEnabled" + } +] \ No newline at end of file diff --git a/common/resources/img/favicon.ico b/keycloak.v2/account/resources/public/favicon.ico similarity index 100% rename from common/resources/img/favicon.ico rename to keycloak.v2/account/resources/public/favicon.ico diff --git a/keycloak.v2/account/resources/public/layout.css b/keycloak.v2/account/resources/public/layout.css new file mode 100644 index 0000000..5a25360 --- /dev/null +++ b/keycloak.v2/account/resources/public/layout.css @@ -0,0 +1,16 @@ +.brand { + height: 35px; +} + +.delete-button { + width: 150px; + height: 50px; +} + +@media (max-width: 320px) { + .delete-button { + width: 120px; + height: 50px; + } + +} \ No newline at end of file diff --git a/keycloak.v2/account/resources/public/logo.svg b/keycloak.v2/account/resources/public/logo.svg new file mode 100644 index 0000000..17edc2c --- /dev/null +++ b/keycloak.v2/account/resources/public/logo.svg @@ -0,0 +1 @@ +keycloak_deliverables \ No newline at end of file diff --git a/keycloak.v2/account/src/.babelrc b/keycloak.v2/account/src/.babelrc new file mode 100644 index 0000000..455e60f --- /dev/null +++ b/keycloak.v2/account/src/.babelrc @@ -0,0 +1,19 @@ +{ + "plugins": [ + [ + "snowpack/assets/babel-plugin.js", + { + "webModulesUrl": "../common/keycloak/web_modules", + "moduleResolution": "node" + } + ], + [ + "@babel/plugin-proposal-class-properties", + {} + ] + ], + "presets": [ + "@babel/preset-react", + "@babel/preset-typescript" + ] +} \ No newline at end of file diff --git a/keycloak.v2/account/src/.eslintrc.js b/keycloak.v2/account/src/.eslintrc.js new file mode 100644 index 0000000..d8ca1a7 --- /dev/null +++ b/keycloak.v2/account/src/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + parser: '@typescript-eslint/parser', + env: { + browser: true, + es6: true, + }, + extends: ['plugin:@typescript-eslint/recommended', 'react-app'], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + impliedStrict: true + }, + ecmaVersion: 2019, + sourceType: 'module', + }, + plugins: [ + 'react', + ], + rules: { + "no-useless-constructor": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-parameter-properties": "off", + "@typescript-eslint/explicit-member-accessibility": "off", + "no-restricted-properties": "off" + }, +}; diff --git a/keycloak.v2/account/src/.gitignore b/keycloak.v2/account/src/.gitignore new file mode 100644 index 0000000..2497619 --- /dev/null +++ b/keycloak.v2/account/src/.gitignore @@ -0,0 +1,2 @@ +# Do not commit, installed at compile time +node_modules \ No newline at end of file diff --git a/keycloak.v2/account/src/app/App.tsx b/keycloak.v2/account/src/app/App.tsx new file mode 100644 index 0000000..9a6f259 --- /dev/null +++ b/keycloak.v2/account/src/app/App.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {KeycloakService} from './keycloak-service/keycloak.service'; + +import {PageNav} from './PageNav'; +import {PageToolbar} from './PageToolbar'; +import {makeRoutes} from './ContentPages'; + +import { + Brand, + Page, + PageHeader, + PageSection, + PageSidebar, +} from '@patternfly/react-core'; + +import { KeycloakContext } from './keycloak-service/KeycloakContext'; + +declare function toggleReact(): void; +declare function isWelcomePage(): boolean; +declare function loggedInUserName(): string; + +declare const brandImg: string; +declare const brandUrl: string; + +export interface AppProps {}; +export class App extends React.Component { + static contextType = KeycloakContext; + context: React.ContextType; + + public constructor(props: AppProps, context: React.ContextType) { + super(props); + this.context = context; + toggleReact(); + } + + public render(): React.ReactNode { + toggleReact(); + + // check login + if (!this.context!.authenticated() && !isWelcomePage()) { + this.context!.login(); + } + + const username = ( + {loggedInUserName()} + ); + const Header = ( + } + toolbar={} + avatar={username} + showNavToggle + /> + ); + + const Sidebar = } />; + + return ( + + + + {makeRoutes()} + + + + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/ContentPages.tsx b/keycloak.v2/account/src/app/ContentPages.tsx new file mode 100644 index 0000000..4e95fb6 --- /dev/null +++ b/keycloak.v2/account/src/app/ContentPages.tsx @@ -0,0 +1,174 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import {Route, Switch} from 'react-router-dom'; +import {NavItem, NavExpandable} from '@patternfly/react-core'; +import {Msg} from './widgets/Msg'; +import {PageNotFound} from './content/page-not-found/PageNotFound'; +import { ForbiddenPage } from './content/forbidden-page/ForbiddenPage'; + +export interface ContentItem { + id?: string; + label: string; + labelParams?: string[]; + hidden?: string; + groupId: string; // computed value + itemId: string; // computed value +}; + +export interface Expansion extends ContentItem { + content: ContentItem[]; +} + +export interface PageDef extends ContentItem { + path: string; +} + +export interface ComponentPageDef extends PageDef { + component: React.ComponentType; +} + +export interface ModulePageDef extends PageDef { + modulePath: string; + componentName: string; + module: React.Component; // computed value +} + +export function isModulePageDef(item: ContentItem): item is ModulePageDef { + return (item as ModulePageDef).modulePath !== undefined; +} + +export function isExpansion(contentItem: ContentItem): contentItem is Expansion { + return (contentItem as Expansion).content !== undefined; +} + +declare const content: ContentItem[]; + +function groupId(group: number): string { + return 'grp-' + group; +} + +function itemId(group: number, item: number): string { + return 'grp-' + group + '_itm-' + item; +} + +function isChildOf(parent: Expansion, child: PageDef): boolean { + for (var item of parent.content) { + if (isExpansion(item) && isChildOf(item, child)) return true; + if (parent.groupId === child.groupId) return true; + } + + return false; +} + +function createNavItems(activePage: PageDef, contentParam: ContentItem[], groupNum: number): React.ReactNode { + if (typeof content === 'undefined') return (); + + const links: React.ReactElement[] = contentParam.map((item: ContentItem) => { + const navLinkId = `nav-link-${item.id}`; + if (isExpansion(item)) { + return + {createNavItems(activePage, item.content, groupNum + 1)} + + } else { + const page: PageDef = item as PageDef; + return + {Msg.localize(page.label, page.labelParams)} + + } + }); + + return ({links}); +} + +export function makeNavItems(activePage: PageDef): React.ReactNode { + console.log({activePage}); + return createNavItems(activePage, content, 0); +} + +function setIds(contentParam: ContentItem[], groupNum: number): number { + if (typeof contentParam === 'undefined') return groupNum; + let expansionGroupNum = groupNum; + + for (let i = 0; i < contentParam.length; i++) { + const item: ContentItem = contentParam[i]; + if (isExpansion(item)) { + item.itemId = itemId(groupNum, i); + expansionGroupNum = expansionGroupNum + 1; + item.groupId = groupId(expansionGroupNum); + expansionGroupNum = setIds(item.content, expansionGroupNum); + console.log('currentGroup=' + (expansionGroupNum)); + } else { + item.groupId = groupId(groupNum); + item.itemId = itemId(groupNum, i); + } + }; + + return expansionGroupNum; +} + +export function initGroupAndItemIds(): void { + setIds(content, 0); + console.log({content}); +} + +// get rid of Expansions and put all PageDef items into a single array +export function flattenContent(pageDefs: ContentItem[]): PageDef[] { + const flat: PageDef[] = []; + + for (let item of pageDefs) { + if (isExpansion(item)) { + flat.push(...flattenContent(item.content)); + } else { + flat.push(item as PageDef); + } + } + + return flat; +} + +export function makeRoutes(): React.ReactNode { + if (typeof content === 'undefined') return (); + + const pageDefs: PageDef[] = flattenContent(content); + + const routes: React.ReactElement[] = pageDefs.map((page: PageDef) => { + if (isModulePageDef(page)) { + const node: React.ReactNode = React.createElement(page.module[page.componentName], {'pageDef': page}); + return node} />; + } else { + const pageDef: ComponentPageDef = page as ComponentPageDef; + return ; + } + }); + + return ( + {routes} + + + ); +} diff --git a/keycloak.v2/account/src/app/Main.tsx b/keycloak.v2/account/src/app/Main.tsx new file mode 100644 index 0000000..da33f16 --- /dev/null +++ b/keycloak.v2/account/src/app/Main.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import {HashRouter} from 'react-router-dom'; + +import {App} from './App'; +import {ContentItem, ModulePageDef, flattenContent, initGroupAndItemIds, isExpansion, isModulePageDef} from './ContentPages'; + +import { KeycloakClient, KeycloakService } from './keycloak-service/keycloak.service'; +import { KeycloakContext } from './keycloak-service/KeycloakContext'; +import { AccountServiceClient } from './account-service/account.service'; +import { AccountServiceContext } from './account-service/AccountServiceContext'; + +declare const keycloak: KeycloakClient; + +declare let isReactLoading: boolean; +declare function toggleReact(): void; +declare const features: { [key: string]: boolean; }; + +export interface MainProps {} +export class Main extends React.Component { + + public constructor(props: MainProps) { + super(props); + } + + public componentDidMount(): void { + isReactLoading = false; + toggleReact(); + } + + public render(): React.ReactNode { + const keycloakService = new KeycloakService(keycloak); + return ( + + + + + + + + ); + } +}; + +declare const resourceUrl: string; +declare let content: ContentItem[]; +const e = React.createElement; + +function removeHidden(items: ContentItem[]): ContentItem[] { + const visible: ContentItem[] = []; + + for (let item of items) { + if (item.hidden && eval(item.hidden)) continue; + + if (isExpansion(item)) { + visible.push(item); + item.content = removeHidden(item.content); + if (item.content.length === 0) { + visible.pop(); // remove empty expansion + } + } else { + visible.push(item); + } + } + + return visible; +} + +content = removeHidden(content); +initGroupAndItemIds(); + +function loadModule(modulePage: ModulePageDef): Promise { + return new Promise ((resolve, reject) => { + console.log('loading: ' + resourceUrl + modulePage.modulePath); + import(resourceUrl + modulePage.modulePath).then( (module: React.Component) => { + modulePage.module = module; + resolve(modulePage); + }).catch((error: Error) => { + console.warn('Unable to load ' + modulePage.label + ' because ' + error.message); + reject(modulePage); + }); + }); +}; + +const moduleLoaders: Promise[] = []; +flattenContent(content).forEach((item: ContentItem) => { + if (isModulePageDef(item)) { + moduleLoaders.push(loadModule(item)); + } +}); + +// load content modules and start +Promise.all(moduleLoaders).then(() => { + const domContainer = document.querySelector('#main_react_container'); + ReactDOM.render(e(Main), domContainer); +}); \ No newline at end of file diff --git a/keycloak.v2/account/src/app/PageNav.tsx b/keycloak.v2/account/src/app/PageNav.tsx new file mode 100644 index 0000000..8f6e2c9 --- /dev/null +++ b/keycloak.v2/account/src/app/PageNav.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; +import {Nav, NavList} from '@patternfly/react-core'; + +import {makeNavItems, flattenContent, ContentItem, PageDef} from './ContentPages'; + +declare const content: ContentItem[]; + +export interface PageNavProps extends RouteComponentProps {} + +export interface PageNavState {} + +class PageNavigation extends React.Component { + + public constructor(props: PageNavProps) { + super(props); + } + + private findActiveItem(): PageDef { + const currentPath: string = this.props.location.pathname; + const items: PageDef[] = flattenContent(content); + const firstItem = items[0]; + for (let item of items) { + const itemPath: string = '/' + item.path; + if (itemPath === currentPath) { + return item; + } + }; + + return firstItem; + } + + public render(): React.ReactNode { + const activeItem: PageDef = this.findActiveItem(); + return ( + + ); + } +} + +export const PageNav = withRouter(PageNavigation); diff --git a/keycloak.v2/account/src/app/PageToolbar.tsx b/keycloak.v2/account/src/app/PageToolbar.tsx new file mode 100644 index 0000000..51aec2d --- /dev/null +++ b/keycloak.v2/account/src/app/PageToolbar.tsx @@ -0,0 +1,86 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {Dropdown, KebabToggle, Toolbar, ToolbarGroup, ToolbarItem} from '@patternfly/react-core'; + +import {ReferrerDropdownItem} from './widgets/ReferrerDropdownItem'; +import {ReferrerLink} from './widgets/ReferrerLink'; +import {Features} from './widgets/features'; +import {LogoutButton,LogoutDropdownItem} from './widgets/Logout'; + +declare const referrerName: string; +declare const features: Features; + +interface PageToolbarProps {} +interface PageToolbarState {isKebabDropdownOpen: boolean} +export class PageToolbar extends React.Component { + private hasReferrer: boolean = typeof referrerName !== 'undefined'; + + public constructor(props: PageToolbarProps) { + super(props); + + this.state = { + isKebabDropdownOpen: false, + }; + } + + private onKebabDropdownToggle = (isKebabDropdownOpen: boolean) => { + this.setState({ + isKebabDropdownOpen + }); + }; + + public render(): React.ReactNode { + const kebabDropdownItems = []; + if (this.hasReferrer) { + kebabDropdownItems.push( + + ) + } + + kebabDropdownItems.push(); + + return ( + + {this.hasReferrer && + + + + + + } + + + + + + + + } + isOpen={this.state.isKebabDropdownOpen} + dropdownItems={kebabDropdownItems} + /> + + + + ); + } +} diff --git a/keycloak.v2/account/src/app/account-service/AccountServiceContext.tsx b/keycloak.v2/account/src/app/account-service/AccountServiceContext.tsx new file mode 100644 index 0000000..f4c3667 --- /dev/null +++ b/keycloak.v2/account/src/app/account-service/AccountServiceContext.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { AccountServiceClient } from './account.service'; + +export const AccountServiceContext = React.createContext(undefined); \ No newline at end of file diff --git a/keycloak.v2/account/src/app/account-service/account.service.ts b/keycloak.v2/account/src/app/account-service/account.service.ts new file mode 100644 index 0000000..1cf62fc --- /dev/null +++ b/keycloak.v2/account/src/app/account-service/account.service.ts @@ -0,0 +1,154 @@ +/* + * Copyright 2018 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import {KeycloakService} from '../keycloak-service/keycloak.service'; +import {ContentAlert} from '../content/ContentAlert'; + +declare const baseUrl: string; + +type ConfigResolve = (config: RequestInit) => void; + +export interface HttpResponse extends Response { + data?: T; +} + +export interface RequestInitWithParams extends RequestInit { + params?: {[name: string]: string | number}; +} + +export class AccountServiceError extends Error { + constructor(public response: HttpResponse) { + super(response.statusText); + } +} + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc. + */ +export class AccountServiceClient { + private kcSvc: KeycloakService; + private accountUrl: string; + + public constructor(keycloakService: KeycloakService) { + this.kcSvc = keycloakService; + this.accountUrl = this.kcSvc.authServerUrl() + 'realms/' + this.kcSvc.realm() + '/account'; + } + + public async doGet(endpoint: string, + config?: RequestInitWithParams): Promise> { + return this.doRequest(endpoint, {...config, method: 'get'}); + } + + public async doDelete(endpoint: string, + config?: RequestInitWithParams): Promise> { + return this.doRequest(endpoint, {...config, method: 'delete'}); + } + + public async doPost(endpoint: string, + body: string | {}, + config?: RequestInitWithParams): Promise> { + return this.doRequest(endpoint, {...config, body: JSON.stringify(body), method: 'post'}); + } + + public async doPut(endpoint: string, + body: string | {}, + config?: RequestInitWithParams): Promise> { + return this.doRequest(endpoint, {...config, body: JSON.stringify(body), method: 'put'}); + } + + public async doRequest(endpoint: string, + config?: RequestInitWithParams): Promise> { + + const response: HttpResponse = await fetch(this.makeUrl(endpoint, config).toString(), + await this.makeConfig(config)); + + try { + response.data = await response.json(); + } catch (e) {} // ignore. Might be empty + + if (!response.ok) { + this.handleError(response); + throw new AccountServiceError(response); + } + + return response; + } + + private handleError(response: HttpResponse): void { + if (response !== null && response.status === 401) { + if (this.kcSvc.authenticated() && !this.kcSvc.audiencePresent()) { + // authenticated and the audience is not present => not allowed + window.location.href = baseUrl + '#/forbidden'; + } else { + // session timed out? + this.kcSvc.login(); + } + } + + if (response !== null && response.status === 403) { + window.location.href = baseUrl + '#/forbidden'; + } + + if (response !== null && response.data != null) { + ContentAlert.danger( + `${response.statusText}: ${response.data['errorMessage'] ? response.data['errorMessage'] : ''} ${response.data['error'] ? response.data['error'] : ''}` + ); + } else { + ContentAlert.danger(response.statusText); + } + } + + private makeUrl(endpoint: string, config?: RequestInitWithParams): URL { + if (endpoint.startsWith('http')) return new URL(endpoint); + const url = new URL(this.accountUrl + endpoint); + + // add request params + if (config && config.hasOwnProperty('params')) { + const params: {[name: string]: string} = config.params as {} || {}; + Object.keys(params).forEach(key => url.searchParams.append(key, params[key])) + } + + return url; + } + + private makeConfig(config: RequestInit = {}): Promise { + return new Promise( (resolve: ConfigResolve) => { + this.kcSvc.getToken() + .then( (token: string) => { + resolve( { + ...config, + headers: {'Content-Type': 'application/json', + ...config.headers, + Authorization: 'Bearer ' + token} + }); + }).catch(() => { + this.kcSvc.login(); + }); + }); + } + +} + +window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => { + event.promise.catch(error => { + if (error instanceof AccountServiceError) { + // We already handled the error. Ignore unhandled rejection. + event.preventDefault(); + } + }); +}); diff --git a/keycloak.v2/account/src/app/content/ContentAlert.tsx b/keycloak.v2/account/src/app/content/ContentAlert.tsx new file mode 100644 index 0000000..8e9e331 --- /dev/null +++ b/keycloak.v2/account/src/app/content/ContentAlert.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import { Alert, AlertActionCloseButton, AlertGroup, AlertVariant } from '@patternfly/react-core'; +import { Msg } from '../widgets/Msg'; + +interface ContentAlertProps { } + +interface ContentAlertState { + alerts: { + key: number; + message: string; + variant: AlertVariant; + }[]; +} +export class ContentAlert extends React.Component { + private static instance: ContentAlert; + + private constructor(props: ContentAlertProps) { + super(props); + + this.state = { + alerts: [] + }; + ContentAlert.instance = this; + } + + /** + * @param message A literal text message or localization key. + */ + public static success(message: string, params?: string[]): void { + ContentAlert.instance.postAlert(AlertVariant.success, message, params); + } + + /** + * @param message A literal text message or localization key. + */ + public static danger(message: string, params?: string[]): void { + ContentAlert.instance.postAlert(AlertVariant.danger, message, params); + } + + /** + * @param message A literal text message or localization key. + */ + public static warning(message: string, params?: string[]): void { + ContentAlert.instance.postAlert(AlertVariant.warning, message, params); + } + + /** + * @param message A literal text message or localization key. + */ + public static info(message: string, params?: string[]): void { + ContentAlert.instance.postAlert(AlertVariant.info, message, params); + } + + private hideAlert = (key: number) => { + this.setState({ alerts: [...this.state.alerts.filter(el => el.key !== key)] }); + } + + private getUniqueId = () => (new Date().getTime()); + + private postAlert = (variant: AlertVariant, message: string, params?: string[]) => { + const alerts = this.state.alerts; + const key = this.getUniqueId(); + alerts.push({ + key, + message: Msg.localize(message, params), + variant + }); + this.setState({ alerts }); + + if (variant !== AlertVariant.danger) { + setTimeout(() => this.hideAlert(key), 8000); + } + } + + public render(): React.ReactNode { + return ( + + {this.state.alerts.map(({ key, variant, message }) => ( + this.hideAlert(key)} + /> + } + key={key} /> + ))} + + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/ContentPage.tsx b/keycloak.v2/account/src/app/content/ContentPage.tsx new file mode 100644 index 0000000..d462c39 --- /dev/null +++ b/keycloak.v2/account/src/app/content/ContentPage.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import {Button, Grid, GridItem, Title, Tooltip} from '@patternfly/react-core'; +import {RedoIcon} from '@patternfly/react-icons'; + +import {Msg} from '../widgets/Msg'; +import {ContentAlert} from './ContentAlert'; + +interface ContentPageProps { + title: string; // Literal title or key into message bundle + introMessage?: string; // Literal message or key into message bundle + onRefresh?: () => void; + children: React.ReactNode; +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc. + */ +export class ContentPage extends React.Component { + + public constructor(props: ContentPageProps) { + super(props); + } + + public render(): React.ReactNode { + return ( + + +
    + + <strong><Msg msgKey={this.props.title}/></strong> + {this.props.onRefresh && + + }> + + + + } + {this.props.introMessage && } + +
    + +
    + {this.props.children} +
    +
    + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx b/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx new file mode 100644 index 0000000..7313a12 --- /dev/null +++ b/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx @@ -0,0 +1,301 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as React from 'react'; +import { ActionGroup, Button, Form, FormGroup, TextInput, Grid, GridItem, Expandable} from '@patternfly/react-core'; + +import { HttpResponse } from '../../account-service/account.service'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { Features } from '../../widgets/features'; +import { Msg } from '../../widgets/Msg'; +import { ContentPage } from '../ContentPage'; +import { ContentAlert } from '../ContentAlert'; +import { LocaleSelector } from '../../widgets/LocaleSelectors'; +import { KeycloakContext } from '../../keycloak-service/KeycloakContext'; +import { KeycloakService } from '../../keycloak-service/keycloak.service'; +import { AIACommand } from '../../util/AIACommand'; + +declare const features: Features; +declare const locale: string; + +interface AccountPageProps { +} + +interface FormFields { + readonly username?: string; + readonly firstName?: string; + readonly lastName?: string; + readonly email?: string; + attributes?: { locale?: [string] }; +} + +interface AccountPageState { + readonly errors: FormFields; + readonly formFields: FormFields; +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc. + */ +export class AccountPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + private isRegistrationEmailAsUsername: boolean = features.isRegistrationEmailAsUsername; + private isEditUserNameAllowed: boolean = features.isEditUserNameAllowed; + private isDeleteAccountAllowed: boolean = features.deleteAccountAllowed; + private readonly DEFAULT_STATE: AccountPageState = { + errors: { + username: '', + firstName: '', + lastName: '', + email: '' + }, + formFields: { + username: '', + firstName: '', + lastName: '', + email: '', + attributes: {} + } + }; + + public state: AccountPageState = this.DEFAULT_STATE; + + public constructor(props: AccountPageProps, context: React.ContextType) { + super(props); + this.context = context; + + this.fetchPersonalInfo(); + } + + private fetchPersonalInfo(): void { + this.context!.doGet("/") + .then((response: HttpResponse) => { + this.setState(this.DEFAULT_STATE); + const formFields = response.data; + if (!formFields!.attributes) { + formFields!.attributes = { locale: [locale] }; + } + else if (!formFields!.attributes.locale) { + formFields!.attributes.locale = [locale]; + } + + this.setState({...{ formFields: formFields as FormFields }}); + }); + } + + private handleCancel = (): void => { + this.fetchPersonalInfo(); + } + + private handleChange = (value: string, event: React.FormEvent) => { + const target = event.currentTarget; + const name = target.name; + + this.setState({ + errors: { ...this.state.errors, [name]: target.validationMessage }, + formFields: { ...this.state.formFields, [name]: value } + }); + } + + private handleSubmit = (event: React.FormEvent): void => { + event.preventDefault(); + const form = event.target as HTMLFormElement; + const isValid = form.checkValidity(); + if (isValid) { + const reqData: FormFields = { ...this.state.formFields }; + this.context!.doPost("/", reqData) + .then(() => { + ContentAlert.success('accountUpdatedMessage'); + if (locale !== this.state.formFields.attributes!.locale![0]) { + window.location.reload(); + } + }); + } else { + const formData = new FormData(form); + const validationMessages = Array.from(formData.keys()).reduce((acc, key) => { + acc[key] = form.elements[key].validationMessage + return acc + }, {}); + this.setState({ + errors: { ...validationMessages }, + formFields: this.state.formFields + }); + } + + } + + private handleDelete = (keycloak: KeycloakService): void => { + new AIACommand(keycloak, "delete_account").execute(); + } + + public render(): React.ReactNode { + const fields: FormFields = this.state.formFields; + return ( + +
    this.handleSubmit(event)}> + {!this.isRegistrationEmailAsUsername && + + {this.isEditUserNameAllowed && } + {!this.isEditUserNameAllowed && } + + } + + + + + + + + + + + + + {features.isInternationalizationEnabled && + this.setState({ + errors: this.state.errors, + formFields: { ...this.state.formFields, attributes: { ...this.state.formFields.attributes, locale: [value] }} + })} + /> + } + + + + +
    + + { this.isDeleteAccountAllowed && +
    + + + +

    + +

    +
    + + + { (keycloak: KeycloakService) => ( + + )} + + + + +
    + +
    +
    } +
    + ); + } + + private UsernameInput = () => ( + + + ); + + private RestrictedUsernameInput = () => ( + + + ); +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/aia-page/AppInitiatedActionPage.tsx b/keycloak.v2/account/src/app/content/aia-page/AppInitiatedActionPage.tsx new file mode 100644 index 0000000..b5bb15d --- /dev/null +++ b/keycloak.v2/account/src/app/content/aia-page/AppInitiatedActionPage.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; + +import {AIACommand} from '../../util/AIACommand'; +import {PageDef} from '../../ContentPages'; +import {Msg} from '../../widgets/Msg'; + +import { + Title, + TitleLevel, + Button, + EmptyState, + EmptyStateVariant, + EmptyStateIcon, + EmptyStateBody +} from '@patternfly/react-core'; +import { PassportIcon } from '@patternfly/react-icons'; +import { KeycloakService } from '../../keycloak-service/keycloak.service'; +import { KeycloakContext } from '../../keycloak-service/KeycloakContext'; + +// Note: This class demonstrates two features of the ContentPages framework: +// 1) The PageDef is available as a React property. +// 2) You can add additional custom properties to the PageDef. In this case, +// we add a value called kcAction in content.js and access it by extending the +// PageDef interface. +interface ActionPageDef extends PageDef { + kcAction: string; +} + +// Extend RouteComponentProps to get access to router information such as +// the hash-routed path associated with this page. See this.props.location.pathname +// as used below. +interface AppInitiatedActionPageProps extends RouteComponentProps { + pageDef: ActionPageDef; +} + +/** + * @author Stan Silvert + */ +class ApplicationInitiatedActionPage extends React.Component { + + public constructor(props: AppInitiatedActionPageProps) { + super(props); + } + + private handleClick = (keycloak: KeycloakService): void => { + new AIACommand(keycloak, this.props.pageDef.kcAction).execute(); + } + + public render(): React.ReactNode { + return ( + + + + <Msg msgKey={this.props.pageDef.label} params={this.props.pageDef.labelParams}/> + + + + + + { keycloak => ( + + )} + + + + ); + } +}; + +// Note that the class name is not exported above. To get access to the router, +// we use withRouter() and export a different name. +export const AppInitiatedActionPage = withRouter(ApplicationInitiatedActionPage); \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/applications-page/ApplicationsPage.tsx b/keycloak.v2/account/src/app/content/applications-page/ApplicationsPage.tsx new file mode 100644 index 0000000..a1d7c01 --- /dev/null +++ b/keycloak.v2/account/src/app/content/applications-page/ApplicationsPage.tsx @@ -0,0 +1,237 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import { + DataList, + DataListItem, + DataListItemRow, + DataListCell, + DataListToggle, + DataListContent, + DataListItemCells, + Grid, + GridItem, + Button, +} from '@patternfly/react-core'; + +import { InfoAltIcon, CheckIcon, BuilderImageIcon, ExternalLinkAltIcon } from '@patternfly/react-icons'; +import { ContentPage } from '../ContentPage'; +import { ContinueCancelModal } from '../../widgets/ContinueCancelModal'; +import { HttpResponse } from '../../account-service/account.service'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { Msg } from '../../widgets/Msg'; + +declare const locale: string; + +export interface ApplicationsPageProps { +} + +export interface ApplicationsPageState { + isRowOpen: boolean[]; + applications: Application[]; +} + +export interface GrantedScope { + displayTest: string; + id: string; + name: string; +} + +export interface Consent { + createDate: number; + grantedScopes: GrantedScope[]; + lastUpdatedDate: number; +} + +interface Application { + effectiveUrl: string; + clientId: string; + clientName: string; + consent: Consent; + description: string; + inUse: boolean; + offlineAccess: boolean; + userConsentRequired: boolean; + scope: string[]; +} + +export class ApplicationsPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: ApplicationsPageProps, context: React.ContextType) { + super(props); + this.context = context; + this.state = { + isRowOpen: [], + applications: [] + }; + + this.fetchApplications(); + } + + private removeConsent = (clientId: string) => { + this.context!.doDelete("/applications/" + clientId + "/consent") + .then(() => { + this.fetchApplications(); + }); + } + + private onToggle = (row: number): void => { + const newIsRowOpen: boolean[] = this.state.isRowOpen; + newIsRowOpen[row] = !newIsRowOpen[row]; + this.setState({ isRowOpen: newIsRowOpen }); + }; + + private fetchApplications(): void { + this.context!.doGet("/applications") + .then((response: HttpResponse) => { + const applications = response.data || []; + this.setState({ + isRowOpen: new Array(applications.length).fill(false), + applications: applications + }); + }); + } + + private elementId(item: string, application: Application): string { + return `application-${item}-${application.clientId}`; + } + + public render(): React.ReactNode { + return ( + + + + + // invisible toggle allows headings to line up properly + + + + + + , + + + , + + + , + ]} + /> + + + {this.state.applications.map((application: Application, appIndex: number) => { + return ( + + + this.onToggle(appIndex)} + isExpanded={this.state.isRowOpen[appIndex]} + id={this.elementId('toggle', application)} + aria-controls={this.elementId("expandable", application)} + /> + + + , + + {application.userConsentRequired ? Msg.localize('thirdPartyApp') : Msg.localize('internalApp')} + {application.offlineAccess ? ', ' + Msg.localize('offlineAccess') : ''} + , + + {application.inUse ? Msg.localize('inUse') : Msg.localize('notInUse')} + + ]} + /> + + + +
    + {Msg.localize('client') + ': '} {application.clientId} + {application.description && + {Msg.localize('description') + ': '} {application.description} + } + URL: {application.effectiveUrl.split('"')} + {application.consent && + + + Has access to: + + {application.consent.grantedScopes.map((scope: GrantedScope, scopeIndex: number) => { + return ( + + {scope.name} + + ) + })} + {Msg.localize('accessGrantedOn') + ': '} + {new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric' + }).format(application.consent.createDate)} + + + } +
    +
    + {(application.consent || application.offlineAccess) && + +
    + + + this.removeConsent(application.clientId)} // required + /> + + + {Msg.localize('infoMessage')} +
    + } +
    +
    + ) + })} +
    +
    + ); + } +}; diff --git a/keycloak.v2/account/src/app/content/authenticator-page/AuthenticatorPage.tsx b/keycloak.v2/account/src/app/content/authenticator-page/AuthenticatorPage.tsx new file mode 100644 index 0000000..93314e1 --- /dev/null +++ b/keycloak.v2/account/src/app/content/authenticator-page/AuthenticatorPage.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +export interface AuthenticatorPageProps { +} + +export class AuthenticatorPage extends React.Component { + + public constructor(props: AuthenticatorPageProps) { + super(props); + } + + public render(): React.ReactNode { + return ( +
    +

    Hello Authenticator Page

    +
    + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/device-activity-page/DeviceActivityPage.tsx b/keycloak.v2/account/src/app/content/device-activity-page/DeviceActivityPage.tsx new file mode 100644 index 0000000..59c80c5 --- /dev/null +++ b/keycloak.v2/account/src/app/content/device-activity-page/DeviceActivityPage.tsx @@ -0,0 +1,320 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {HttpResponse} from '../../account-service/account.service'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import TimeUtil from '../../util/TimeUtil'; + +import { + Bullseye, + DataList, + DataListItem, + DataListItemRow, + DataListCell, + DataListItemCells, + Grid, + GridItem, + Stack, + StackItem +} from '@patternfly/react-core'; + +import { + AmazonIcon, + ChromeIcon, + EdgeIcon, + FirefoxIcon, + GlobeIcon, + InternetExplorerIcon, + OperaIcon, + SafariIcon, + YandexInternationalIcon, +} from '@patternfly/react-icons'; + +import {Msg} from '../../widgets/Msg'; +import {ContinueCancelModal} from '../../widgets/ContinueCancelModal'; +import { KeycloakService } from '../../keycloak-service/keycloak.service'; +import { KeycloakContext } from '../../keycloak-service/KeycloakContext'; + +import {ContentPage} from '../ContentPage'; +import { ContentAlert } from '../ContentAlert'; + +export interface DeviceActivityPageProps { +} + +export interface DeviceActivityPageState { + devices: Device[]; +} + +interface Device { + browser: string; + current: boolean; + device: string; + ipAddress: string; + lastAccess: number; + mobile: boolean; + os: string; + osVersion: string; + sessions: Session[]; +} + +interface Session { + browser: string; + current: boolean; + clients: Client[]; + expires: number; + id: string; + ipAddress: string; + lastAccess: number; + started: number; +} + +interface Client { + clientId: string; + clientName: string; +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc. + */ +export class DeviceActivityPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: DeviceActivityPageProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + devices: [] + }; + + this.fetchDevices(); + } + + private signOutAll = (keycloakService: KeycloakService) => { + this.context!.doDelete("/sessions") + .then( () => { + keycloakService.logout(); + }); + } + + private signOutSession = (device: Device, session: Session) => { + this.context!.doDelete("/sessions/" + session.id) + .then (() => { + this.fetchDevices(); + ContentAlert.success('signedOutSession', [session.browser, device.os]); + }); + } + + private fetchDevices(): void { + this.context!.doGet("/sessions/devices") + .then((response: HttpResponse) => { + console.log({response}); + + let devices: Device[] = this.moveCurrentToTop(response.data as Device[]); + + this.setState({ + devices: devices + }); + + }); + } + + // current device and session should display at the top of their respective lists + private moveCurrentToTop(devices: Device[]): Device[] { + let currentDevice: Device = devices[0]; + + devices.forEach((device: Device, index: number) => { + if (device.current) { + currentDevice = device; + devices.splice(index, 1); + devices.unshift(device); + } + }); + + currentDevice.sessions.forEach((session: Session, index: number) => { + if (session.current) { + const currentSession: Session[] = currentDevice.sessions.splice(index, 1); + currentDevice.sessions.unshift(currentSession[0]); + } + }); + + return devices; + } + + private time(time: number): string { + return TimeUtil.format(time * 1000); + } + + private elementId(item: string, session: Session): string { + return `session-${session.id.substring(0,7)}-${item}`; + } + + private findBrowserIcon(session: Session): React.ReactNode { + const browserName: string = session.browser.toLowerCase(); + if (browserName.includes("chrom")) return (); // chrome or chromium + if (browserName.includes("firefox")) return (); + if (browserName.includes("edge")) return (); + if (browserName.startsWith("ie/")) return (); + if (browserName.includes("safari")) return (); + if (browserName.includes("opera")) return (); + if (browserName.includes("yandex")) return (); + if (browserName.includes("amazon")) return (); + + return (); + } + + private findOS(device: Device): string { + if (device.os.toLowerCase().includes('unknown')) return Msg.localize('unknownOperatingSystem'); + + return device.os; + } + + private findOSVersion(device: Device): string { + if (device.osVersion.toLowerCase().includes('unknown')) return ''; + + return device.osVersion; + } + + private makeClientsString(clients: Client[]): string { + let clientsString = ""; + clients.forEach( (client: Client, index: number) => { + let clientName: string; + if (client.hasOwnProperty('clientName') && (client.clientName !== undefined) && (client.clientName !== '')) { + clientName = Msg.localize(client.clientName); + } else { + clientName = client.clientId; + } + + clientsString += clientName; + + if (clients.length > index + 1) clientsString += ', '; + }) + + return clientsString; + } + + private isShowSignOutAll(devices: Device[]): boolean { + if (devices.length === 0) return false; + if (devices.length > 1) return true; + if (devices[0].sessions.length > 1) return true; + + return false; + } + + public render(): React.ReactNode { + + return ( + + + + + + + +
    +

    +

    + +

    +
    + , + + { (keycloak: KeycloakService) => ( + + {this.isShowSignOutAll(this.state.devices) && + this.signOutAll(keycloak)} + /> + } + + )} + + ]} + /> +
    +
    + + + + + {/* <-- top spacing */} + {this.state.devices.map((device: Device, deviceIndex: number) => { + return ( + + {device.sessions.map((session: Session, sessionIndex: number) => { + return ( + + + + + + {this.findBrowserIcon(session)} + + + {session.ipAddress} + + {session.current && + + + + } + + + + {!session.browser.toLowerCase().includes('unknown') && +

    {session.browser} / {this.findOS(device)} {this.findOSVersion(device)}

    } +

    {Msg.localize('lastAccessedOn')} {this.time(session.lastAccess)}

    +

    {Msg.localize('clients')} {this.makeClientsString(session.clients)}

    +

    {Msg.localize('startedAt')} {this.time(session.started)}

    +

    {Msg.localize('expiresAt')} {this.time(session.expires)}

    + {!session.current && + this.signOutSession(device, session)} + /> + } + +
    +
    + ); + + })} +
    + ) + })} + {/* <-- bottom spacing */} +
    +
    +
    +
    +
    + +
    +
    + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/forbidden-page/ForbiddenPage.tsx b/keycloak.v2/account/src/app/content/forbidden-page/ForbiddenPage.tsx new file mode 100644 index 0000000..c11ae45 --- /dev/null +++ b/keycloak.v2/account/src/app/content/forbidden-page/ForbiddenPage.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as React from 'react'; + +import { WarningTriangleIcon } from '@patternfly/react-icons'; +import {Msg} from '../../widgets/Msg'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; + + +export class ForbiddenPage extends React.Component { + + public constructor() { + super({}); + } + + public render(): React.ReactNode { + return ( + + + + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/linked-accounts-page/LinkedAccountsPage.tsx b/keycloak.v2/account/src/app/content/linked-accounts-page/LinkedAccountsPage.tsx new file mode 100644 index 0000000..85a2f45 --- /dev/null +++ b/keycloak.v2/account/src/app/content/linked-accounts-page/LinkedAccountsPage.tsx @@ -0,0 +1,232 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; + +import { + Badge, + Button, + DataList, + DataListAction, + DataListItemCells, + DataListCell, + DataListItemRow, + Stack, + StackItem, + Title, + TitleLevel, + DataListItem, +} from '@patternfly/react-core'; + +import { + BitbucketIcon, + CubeIcon, + FacebookIcon, + GithubIcon, + GitlabIcon, + GoogleIcon, + InstagramIcon, + LinkIcon, + LinkedinIcon, + MicrosoftIcon, + OpenshiftIcon, + PaypalIcon, + StackOverflowIcon, + TwitterIcon, + UnlinkIcon +} from '@patternfly/react-icons'; + +import {HttpResponse} from '../../account-service/account.service'; +import {AccountServiceContext} from '../../account-service/AccountServiceContext'; +import {Msg} from '../../widgets/Msg'; +import {ContentPage} from '../ContentPage'; +import {createRedirect} from '../../util/RedirectUri'; + +interface LinkedAccount { + connected: boolean; + social: boolean; + providerAlias: string; + providerName: string; + displayName: string; + linkedUsername: string; +} + +interface LinkedAccountsPageProps extends RouteComponentProps { +} + +interface LinkedAccountsPageState { + linkedAccounts: LinkedAccount[]; + unLinkedAccounts: LinkedAccount[]; +} + +/** + * @author Stan Silvert + */ +class LinkedAccountsPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: LinkedAccountsPageProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + linkedAccounts: [], + unLinkedAccounts: [] + } + + this.getLinkedAccounts(); + } + + private getLinkedAccounts(): void { + this.context!.doGet("/linked-accounts") + .then((response: HttpResponse) => { + console.log({response}); + const linkedAccounts = response.data!.filter((account) => account.connected); + const unLinkedAccounts = response.data!.filter((account) => !account.connected); + this.setState({linkedAccounts: linkedAccounts, unLinkedAccounts: unLinkedAccounts}); + }); + } + + private unLinkAccount(account: LinkedAccount): void { + const url = '/linked-accounts/' + account.providerName; + + this.context!.doDelete(url) + .then((response: HttpResponse) => { + console.log({response}); + this.getLinkedAccounts(); + }); + } + + private linkAccount(account: LinkedAccount): void { + const url = '/linked-accounts/' + account.providerName; + + const redirectUri: string = createRedirect(this.props.location.pathname); + + this.context!.doGet<{accountLinkUri: string}>(url, { params: {providerId: account.providerName, redirectUri}}) + .then((response: HttpResponse<{accountLinkUri: string}>) => { + console.log({response}); + window.location.href = response.data!.accountLinkUri; + }); + } + + public render(): React.ReactNode { + + return ( + + + + + <Msg msgKey='linkedLoginProviders'/> + + + {this.makeRows(this.state.linkedAccounts, true)} + + + + + + <Msg msgKey='unlinkedLoginProviders'/> + + + {this.makeRows(this.state.unLinkedAccounts, false)} + + + + + ); + } + + private emptyRow(isLinked: boolean): React.ReactNode { + let isEmptyMessage = ''; + if (isLinked) { + isEmptyMessage = Msg.localize('linkedEmpty'); + } else { + isEmptyMessage = Msg.localize('unlinkedEmpty'); + } + + return ( + + + {isEmptyMessage} + ]}/> + + + ) + } + + private makeRows(accounts: LinkedAccount[], isLinked: boolean): React.ReactNode { + if (accounts.length === 0) { + return this.emptyRow(isLinked); + } + + return ( + <> { + + accounts.map( (account: LinkedAccount) => ( + + + {this.findIcon(account)}

    {account.displayName}

    , + {this.badge(account)}, + {account.linkedUsername}, + ]}/> + + {isLinked && } + {!isLinked && } + +
    +
    + )) + + } + + ) + } + + private badge(account: LinkedAccount): React.ReactNode { + if (account.social) { + return (); + } + + return (); + } + + private findIcon(account: LinkedAccount): React.ReactNode { + const socialIconId = `${account.providerAlias}-idp-icon-social`; + if (account.providerName.toLowerCase().includes('github')) return (); + if (account.providerName.toLowerCase().includes('linkedin')) return (); + if (account.providerName.toLowerCase().includes('facebook')) return (); + if (account.providerName.toLowerCase().includes('google')) return (); + if (account.providerName.toLowerCase().includes('instagram')) return (); + if (account.providerName.toLowerCase().includes('microsoft')) return (); + if (account.providerName.toLowerCase().includes('bitbucket')) return (); + if (account.providerName.toLowerCase().includes('twitter')) return (); + if (account.providerName.toLowerCase().includes('openshift')) return (); + if (account.providerName.toLowerCase().includes('gitlab')) return (); + if (account.providerName.toLowerCase().includes('paypal')) return (); + if (account.providerName.toLowerCase().includes('stackoverflow')) return (); + + return (); + } + +}; + +const LinkedAccountsPagewithRouter = withRouter(LinkedAccountsPage); +export {LinkedAccountsPagewithRouter as LinkedAccountsPage}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/AbstractResourceTable.tsx b/keycloak.v2/account/src/app/content/my-resources-page/AbstractResourceTable.tsx new file mode 100644 index 0000000..e1c6f40 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/AbstractResourceTable.tsx @@ -0,0 +1,53 @@ +import * as React from 'react'; +import { Permission, PaginatedResources, Client } from './resource-model'; +import { Msg } from '../../widgets/Msg'; + +export interface ResourcesTableProps { + resources: PaginatedResources; +} + +export interface ResourcesTableState { + permissions: Map; +} + +export abstract class AbstractResourcesTable extends React.Component { + + protected hasPermissions(row: number): boolean { + return (this.state.permissions.has(row)) && (this.state.permissions.get(row)!.length > 0); + } + + private firstUser(row: number): string { + if (!this.hasPermissions(row)) return 'ERROR!!!!'; // should never happen + + return this.state.permissions.get(row)![0].username; + } + + protected numOthers(row: number): number { + if (!this.hasPermissions(row)) return -1; // should never happen + + return this.state.permissions.get(row)!.length - 1; + } + + public sharedWithUsersMessage(row: number): React.ReactNode { + if (!this.hasPermissions(row)) return (); + + return ( + + + {this.firstUser(row)} + + {this.numOthers(row) > 0 && + {this.numOthers(row)} + }. + + ); + } + + protected getClientName(client: Client): string { + if (client.hasOwnProperty('name') && client.name !== null && client.name !== '') { + return Msg.localize(client.name!); + } else { + return client.clientId; + } + } +} diff --git a/keycloak.v2/account/src/app/content/my-resources-page/EditTheResource.tsx b/keycloak.v2/account/src/app/content/my-resources-page/EditTheResource.tsx new file mode 100644 index 0000000..4d07174 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/EditTheResource.tsx @@ -0,0 +1,145 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import { + Button, + Modal, + Form, + FormGroup, + TextInput, + InputGroup +} from '@patternfly/react-core'; +import { OkIcon } from '@patternfly/react-icons'; + +import { Resource, Permission, Scope } from './resource-model'; +import { Msg } from '../../widgets/Msg'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { ContentAlert } from '../ContentAlert'; +import { PermissionSelect } from './PermissionSelect'; + +interface EditTheResourceProps { + resource: Resource; + permissions: Permission[]; + onClose: () => void; + children: (toggle: () => void) => void; +} + +interface EditTheResourceState { + changed: boolean[]; + isOpen: boolean; +} + +export class EditTheResource extends React.Component { + protected static defaultProps = { permissions: [] }; + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: EditTheResourceProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + changed: [], + isOpen: false, + }; + } + + private clearState(): void { + this.setState({}); + } + + private handleToggleDialog = () => { + if (this.state.isOpen) { + this.setState({ isOpen: false }); + this.props.onClose(); + } else { + this.clearState(); + this.setState({ isOpen: true }); + } + }; + + private updateChanged = (row: number) => { + const changed = this.state.changed; + changed[row] = !changed[row]; + this.setState({ changed }); + } + + async savePermission(permission: Permission): Promise { + await this.context!.doPut(`/resources/${this.props.resource._id}/permissions`, [permission]); + ContentAlert.success(Msg.localize('updateSuccess')); + } + + public render(): React.ReactNode { + return ( + + {this.props.children(this.handleToggleDialog)} + + + + , + ]} + > +
    + {this.props.permissions.map((p, row) => ( + + + + + + + + new Scope(s))} + direction={row === this.props.permissions.length - 1 ? "up" : "down"} + onSelect={selection => { + p.scopes = selection.map(s => s.name); + this.updateChanged(row); + }} + /> + + + +
    +
    + ))} +
    +
    +
    + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/MyResourcesPage.tsx b/keycloak.v2/account/src/app/content/my-resources-page/MyResourcesPage.tsx new file mode 100644 index 0000000..a1aff52 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/MyResourcesPage.tsx @@ -0,0 +1,256 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import parse from '../../util/ParseLink'; + +import { Button, Level, LevelItem, Stack, StackItem, Tab, Tabs, TextInput } from '@patternfly/react-core'; + +import {HttpResponse} from '../../account-service/account.service'; +import {AccountServiceContext} from '../../account-service/AccountServiceContext'; + +import { PaginatedResources, Resource, Scope, Permission } from './resource-model'; +import {ResourcesTable} from './ResourcesTable'; +import {ContentPage} from '../ContentPage'; +import {Msg} from '../../widgets/Msg'; +import { SharedResourcesTable } from './SharedResourcesTable'; + +export interface MyResourcesPageProps { +} + +export interface MyResourcesPageState { + activeTabKey: number; + isModalOpen: boolean; + nameFilter: string; + myResources: PaginatedResources; + sharedWithMe: PaginatedResources; +} + +const MY_RESOURCES_TAB = 0; +const SHARED_WITH_ME_TAB = 1; + +export class MyResourcesPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + private first = 0; + private max = 5; + + public constructor(props: MyResourcesPageProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + activeTabKey: MY_RESOURCES_TAB, + nameFilter: '', + isModalOpen: false, + myResources: {nextUrl: '', prevUrl: '', data: []}, + sharedWithMe: {nextUrl: '', prevUrl: '', data: []} + }; + + this.fetchInitialResources(); + } + + private isSharedWithMeTab(): boolean { + return this.state.activeTabKey === SHARED_WITH_ME_TAB; + } + + private hasNext(): boolean { + if (this.isSharedWithMeTab()) { + return (this.state.sharedWithMe.nextUrl !== null) && (this.state.sharedWithMe.nextUrl !== ''); + } else { + return (this.state.myResources.nextUrl !== null) && (this.state.myResources.nextUrl !== ''); + } + } + + private hasPrevious(): boolean { + if (this.isSharedWithMeTab()) { + return (this.state.sharedWithMe.prevUrl !== null) && (this.state.sharedWithMe.prevUrl !== ''); + } else { + return (this.state.myResources.prevUrl !== null) && (this.state.myResources.prevUrl !== ''); + } + } + + private fetchInitialResources(): void { + if (this.isSharedWithMeTab()) { + this.fetchResources("/resources/shared-with-me"); + } else { + this.fetchResources("/resources", {first: this.first, max: this.max}); + } + } + + private fetchFilteredResources(params: Record): void { + if (this.isSharedWithMeTab()) { + this.fetchResources("/resources/shared-with-me", params); + } else { + this.fetchResources("/resources", {...params, first: this.first, max: this.max}); + } + } + + private fetchResources(url: string, extraParams?: Record): void { + this.context!.doGet(url, {params: extraParams}) + .then((response: HttpResponse) => { + const resources: Resource[] = response.data || []; + resources.forEach((resource: Resource) => resource.shareRequests = []); + + // serialize the Scope objects from JSON so that toString() will work. + resources.forEach((resource: Resource) => resource.scopes = resource.scopes.map(this.makeScopeObj)); + + if (this.isSharedWithMeTab()) { + this.setState({sharedWithMe: this.parseResourceResponse(response)}, this.fetchPending); + } else { + this.setState({myResources: this.parseResourceResponse(response)}, this.fetchPermissionRequests); + } + }); + } + + private makeScopeObj = (scope: Scope): Scope => { + return new Scope(scope.name, scope.displayName); + } + + private fetchPermissionRequests = () => { + this.state.myResources.data.forEach((resource: Resource) => { + this.fetchShareRequests(resource); + }); + } + + private fetchShareRequests(resource: Resource): void { + this.context!.doGet('/resources/' + resource._id + '/permissions/requests') + .then((response: HttpResponse) => { + resource.shareRequests = response.data || []; + if (resource.shareRequests.length > 0) { + this.forceUpdate(); + } + }); + } + + private fetchPending = async () => { + const response: HttpResponse = await this.context!.doGet(`/resources/pending-requests`); + const resources: Resource[] = response.data || []; + resources.forEach((pendingRequest: Resource) => { + this.state.sharedWithMe.data.forEach(resource => { + if (resource._id === pendingRequest._id) { + resource.shareRequests = [{username: 'me', scopes: pendingRequest.scopes}] + this.forceUpdate(); + } + }); + }); + } + + private parseResourceResponse(response: HttpResponse): PaginatedResources { + const links: string | undefined = response.headers.get('link') || undefined; + const parsed = parse(links); + + let next = ''; + let prev = ''; + + if (parsed !== null) { + if (parsed.next) next = parsed.next; + if (parsed.prev) prev = parsed.prev; + } + + const resources: Resource[] = response.data || []; + + return {nextUrl: next, prevUrl: prev, data: resources}; + } + + private makeTab(eventKey: number, title: string, resources: PaginatedResources, sharedResourcesTab: boolean): React.ReactNode { + return ( + + + + + + + + + + + + {!sharedResourcesTab && } + {sharedResourcesTab && } + + + + ) + } + + public render(): React.ReactNode { + return ( + + + {this.makeTab(0, 'myResources', this.state.myResources, false)} + {this.makeTab(1, 'sharedwithMe', this.state.sharedWithMe, true)} + + + + + {this.hasPrevious() && } + + + + {this.hasPrevious() && } + + + + {this.hasNext() && } + + + + ); + } + + private handleFilterRequest = (value: string) => { + this.setState({nameFilter: value}); + this.fetchFilteredResources({name: value}); + } + + private clearNextPrev(): void { + const newMyResources: PaginatedResources = this.state.myResources; + newMyResources.nextUrl = ''; + newMyResources.prevUrl = ''; + this.setState({myResources: newMyResources}); + } + + private handleFirstPageClick = () => { + this.fetchInitialResources(); + } + + private handleNextClick = () => { + if (this.isSharedWithMeTab()) { + this.fetchResources(this.state.sharedWithMe.nextUrl); + } else { + this.fetchResources(this.state.myResources.nextUrl); + } + } + + private handlePreviousClick = () => { + if (this.isSharedWithMeTab()) { + this.fetchResources(this.state.sharedWithMe.prevUrl); + } else { + this.fetchResources(this.state.myResources.prevUrl); + } + } + + private handleTabClick = (event: React.MouseEvent, tabIndex: number) => { + if (this.state.activeTabKey === tabIndex) return; + + this.setState({ + nameFilter: '', + activeTabKey: tabIndex + }, () => {this.fetchInitialResources()}); + }; +}; diff --git a/keycloak.v2/account/src/app/content/my-resources-page/PermissionRequest.tsx b/keycloak.v2/account/src/app/content/my-resources-page/PermissionRequest.tsx new file mode 100644 index 0000000..e9ea584 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/PermissionRequest.tsx @@ -0,0 +1,179 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as React from 'react'; +import { + Button, + Modal, + Text, + Badge, + DataListItem, + DataList, + TextVariants, + DataListItemRow, + DataListItemCells, + DataListCell, + Chip, + Split, + SplitItem +} from '@patternfly/react-core'; +import { UserCheckIcon } from '@patternfly/react-icons'; + +import { HttpResponse } from '../../account-service/account.service'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { Msg } from '../../widgets/Msg'; +import { ContentAlert } from '../ContentAlert'; +import { Resource, Scope, Permission } from './resource-model'; + + +interface PermissionRequestProps { + resource: Resource; + onClose: () => void; +} + +interface PermissionRequestState { + isOpen: boolean; +} + +export class PermissionRequest extends React.Component { + protected static defaultProps = { permissions: [], row: 0 }; + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: PermissionRequestProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + isOpen: false, + }; + } + + private handleApprove = async (shareRequest: Permission, index: number) => { + this.handle(shareRequest.username, shareRequest.scopes as Scope[], true); + this.props.resource.shareRequests.splice(index, 1); + }; + + private handleDeny = async (shareRequest: Permission, index: number) => { + this.handle(shareRequest.username, shareRequest.scopes as Scope[]); + this.props.resource.shareRequests.splice(index, 1) + }; + + private handle = async (username: string, scopes: Scope[], approve: boolean = false) => { + const id = this.props.resource._id + this.handleToggleDialog(); + + const permissionsRequest: HttpResponse = await this.context!.doGet(`/resources/${id}/permissions`); + const permissions = permissionsRequest.data || []; + const foundPermission = permissions.find(p => p.username === username); + const userScopes = foundPermission ? (foundPermission.scopes as Scope[]): []; + if (approve) { + userScopes.push(...scopes); + } + try { + await this.context!.doPut(`/resources/${id}/permissions`, [{ username: username, scopes: userScopes }] ) + ContentAlert.success(Msg.localize('shareSuccess')); + this.props.onClose(); + } catch (e) { + console.error('Could not update permissions', e.error); + } + }; + + private handleToggleDialog = () => { + this.setState({ isOpen: !this.state.isOpen }); + }; + + public render(): React.ReactNode { + const id = `shareRequest-${this.props.resource.name.replace(/\s/, '-')}`; + return ( + + + + + + , + ]} + > + + + + Requestor + , + + + , + + + ]} + /> + + {this.props.resource.shareRequests.map((shareRequest, i) => + + + + + {shareRequest.firstName} {shareRequest.lastName} {shareRequest.lastName ? '' : shareRequest.username} +
    + {shareRequest.email} + , + + {(shareRequest.scopes as Scope[]).map((scope, j) => {scope})} + , + + + + + + + + + + + ]} + /> +
    +
    + )} +
    +
    +
    + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/PermissionSelect.tsx b/keycloak.v2/account/src/app/content/my-resources-page/PermissionSelect.tsx new file mode 100644 index 0000000..8b9ca88 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/PermissionSelect.tsx @@ -0,0 +1,108 @@ +import * as React from 'react'; + +import { Select, SelectOption, SelectVariant, SelectOptionObject } from '@patternfly/react-core'; +import { Scope } from './resource-model'; + +interface PermissionSelectState { + selected: ScopeValue[]; + isExpanded: boolean; + scopes: JSX.Element[]; +} + +interface PermissionSelectProps { + scopes: Scope[]; + selected?: Scope[]; + direction?: 'up' | 'down'; + onSelect: (selected: Scope[]) => void; +} + +class ScopeValue implements SelectOptionObject { + value: Scope; + constructor(value: Scope) { + this.value = value; + } + + toString() { + return this.value.displayName ? this.value.displayName : this.value.name; + } + + compareTo(selectOption: Scope): boolean { + return selectOption.name === this.value.name; + } +} + +export class PermissionSelect extends React.Component { + constructor(props: PermissionSelectProps) { + super(props); + + let values: ScopeValue[] = []; + if (this.props.selected) { + values = this.props.selected!.map(s => new ScopeValue(s)) + } + + this.state = { + isExpanded: false, + selected: values, + scopes: this.props.scopes.map((option, index) => ( + s.compareTo(option)) || new ScopeValue(option)} /> + )) + }; + } + + private onSelect = (_event: React.MouseEvent | React.ChangeEvent, selection: ScopeValue): void => { + const { selected } = this.state; + const { onSelect } = this.props; + if (selected.includes(selection)) { + this.setState( + prevState => ({ selected: prevState.selected.filter(item => item !== selection) }), + () => onSelect(this.state.selected.map(sv => sv.value)) + ); + } else { + this.setState( + prevState => ({ selected: [...prevState.selected, selection] }), + () => onSelect(this.state.selected.map(sv => sv.value)) + ); + } + } + + private onToggle = (isExpanded: boolean) => { + this.setState({ + isExpanded + }); + } + + private clearSelection = () => { + this.setState({ + selected: [], + isExpanded: false + }); + this.props.onSelect([]); + }; + + render() { + const { isExpanded, selected } = this.state; + const titleId = 'permission-id'; + + return ( +
    + + +
    + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/ResourcesTable.tsx b/keycloak.v2/account/src/app/content/my-resources-page/ResourcesTable.tsx new file mode 100644 index 0000000..2d6cbf0 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/ResourcesTable.tsx @@ -0,0 +1,341 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import { + DataList, + DataListItem, + DataListItemRow, + DataListCell, + DataListToggle, + DataListContent, + DataListItemCells, + Level, + LevelItem, + Button, + DataListAction, + DataListActionVisibility, + Dropdown, + DropdownPosition, + DropdownItem, + KebabToggle +} from '@patternfly/react-core'; +import { css } from '@patternfly/react-styles'; + +import { Remove2Icon, RepositoryIcon, ShareAltIcon, EditAltIcon } from '@patternfly/react-icons'; + +import { HttpResponse } from '../../account-service/account.service'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { PermissionRequest } from "./PermissionRequest"; +import { ShareTheResource } from "./ShareTheResource"; +import { Permission, Resource } from "./resource-model"; +import { Msg } from '../../widgets/Msg'; +import { ResourcesTableState, ResourcesTableProps, AbstractResourcesTable } from './AbstractResourceTable'; +import { EditTheResource } from './EditTheResource'; +import { ContentAlert } from '../ContentAlert'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; +import { ContinueCancelModal } from '../../widgets/ContinueCancelModal'; + +export interface CollapsibleResourcesTableState extends ResourcesTableState { + isRowOpen: boolean[]; + contextOpen: boolean[]; + isModalActive: boolean; +} + +export class ResourcesTable extends AbstractResourcesTable { + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: ResourcesTableProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + isRowOpen: [], + contextOpen: [], + isModalActive: false, + permissions: new Map() + } + } + + private onToggle = (row: number): void => { + const newIsRowOpen: boolean[] = this.state.isRowOpen; + newIsRowOpen[row] = !newIsRowOpen[row]; + if (newIsRowOpen[row]) this.fetchPermissions(this.props.resources.data[row], row); + this.setState({ isRowOpen: newIsRowOpen }); + }; + + private onContextToggle = (row: number, isOpen: boolean): void => { + if (this.state.isModalActive) return; + const data = this.props.resources.data; + const contextOpen = this.state.contextOpen; + contextOpen[row] = isOpen; + if (isOpen) { + const index = row > data.length ? row - data.length - 1 : row; + this.fetchPermissions(data[index], index); + } + this.setState({ contextOpen }); + } + + private fetchPermissions(resource: Resource, row: number): void { + this.context!.doGet(`/resources/${resource._id}/permissions`) + .then((response: HttpResponse) => { + const newPermissions: Map = new Map(this.state.permissions); + newPermissions.set(row, response.data || []); + this.setState({ permissions: newPermissions }); + }); + } + + private removeShare(resource: Resource, row: number): Promise { + const permissions = this.state.permissions.get(row)!.map(a => ({ username: a.username, scopes: [] })); + return this.context!.doPut(`/resources/${resource._id}/permissions`, permissions) + .then(() => { + ContentAlert.success(Msg.localize('unShareSuccess')); + }); + } + + public render(): React.ReactNode { + if (this.props.resources.data.length === 0) { + return ( + + ); + } + return ( + + + + // invisible toggle allows headings to line up properly + + + + + + , + + + , + + + , + ]} + /> + + + {this.props.resources.data.map((resource: Resource, row: number) => ( + + + this.onToggle(row)} + isExpanded={this.state.isRowOpen[row]} + id={'resourceToggle-' + row} + aria-controls="ex-expand1" + /> + + + , + + {this.getClientName(resource.client)} + , + + {resource.shareRequests.length > 0 && + this.fetchPermissions(resource, row)} + > + } + + ]} + /> + + this.setState({ isModalActive: true })} + toggle={ this.onContextToggle(row + this.props.resources.data.length + 1, isOpen)} />} + isOpen={this.state.contextOpen[row + this.props.resources.data.length + 1]} + dropdownItems={[ + { + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row + this.props.resources.data.length + 1, false); + this.fetchPermissions(resource, row + this.props.resources.data.length + 1); + }); + }} + > + { + (toggle: () => void) => ( + + + ) + } + , + { + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row + this.props.resources.data.length + 1, false); + this.fetchPermissions(resource, row + this.props.resources.data.length + 1); + }); + }} + > + { + (toggle: () => void) => ( + + + ) + } + , + void) => ( + + + + )} + modalTitle="unShare" + modalMessage="unShareAllConfirm" + onClose={() => + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row + this.props.resources.data.length + 1, false); + }) + } + onContinue={() => this.removeShare(resource, row) + .then(() => this.fetchPermissions(resource, row + this.props.resources.data.length + 1))} + /> + ]} + /> + + + this.fetchPermissions(resource, row)} + > + { + (toggle: () => void) => ( + + ) + } + + this.onContextToggle(row, isOpen)} />} + onSelect={() => this.setState({ isModalActive: true })} + isOpen={this.state.contextOpen[row]} + dropdownItems={[ + { + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row, false); + this.fetchPermissions(resource, row); + }); + }} + > + { + (toggle: () => void) => ( + + + ) + } + , + void) => ( + + + + )} + modalTitle="unShare" + modalMessage='unShareAllConfirm' + onClose={() => + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row, false); + }) + } + onContinue={() => this.removeShare(resource, row).then(() => this.fetchPermissions(resource, row))} + /> + ]} + /> + + + + + + + {this.sharedWithUsersMessage(row)} + + + + + ))} + + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/ShareTheResource.tsx b/keycloak.v2/account/src/app/content/my-resources-page/ShareTheResource.tsx new file mode 100644 index 0000000..cc481c0 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/ShareTheResource.tsx @@ -0,0 +1,243 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import { + Button, + Chip, + ChipGroup, + ChipGroupToolbarItem, + Form, + FormGroup, + Gallery, + GalleryItem, + Modal, + Stack, + StackItem, + TextInput +} from '@patternfly/react-core'; + +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { Resource, Permission, Scope } from './resource-model'; +import { Msg } from '../../widgets/Msg'; +import {ContentAlert} from '../ContentAlert'; +import { PermissionSelect } from './PermissionSelect'; + +interface ShareTheResourceProps { + resource: Resource; + permissions: Permission[]; + sharedWithUsersMsg: React.ReactNode; + onClose: () => void; + children: (toggle: () => void) => void; +} + +interface ShareTheResourceState { + isOpen: boolean; + permissionsSelected: Scope[]; + permissionsUnSelected: Scope[]; + usernames: string[]; + usernameInput: string; +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc. + */ +export class ShareTheResource extends React.Component { + protected static defaultProps = {permissions: []}; + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: ShareTheResourceProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + isOpen: false, + permissionsSelected: [], + permissionsUnSelected: this.props.resource.scopes, + usernames: [], + usernameInput: '' + }; + } + + private clearState(): void { + this.setState({ + permissionsSelected: [], + permissionsUnSelected: this.props.resource.scopes, + usernames: [], + usernameInput: '' + }); + } + + private handleAddPermission = () => { + const rscId: string = this.props.resource._id; + const newPermissions: string[] = []; + + for (const permission of this.state.permissionsSelected) { + newPermissions.push(permission.name); + } + + const permissions = []; + + for (const username of this.state.usernames) { + permissions.push({username: username, scopes: newPermissions}); + } + + this.handleToggleDialog(); + + this.context!.doPut(`/resources/${rscId}/permissions`, permissions) + .then(() => { + ContentAlert.success('shareSuccess'); + this.props.onClose(); + }) + }; + + private handleToggleDialog = () => { + if (this.state.isOpen) { + this.setState({isOpen: false}); + this.props.onClose(); + } else { + this.clearState(); + this.setState({isOpen: true}); + } + }; + + private handleUsernameChange = (username: string) => { + this.setState({usernameInput: username}); + } + + private handleAddUsername = async () => { + if ((this.state.usernameInput !== '') && (!this.state.usernames.includes(this.state.usernameInput))) { + const response = await this.context!.doGet<{username: string}>(`/resources/${this.props.resource._id}/user`, { params: { value: this.state.usernameInput } }); + if (response.data && response.data.username) { + this.setState({ usernameInput: '', usernames: [...this.state.usernames, this.state.usernameInput] }); + } else { + ContentAlert.info('userNotFound', [this.state.usernameInput]); + } + } + } + + private handleEnterKeyInAddField = (event: React.KeyboardEvent) => { + if (event.key === "Enter") { + event.preventDefault(); + this.handleAddUsername(); + } + } + + private handleDeleteUsername = (username: string) => { + const newUsernames: string[] = this.state.usernames.filter(user => user !== username); + this.setState({usernames: newUsernames}); + } + + private isAddDisabled(): boolean { + return this.state.usernameInput === '' || this.isAlreadyShared(); + } + + private isAlreadyShared(): boolean { + for (let permission of this.props.permissions) { + if (permission.username === this.state.usernameInput) return true; + } + + return false; + } + + private isFormInvalid(): boolean { + return (this.state.usernames.length === 0) || (this.state.permissionsSelected.length === 0); + } + + public render(): React.ReactNode { + return ( + + {this.props.children(this.handleToggleDialog)} + + + + , + + ]} + > + + +
    + + + + + + + + + + + + + {this.state.usernames.map((currentChip: string) => ( + this.handleDeleteUsername(currentChip)}> + {currentChip} + + ))} + + + + + this.setState({ permissionsSelected: selection })} + direction="up" + /> + +
    +
    +
    + + {this.props.sharedWithUsersMsg} + + +
    +
    +
    + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/SharedResourcesTable.tsx b/keycloak.v2/account/src/app/content/my-resources-page/SharedResourcesTable.tsx new file mode 100644 index 0000000..0bf0817 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/SharedResourcesTable.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import { + DataList, + DataListItem, + DataListItemRow, + DataListCell, + DataListItemCells, + ChipGroup, + ChipGroupToolbarItem, + Chip + } from '@patternfly/react-core'; +import { RepositoryIcon } from '@patternfly/react-icons'; + + +import { PaginatedResources, Resource, Scope } from "./resource-model"; +import { Msg } from '../../widgets/Msg'; +import { AbstractResourcesTable, ResourcesTableState } from './AbstractResourceTable'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; + +export interface ResourcesTableProps { + resources: PaginatedResources; + noResourcesMessage: string; +} + +export class SharedResourcesTable extends AbstractResourcesTable { + + public constructor(props: ResourcesTableProps) { + super(props); + this.state = { + permissions: new Map() + } + } + + public render(): React.ReactNode { + if (this.props.resources.data.length === 0) { + return ( + + ); + } + return ( + + + + + + , + + + , + , + , + ]} + /> + + + {this.props.resources.data.map((resource: Resource, row: number) => ( + + + + + , + + {this.getClientName(resource.client)} + , + + { resource.scopes.length > 0 && + + + { + resource.scopes.map(scope => ( + + {scope.displayName || scope.name} + + )) + } + + } + , + + {resource.shareRequests.length > 0 && + + + { + (resource.shareRequests[0].scopes as Scope[]).map(scope => ( + + {scope.displayName || scope.name} + + )) + } + + + } + + ]} + /> + + + ))} + + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/my-resources-page/resource-model.ts b/keycloak.v2/account/src/app/content/my-resources-page/resource-model.ts new file mode 100644 index 0000000..960bd51 --- /dev/null +++ b/keycloak.v2/account/src/app/content/my-resources-page/resource-model.ts @@ -0,0 +1,40 @@ +export interface Resource { + _id: string; + name: string; + client: Client; + scopes: Scope[]; + uris: string[]; + shareRequests: Permission[]; +} + +export interface Client { + baseUrl: string; + clientId: string; + name?: string; +} + +export class Scope { + public constructor(public name: string, public displayName?: string) {} + + public toString(): string { + if (this.hasOwnProperty('displayName') && (this.displayName)) { + return this.displayName; + } else { + return this.name; + } + } +} + +export interface PaginatedResources { + nextUrl: string; + prevUrl: string; + data: Resource[]; +} + +export interface Permission { + email?: string; + firstName?: string; + lastName?: string; + scopes: Scope[] | string[]; // this should be Scope[] - fix API + username: string; +} diff --git a/keycloak.v2/account/src/app/content/page-not-found/PageNotFound.tsx b/keycloak.v2/account/src/app/content/page-not-found/PageNotFound.tsx new file mode 100644 index 0000000..26fefe4 --- /dev/null +++ b/keycloak.v2/account/src/app/content/page-not-found/PageNotFound.tsx @@ -0,0 +1,31 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +import * as React from 'react'; + +import { WarningTriangleIcon } from '@patternfly/react-icons'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; +import {Msg} from '../../widgets/Msg'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; + +export interface PageNotFoundProps extends RouteComponentProps {} + +class PgNotFound extends React.Component { + + public constructor(props: PageNotFoundProps) { + super(props); + } + + public render(): React.ReactNode { + return ( + + + + ); + } +}; + +export const PageNotFound = withRouter(PgNotFound); \ No newline at end of file diff --git a/keycloak.v2/account/src/app/content/signingin-page/SigningInPage.tsx b/keycloak.v2/account/src/app/content/signingin-page/SigningInPage.tsx new file mode 100644 index 0000000..0bb563e --- /dev/null +++ b/keycloak.v2/account/src/app/content/signingin-page/SigningInPage.tsx @@ -0,0 +1,365 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {withRouter, RouteComponentProps} from 'react-router-dom'; +import { + Button, + DataList, + DataListAction, + DataListItemCells, + DataListCell, + DataListItem, + DataListItemRow, + Stack, + StackItem, + Title, + TitleLevel, + DataListActionVisibility, + Dropdown, + DropdownPosition, + KebabToggle, + } from '@patternfly/react-core'; + +import {AIACommand} from '../../util/AIACommand'; +import TimeUtil from '../../util/TimeUtil'; +import {HttpResponse, AccountServiceClient} from '../../account-service/account.service'; +import {AccountServiceContext} from '../../account-service/AccountServiceContext'; +import {ContinueCancelModal} from '../../widgets/ContinueCancelModal'; +import {Features} from '../../widgets/features'; +import {Msg} from '../../widgets/Msg'; +import {ContentPage} from '../ContentPage'; +import {ContentAlert} from '../ContentAlert'; +import { KeycloakContext } from '../../keycloak-service/KeycloakContext'; +import { KeycloakService } from '../../keycloak-service/keycloak.service'; +import { css } from '@patternfly/react-styles'; + +declare const features: Features; + +interface PasswordDetails { + registered: boolean; + lastUpdate: number; +} + +type CredCategory = 'password' | 'two-factor' | 'passwordless'; +type CredType = string; +type CredTypeMap = Map; +type CredContainerMap = Map; + +interface UserCredential { + id: string; + type: string; + userLabel: string; + createdDate?: number; + strCreatedDate?: string; +} + +// A CredentialContainer is unique by combo of credential type and credential category +interface CredentialContainer { + category: CredCategory; + type: CredType; + displayName: string; + helptext?: string; + createAction?: string; + updateAction?: string; + removeable: boolean; + userCredentials: UserCredential[]; +} + +interface SigningInPageProps extends RouteComponentProps { +} + +interface SigningInPageState { + // Credential containers organized by category then type + credentialContainers: CredContainerMap; + toggle: boolean +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc. + */ +class SigningInPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + + public constructor(props: SigningInPageProps, context: React.ContextType) { + super(props); + this.context = context; + + this.state = { + credentialContainers: new Map(), + toggle: false + } + + this.getCredentialContainers(); + } + + private getCredentialContainers(): void { + this.context!.doGet("/credentials") + .then((response: HttpResponse) => { + + const allContainers: CredContainerMap = new Map(); + const containers: CredentialContainer[] = response.data || []; + containers.forEach(container => { + let categoryMap = allContainers.get(container.category); + if (!categoryMap) { + categoryMap = new Map(); + allContainers.set(container.category, categoryMap); + } + categoryMap.set(container.type, container); + }); + + this.setState({credentialContainers: allContainers}); + console.log({allContainers}) + }); + } + + private handleRemove = (credentialId: string, userLabel: string) => { + this.context!.doDelete("/credentials/" + credentialId) + .then(() => { + this.getCredentialContainers(); + ContentAlert.success('successRemovedMessage', [userLabel]); + }); + } + + public static credElementId(credType: CredType, credId: string, item: string): string { + return `${credType}-${item}-${credId.substring(0,8)}`; + } + + public render(): React.ReactNode { + return ( + + + {this.renderCategories()} + + + ); + } + + private renderCategories(): React.ReactNode { + return (<> { + Array.from(this.state.credentialContainers.keys()).map(category => ( + + + <strong><Msg msgKey={category}/></strong> + + + {this.renderTypes(this.state.credentialContainers.get(category)!)} + + + )) + + }) + } + + private renderTypes(credTypeMap: CredTypeMap): React.ReactNode { + return ( + + { keycloak => ( + <>{ + Array.from(credTypeMap.keys()).map((credType: CredType, index: number, typeArray: string[]) => ([ + this.renderCredTypeTitle(credTypeMap.get(credType)!, keycloak!), + this.renderUserCredentials(credTypeMap, credType, keycloak!), + this.renderEmptyRow(credTypeMap.get(credType)!.type, index === typeArray.length - 1) + ])) + } + )} + + ); + } + + private renderEmptyRow(type: string, isLast: boolean): React.ReactNode { + if (isLast) return; // don't put empty row at the end + + return ( + + + ]}/> + + + ) + } + + private renderUserCredentials(credTypeMap: CredTypeMap, credType: CredType, keycloak: KeycloakService): React.ReactNode { + const credContainer: CredentialContainer = credTypeMap.get(credType)!; + const userCredentials: UserCredential[] = credContainer.userCredentials; + const removeable: boolean = credContainer.removeable; + const type: string = credContainer.type; + const displayName: string = credContainer.displayName; + + if (!userCredentials || userCredentials.length === 0) { + const localizedDisplayName = Msg.localize(displayName); + return ( + + + , + , + + ]}/> + + + ); + } + + userCredentials.forEach(credential => { + if (!credential.userLabel) credential.userLabel = Msg.localize(credential.type); + if (credential.hasOwnProperty('createdDate') && credential.createdDate && credential.createdDate! > 0) { + credential.strCreatedDate = TimeUtil.format(credential.createdDate as number); + } + }); + + let updateAIA: AIACommand; + if (credContainer.updateAction) { + updateAIA = new AIACommand(keycloak, credContainer.updateAction); + } + + return ( + { + userCredentials.map(credential => ( + + + + + + + + )) + } + ) + } + + private credentialRowCells(credential: UserCredential, type: string): React.ReactNode[] { + const credRowCells: React.ReactNode[] = []; + credRowCells.push({credential.userLabel}); + if (credential.strCreatedDate) { + credRowCells.push(: {credential.strCreatedDate}); + credRowCells.push(); + } + + return credRowCells; + } + + private renderCredTypeTitle(credContainer: CredentialContainer, keycloak: KeycloakService): React.ReactNode { + if (!credContainer.hasOwnProperty('helptext') && !credContainer.hasOwnProperty('createAction')) return; + + let setupAction: AIACommand; + if (credContainer.createAction) { + setupAction = new AIACommand(keycloak, credContainer.createAction); + } + const credContainerDisplayName: string = Msg.localize(credContainer.displayName); + + return ( + + + + + + <strong id={`${credContainer.type}-cred-title`}><Msg msgKey={credContainer.displayName}/></strong> + + + {credContainer.helptext && } + + , + + ]}/> + {credContainer.createAction && + + this.setState({ toggle: isOpen })} />} + isOpen={this.state.toggle} + dropdownItems={[ + ]} + /> + } + {credContainer.createAction && + + + } + + +
    + ) + } + +}; + +type CredRemover = (credentialId: string, userLabel: string) => void; +interface CredentialActionProps {credential: UserCredential; + removeable: boolean; + updateAction: AIACommand; + credRemover: CredRemover;}; +class CredentialAction extends React.Component { + + render(): React.ReactNode { + if (this.props.updateAction) { + return ( + + + + ) + } + + if (this.props.removeable) { + const userLabel: string = this.props.credential.userLabel; + return ( + + this.props.credRemover(this.props.credential.id, userLabel)} + /> + + ) + } + + return (<>) + } +} + +const SigningInPageWithRouter = withRouter(SigningInPage); +export { SigningInPageWithRouter as SigningInPage}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/keycloak-service/KeycloakContext.tsx b/keycloak.v2/account/src/app/keycloak-service/KeycloakContext.tsx new file mode 100644 index 0000000..ac73e19 --- /dev/null +++ b/keycloak.v2/account/src/app/keycloak-service/KeycloakContext.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { KeycloakService } from './keycloak.service'; + +export const KeycloakContext = React.createContext(undefined); \ No newline at end of file diff --git a/keycloak.v2/account/src/app/keycloak-service/keycloak.service.ts b/keycloak.v2/account/src/app/keycloak-service/keycloak.service.ts new file mode 100644 index 0000000..8864263 --- /dev/null +++ b/keycloak.v2/account/src/app/keycloak-service/keycloak.service.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {KeycloakLoginOptions} from "../../../../../../../../../../adapters/oidc/js/src/main/resources/keycloak"; + +declare const baseUrl: string; +export type KeycloakClient = Keycloak.KeycloakInstance; + +export class KeycloakService { + private keycloakAuth: KeycloakClient; + + public constructor(keycloak: KeycloakClient) { + this.keycloakAuth = keycloak; + } + + public authenticated(): boolean { + return this.keycloakAuth.authenticated ? this.keycloakAuth.authenticated : false; + } + + public audiencePresent(): boolean { + if (this.keycloakAuth.tokenParsed) { + const audience = this.keycloakAuth.tokenParsed['aud']; + return audience === 'account' || (Array.isArray(audience) && audience.indexOf('account') >= 0); + } + return false; + } + + public login(options?: KeycloakLoginOptions): void { + this.keycloakAuth.login(options); + } + + public logout(redirectUri: string = baseUrl): void { + this.keycloakAuth.logout({redirectUri: redirectUri}); + } + + public account(): void { + this.keycloakAuth.accountManagement(); + } + + public authServerUrl(): string | undefined { + const authServerUrl = this.keycloakAuth.authServerUrl; + return authServerUrl!.charAt(authServerUrl!.length - 1) === '/' ? authServerUrl : authServerUrl + '/'; + } + + public realm(): string | undefined { + return this.keycloakAuth.realm; + } + + public getToken(): Promise { + return new Promise((resolve, reject) => { + if (this.keycloakAuth.token) { + this.keycloakAuth + .updateToken(5) + .success(() => { + resolve(this.keycloakAuth.token as string); + }) + .error(() => { + reject('Failed to refresh token'); + }); + } else { + reject('Not logged in'); + } + }); + } +} diff --git a/keycloak.v2/account/src/app/util/AIACommand.ts b/keycloak.v2/account/src/app/util/AIACommand.ts new file mode 100644 index 0000000..78536de --- /dev/null +++ b/keycloak.v2/account/src/app/util/AIACommand.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {KeycloakService} from '../keycloak-service/keycloak.service'; + +/** + * @author Stan Silvert + */ +export class AIACommand { + + constructor(private keycloak: KeycloakService, private action: string) {} + + public execute(): void { + this.keycloak.login({ + action: this.action, + }) + + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/util/ParseLink.ts b/keycloak.v2/account/src/app/util/ParseLink.ts new file mode 100644 index 0000000..a8f4be4 --- /dev/null +++ b/keycloak.v2/account/src/app/util/ParseLink.ts @@ -0,0 +1,22 @@ + +export interface Links { + prev?: string; + next?: string; +} + +function parse(linkHeader: string | undefined): Links { + if (!linkHeader) return {}; + const links = linkHeader.split(/,\s*((acc: Links, link: string): Links => { + const matcher = link.match(/]*)>(.*)/); + if (!matcher) return {}; + const linkUrl = matcher[1]; + const rel = matcher[2].match(/\s*(.+)\s*=\s*"?([^"]+)"?/); + if (rel) { + acc[rel[2]] = linkUrl; + } + return acc; + }, {}); +} + +export default parse; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/util/RedirectUri.ts b/keycloak.v2/account/src/app/util/RedirectUri.ts new file mode 100644 index 0000000..c5211ad --- /dev/null +++ b/keycloak.v2/account/src/app/util/RedirectUri.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare const baseUrl: string; +declare const referrer: string; +declare const referrerUri: string; + +/** + * Create a redirect uri that can return to this application with referrer and referrer_uri intact. + * + * @param currentLocation The ReactRouter location to return to. + * + * @author Stan Silvert + */ +export const createRedirect = (currentLocation: string): string => { + let redirectUri: string = baseUrl; + + if (typeof referrer !== 'undefined') { + // '_hash_' is a workaround for when uri encoding is not + // sufficient to escape the # character properly. + // The problem is that both the redirect and the application URL contain a hash. + // The browser will consider anything after the first hash to be client-side. So + // it sees the hash in the redirect param and stops. + redirectUri += "?referrer=" + referrer + "&referrer_uri=" + referrerUri.replace('#', '_hash_'); + } + + return encodeURIComponent(redirectUri) + encodeURIComponent("/#" + currentLocation); +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/util/TimeUtil.ts b/keycloak.v2/account/src/app/util/TimeUtil.ts new file mode 100644 index 0000000..950bc15 --- /dev/null +++ b/keycloak.v2/account/src/app/util/TimeUtil.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare const locale: string; + +/** + * @author Stan Silvert + */ +class TimeUtil { + private options = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }; + private formatter: Intl.DateTimeFormat; + + constructor() { + try { + this.formatter = new Intl.DateTimeFormat(locale, this.options); + } catch(e) { + // unknown locale falling back to English + this.formatter = new Intl.DateTimeFormat('en', this.options); + } + } + + format(time: number): string { + return this.formatter.format(time); + } +} + +const TimeUtilInstance: TimeUtil = new TimeUtil(); +export default TimeUtilInstance as TimeUtil; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/ContinueCancelModal.tsx b/keycloak.v2/account/src/app/widgets/ContinueCancelModal.tsx new file mode 100644 index 0000000..c9da937 --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/ContinueCancelModal.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import { Modal, Button, ButtonProps } from '@patternfly/react-core'; +import {Msg} from './Msg'; + +/** + * For any of these properties that are strings, you can + * pass in a localization key instead of a static string. + */ +interface ContinueCancelModalProps { + buttonTitle?: string; + buttonVariant?: ButtonProps['variant']; + buttonId?: string; + render?(toggle: () => void): React.ReactNode; + modalTitle: string; + modalMessage?: string; + modalContinueButtonLabel?: string; + modalCancelButtonLabel?: string; + onContinue: () => void; + onClose?: () => void; + isDisabled?: boolean; + isLarge?: boolean; +} + +interface ContinueCancelModalState { + isModalOpen: boolean; +} + +/** + * This class renders a button that provides a continue/cancel modal dialog when clicked. If the user selects 'Continue' + * then the onContinue function is executed. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc. + */ +export class ContinueCancelModal extends React.Component { + protected static defaultProps = { + buttonVariant: 'primary', + modalContinueButtonLabel: 'continue', + modalCancelButtonLabel: 'doCancel', + isDisabled: false, + isSmall: true + }; + + public constructor(props: ContinueCancelModalProps) { + super(props); + this.state = { + isModalOpen: false + }; + } + + private handleModalToggle = () => { + this.setState(({ isModalOpen }) => ({ + isModalOpen: !isModalOpen + })); + if (this.props.onClose) this.props.onClose(); + }; + + private handleContinue = () => { + this.handleModalToggle(); + this.props.onContinue(); + } + + public render(): React.ReactNode { + const { isModalOpen } = this.state; + + return ( + + {!this.props.render && + } + {this.props.render && this.props.render(this.handleModalToggle)} + + + , + + ]} + > + { !this.props.modalMessage && this.props.children} + { this.props.modalMessage && } + + + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/EmptyMessageState.tsx b/keycloak.v2/account/src/app/widgets/EmptyMessageState.tsx new file mode 100644 index 0000000..a3aaa8e --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/EmptyMessageState.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import { + EmptyState, + EmptyStateVariant, + Title, + EmptyStateIcon, + TitleLevel, + EmptyStateBody, + IconProps, +} from '@patternfly/react-core' + +import { Msg } from './Msg'; + +export interface EmptyMessageStateProps { + icon: React.FunctionComponent; + messageKey: string; +} + +export default class EmptyMessageState extends React.Component { + constructor(props: EmptyMessageStateProps) { + super(props); + } + + render() { + return ( + + + + <Msg msgKey={this.props.messageKey} /> + + + {this.props.children} + + + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/LocaleSelectors.tsx b/keycloak.v2/account/src/app/widgets/LocaleSelectors.tsx new file mode 100644 index 0000000..be04b04 --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/LocaleSelectors.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as React from 'react'; + +import { + FormSelect, + FormSelectOption, + FormSelectProps +} from '@patternfly/react-core'; +import { Msg } from './Msg'; + +interface AvailableLocale { + locale: string; + label: string; +}; +declare const availableLocales: [AvailableLocale]; + +interface LocaleSelectorProps extends Omit { } +interface LocaleSelectorState { } +export class LocaleSelector extends React.Component { + + constructor(props: LocaleSelectorProps) { + super(props); + } + + render(): React.ReactNode { + return ( + { if (this.props.onChange) this.props.onChange(value, event) }} + aria-label={Msg.localize('selectLocale')} + > + {availableLocales.map((locale, index) => + ) + } + + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/Logout.tsx b/keycloak.v2/account/src/app/widgets/Logout.tsx new file mode 100644 index 0000000..cb59109 --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/Logout.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {Msg} from './Msg'; +import {KeycloakService} from '../keycloak-service/keycloak.service'; +import { KeycloakContext } from '../keycloak-service/KeycloakContext'; + +import {Button, DropdownItem} from '@patternfly/react-core'; + +function handleLogout(keycloak: KeycloakService): void { + keycloak.logout(); +} + +interface LogoutProps {} +export class LogoutButton extends React.Component { + public render(): React.ReactNode { + return ( + + { keycloak => ( + + )} + + + ); + } +} + +interface LogoutDropdownItemProps {} +export class LogoutDropdownItem extends React.Component { + public render(): React.ReactNode { + return ( + + { keycloak => ( + handleLogout(keycloak!)}> + {Msg.localize('doSignOut')} + + )} + + ); + } +} \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/Msg.tsx b/keycloak.v2/account/src/app/widgets/Msg.tsx new file mode 100644 index 0000000..9d20b2d --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/Msg.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +declare const l18nMsg: {[key: string]: string}; + +export interface MsgProps { + readonly msgKey: string; + readonly params?: string[]; +} + +export class Msg extends React.Component { + + public constructor(props: MsgProps) { + super(props); + } + + public render(): React.ReactNode { + if (this.props.children) { + return Msg.localizeWithChildren(this.props.msgKey, this.props.children); + } + return ( + {Msg.localize(this.props.msgKey, this.props.params)} + ); + } + + private static localizeWithChildren(msgKey: string, children: React.ReactNode): React.ReactNode { + const message: string = l18nMsg[this.processKey(msgKey)]; + const parts = message.split(/\{\{param_\d*}}/); + const count = React.Children.count(children); + return React.Children.map(children, (child, i) => + [parts[i], child, count === i + 1 ? parts[count] : ''] + ); + } + + public static localize(msgKey: string, params?: string[]): string { + let message: string = l18nMsg[this.processKey(msgKey)]; + if (message === undefined) message = msgKey; + + if ((params !== undefined) && (params.length > 0)) { + params.forEach((value: string, index: number) => { + value = this.processParam(value); + message = message.replace('{{param_'+ index + '}}', value); + }) + } + + return unescape(message); + } + + // if the message key has Freemarker syntax, remove it + private static processKey(msgKey: string): string { + if (!(msgKey.startsWith('${') && msgKey.endsWith('}'))) return msgKey; + + // remove Freemarker syntax + return msgKey.substring(2, msgKey.length - 1); + } + + // if the param has Freemarker syntax, try to look up its value + private static processParam(param: string): string { + if (!(param.startsWith('${') && param.endsWith('}'))) return param; + + // remove Freemarker syntax + const key: string = param.substring(2, param.length - 1); + + let value: string = l18nMsg[key]; + if (value === undefined) return param; + + return value; + } +} diff --git a/keycloak.v2/account/src/app/widgets/ReferrerDropdownItem.tsx b/keycloak.v2/account/src/app/widgets/ReferrerDropdownItem.tsx new file mode 100644 index 0000000..107b8fc --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/ReferrerDropdownItem.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {Msg} from '../widgets/Msg'; + +import {DropdownItem} from '@patternfly/react-core'; +import {ArrowIcon} from '@patternfly/react-icons'; + +declare const referrerName: string; +declare const referrerUri: string; + +export interface ReferrerDropdownItemProps { +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc. + */ +export class ReferrerDropdownItem extends React.Component { + + public constructor(props: ReferrerDropdownItemProps) { + super(props); + } + + public render(): React.ReactNode { + + return ( + + {Msg.localize('backTo', [referrerName])} + + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/ReferrerLink.tsx b/keycloak.v2/account/src/app/widgets/ReferrerLink.tsx new file mode 100644 index 0000000..8755057 --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/ReferrerLink.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; + +import {Msg} from './Msg'; + +import {ArrowIcon} from '@patternfly/react-icons'; + +declare const referrerName: string; +declare const referrerUri: string; + +export interface ReferrerLinkProps { +} + +/** + * @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc. + */ +export class ReferrerLink extends React.Component { + + public constructor(props: ReferrerLinkProps) { + super(props); + } + + public render(): React.ReactNode { + return ( + // '_hash_' is a workaround for when uri encoding is not + // sufficient to escape the # character properly. + // See AppInitiatedActionPage for more details. + + + + ); + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/app/widgets/features.ts b/keycloak.v2/account/src/app/widgets/features.ts new file mode 100644 index 0000000..48e7f4a --- /dev/null +++ b/keycloak.v2/account/src/app/widgets/features.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + export interface Features { + isRegistrationEmailAsUsername: boolean; + isEditUserNameAllowed: boolean; + isInternationalizationEnabled: boolean; + isLinkedAccountsEnabled: boolean; + isEventsEnabled: boolean; + isMyResourcesEnabled: boolean; + isTotpConfigured: boolean; + deleteAccountAllowed: boolean; + } + + diff --git a/keycloak.v2/account/src/eslint.cmd b/keycloak.v2/account/src/eslint.cmd new file mode 100644 index 0000000..9a1fbd8 --- /dev/null +++ b/keycloak.v2/account/src/eslint.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\node_modules\eslint\bin\eslint.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\node_modules\eslint\bin\eslint.js" %* +) \ No newline at end of file diff --git a/keycloak.v2/account/src/package.json b/keycloak.v2/account/src/package.json new file mode 100644 index 0000000..001283d --- /dev/null +++ b/keycloak.v2/account/src/package.json @@ -0,0 +1,56 @@ +{ + "name": "keycloak.v2", + "version": "1.0.0", + "description": "keycloak account management written in React", + "scripts": { + "build": "snowpack --optimize && npm run check-types && npm run babel && npm run move-web_modules && npm run copy-pf-resources", + "babel": "babel --source-maps --extensions \".js,.ts,.tsx\" app/ --out-dir ../resources/", + "babel:watch": "npm run babel -- --watch", + "check-types": "tsc --noImplicitAny --strictNullChecks --jsx react -p ./", + "check-types:watch": "npm run check-types -- -w", + "lint": "eslint ./app/**/*.ts*", + "move-web_modules": "shx mv web_modules ../../../keycloak/common/resources", + "copy-pf-resources": "npm run move-app-css && npm run copy-base-css && npm run copy-fonts && npm run copy-pficon", + "move-app-css": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles && shx mv app.css ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles", + "copy-base-css": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles && shx cp node_modules/@patternfly/react-core/dist/styles/base.css ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles", + "copy-fonts": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/fonts/overpass-webfont && shx cp node_modules/@patternfly/react-core/dist/styles/assets/fonts/overpass-webfont/overpass*.woff2 ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/fonts/overpass-webfont", + "copy-pficon": "shx mkdir -p ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/pficon && shx cp node_modules/@patternfly/react-core/dist/styles/assets/pficon/pficon.woff2 ../../../keycloak/common/resources/web_modules/@patternfly/react-core/dist/styles/assets/pficon" + }, + "keywords": [], + "author": "Stan Silvert", + "license": "Apache-2.0", + "dependencies": { + "@patternfly/react-core": "^3.153.3", + "@patternfly/react-icons": "^3.15.16", + "@patternfly/react-styles": "^3.7.14", + "react": "npm:@pika/react@^16.13.1", + "react-dom": "npm:@pika/react-dom@^16.13.1", + "react-router-dom": "^4.3.1" + }, + "devDependencies": { + "@babel/cli": "^7.8.4", + "@babel/core": "^7.8.7", + "@babel/plugin-proposal-class-properties": "^7.8.3", + "@babel/preset-env": "^7.8.7", + "@babel/preset-react": "^7.8.3", + "@babel/preset-typescript": "^7.8.3", + "@types/node": "^13.9.8", + "@types/react": "^16.9.23", + "@types/react-dom": "^16.9.5", + "@types/react-router-dom": "^4.3.1", + "@typescript-eslint/eslint-plugin": "^1.4.2", + "@typescript-eslint/parser": "^1.4.2", + "babel-eslint": "^9.0.0", + "eslint": "^5.15.1", + "eslint-config-react-app": "^3.0.8", + "eslint-plugin-flowtype": "^2.50.3", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-react": "^7.12.4", + "rollup-plugin-postcss": "^2.5.0", + "shx": "^0.3.2", + "snowpack": "^1.7.1", + "typescript": "^3.8.3" + }, + "repository": {} +} diff --git a/keycloak.v2/account/src/snowpack.config.js b/keycloak.v2/account/src/snowpack.config.js new file mode 100644 index 0000000..ab8ee0c --- /dev/null +++ b/keycloak.v2/account/src/snowpack.config.js @@ -0,0 +1,11 @@ +const postcss = require('rollup-plugin-postcss'); + +module.exports = { + rollup: { + plugins: [ + postcss({ + extract: 'app.css' + }) + ] + } +}; \ No newline at end of file diff --git a/keycloak.v2/account/src/tsconfig.json b/keycloak.v2/account/src/tsconfig.json new file mode 100644 index 0000000..e4e3273 --- /dev/null +++ b/keycloak.v2/account/src/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "noEmit": true, + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom", "dom.iterable" ], + "noImplicitAny": true, + "strictNullChecks": true, + "jsx": "react", + "suppressImplicitAnyIndexErrors": true + }, + "include": [ + "./app/**/*.ts?" + ], + "files": [ + "../../../../../../../../adapters/oidc/js/src/main/resources/keycloak.d.ts" + ] +} diff --git a/keycloak.v2/account/theme.properties b/keycloak.v2/account/theme.properties new file mode 100644 index 0000000..b41dd5b --- /dev/null +++ b/keycloak.v2/account/theme.properties @@ -0,0 +1,16 @@ +parent=base +deprecatedMode=false +scripts=welcome-page-scripts.js +developmentMode=false + +# This is the logo in upper lefthand corner. +# It must be a relative path. +logo=/public/logo.svg + +# This is the link followed when clicking on the logo. +# It can be any valid URL, including an external site. +logoUrl=./ + +# This is the icon for the account console. +# It must be a relative path. +favIcon=/public/favicon.ico diff --git a/account/resources/css/account.css b/keycloak/account/resources/css/account.css similarity index 100% rename from account/resources/css/account.css rename to keycloak/account/resources/css/account.css diff --git a/account/resources/img/icon-sidebar-active.png b/keycloak/account/resources/img/icon-sidebar-active.png similarity index 100% rename from account/resources/img/icon-sidebar-active.png rename to keycloak/account/resources/img/icon-sidebar-active.png diff --git a/account/resources/img/keycloak-logo.png b/keycloak/account/resources/img/keycloak-logo.png similarity index 100% rename from account/resources/img/keycloak-logo.png rename to keycloak/account/resources/img/keycloak-logo.png diff --git a/account/resources/img/logo.png b/keycloak/account/resources/img/logo.png similarity index 100% rename from account/resources/img/logo.png rename to keycloak/account/resources/img/logo.png diff --git a/account/theme.properties b/keycloak/account/theme.properties similarity index 100% rename from account/theme.properties rename to keycloak/account/theme.properties diff --git a/admin/resources/css/styles.css b/keycloak/admin/resources/css/styles.css similarity index 100% rename from admin/resources/css/styles.css rename to keycloak/admin/resources/css/styles.css diff --git a/admin/resources/img/keyclok-logo.png b/keycloak/admin/resources/img/keyclok-logo.png similarity index 100% rename from admin/resources/img/keyclok-logo.png rename to keycloak/admin/resources/img/keyclok-logo.png diff --git a/admin/resources/img/keyclok-logo.svg b/keycloak/admin/resources/img/keyclok-logo.svg similarity index 100% rename from admin/resources/img/keyclok-logo.svg rename to keycloak/admin/resources/img/keyclok-logo.svg diff --git a/admin/resources/img/select-arrow.png b/keycloak/admin/resources/img/select-arrow.png similarity index 100% rename from admin/resources/img/select-arrow.png rename to keycloak/admin/resources/img/select-arrow.png diff --git a/admin/theme.properties b/keycloak/admin/theme.properties similarity index 100% rename from admin/theme.properties rename to keycloak/admin/theme.properties diff --git a/keycloak/common/resources/img/favicon.ico b/keycloak/common/resources/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..48188dedaaed851f39dcf435d3bde8e0dce753fd GIT binary patch literal 627 zcmV-(0*w8MP)fnm`u?`7EDZT zU@#004h&*6qDT$3A+1HARIlxQw0Gw~Fqjw}@R{Bx@00f{&kMVRw3Fz%ejPaV7yTE* zFfRVFfUfKNfww^PA5AYAhLPDOVn^Z@5Uq#8eCj{K^1uiH-%|sWPn^Z+JGia8uj_ip zZxduPnQOLfKTs5fNvoYXN5LH&;bDG;iSDDcboJ0G%bfWzLoC#S@B5!sRW1H1Fh4(U zCK8G8Z0;+U=JG_6dl~BMWvaKyqfNox4TZyDm6NS?PHPsf>mtkY)(X?p)044SEDSKQ zTqY7~#q8?iUXO?Cx}0ybNC=-sv&nO(ja);Zsw$GSbv}_!r^h5o;>pJq=GIKi)BxwS zIvtLQX_~mM%k55!1cRzr-Bk6$hn3YADC!p(StiU*sn$CQ!;;MuB0(GzS&ySNfE*{22sK>&cF zD98ZA!^0c7Ty9jX6mNc<6~^+fSY63eDwURie5q8*?sVcL zaG@TFvod&`Xu~G?W|nd+MRD*L$@~KG!V