diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7559e10
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,3 @@
+FROM quay.io/keycloak/keycloak:latest
+
+COPY ["./juice", "/opt/jboss/keycloak/themes/juice/"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 0435704..549eeb5 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,45 @@
-# keycloak-themed
+# Keycloak image with juice theme inside
+A short project that inject juice theme into a docker image.
+Juice theme should be located in `./juice` folder.
+
+!!!IMPORTANT!!!: IF YOU MAKE AN UPDATE, PLEASE UPDATE THE VERSION.INI FILE ACORDING TO STANDARD VERSIONING RULES (NUMBERS ONLY).
+
+## Docker commands
+
+### Build the image
+Build an image with tag juice-keycloak.
+
+```bash
+docker build -t juice-keycloak .
+```
+
+### Stop previously running container
+```bash
+docker stop juice-keycloak
+```
+
+### Start a container
+```bash
+docker run --rm -it -d \
+ -p 8080:8080 \
+ -e KEYCLOAK_USER=admin \
+ -e KEYCLOAK_PASSWORD=admin \
+ -e DB_VENDOR=postgres \
+ -e DB_ADDR=172.17.0.1 \
+ -e DB_DATABASE=keycloak \
+ -e DB_USER=keycloak \
+ -e DB_PASSWORD=keycloak \
+ --name juice-keycloak \
+ juice-keycloak
+```
+
+Linux: PGHOST=172.17.0.1
+Mac&Win: PGHOST=host.docker.internal
+
+## Testing
+There is a file `url.test.js` that will generate a login URL. After sucessfull login, provide the redirect url from the browser as an argument for the script to acquire tokens.
+
+The script includes installable package listed at the top.
+
+Change configuration inside of the script according to your realm settings.
\ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..ebf78ea
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+function cleanup {
+ if [ ! -z "${RPM_BUILD_ROOT}" ] && [ -d "${RPM_BUILD_ROOT}" ] && [ "${RPM_BUILD_ROOT}" != "./" ]
+ then
+ rm -rf "${RPM_BUILD_ROOT}"
+ fi
+}
+export SOURCE_DIR=`dirname $0`
+echo "${SOURCE_DIR}" |grep -q "^\."
+if [ $? -eq 0 ]
+then
+ echo "sourcdir is ."
+ export SOURCE_DIR="`pwd`/"
+fi
+echo "${SOURCE_DIR}" |grep -q "^/"
+if [ $? -ne 0 ]
+then
+ export SOURCE_DIR="`pwd`/`dirname $0`"
+fi
+echo "SOURCE_DIR: ${SOURCE_DIR}"
+export RELEASE_VERSION=`cat version.ini`
+if [ "${docker_registry}" != "" ] || [ "${1}" == "docker-image" ] || [ "${2}" == "docker-image" ]
+then
+ docker build -t ${docker_registry}${docker_registry_path_suffix}/juice-keycloak:${RELEASE_VERSION} -t ${docker_registry}${docker_registry_path_suffix}/juice-keycloak:latest . &&
+ docker login --username ${docker_registry_username} --password ${docker_registry_password} ${docker_registry} &&
+ docker push ${docker_registry}${docker_registry_path_suffix}/juice-keycloak:${RELEASE_VERSION} &&
+ docker push ${docker_registry}${docker_registry_path_suffix}/juice-keycloak:latest
+ exit $?
+fi
+
+
+if [ "${BUILD_RPM}" != "" ] || [ "${1}" == "rpm" ] || [ "${2}" == "rpm" ]
+then
+
+ export _RPMBUILD_DIR="${RPMBUILD_DIR:-/tmp/rpmbuild-juice-keycloak}"
+ export RPM_BUILD_ROOT="${RPM_BUILD_ROOT:-/tmp/build-juice-keycloak}"
+ if [ ! -d "${_RPMBUILD_DIR}" ]
+ then
+ mkdir -p "${_RPMBUILD_DIR}"
+ mkdir -p "${_RPMBUILD_DIR}"/RPMS/noarch
+ mkdir -p "${_RPMBUILD_DIR}"/SOURCES
+ mkdir -p "${_RPMBUILD_DIR}"/SPECS
+ mkdir -p "${_RPMBUILD_DIR}"/SRPMS
+ fi
+ if [ ! -d "${RPM_BUILD_ROOT}" ]
+ then
+ mkdir -p "${RPM_BUILD_ROOT}"
+ fi
+ rpmbuild --target noarch -bb "${SOURCE_DIR}/juice-keycloak.spec" --buildroot="${RPM_BUILD_ROOT}" --define "_topdir ${_RPMBUILD_DIR}" --define "_release_version ${RELEASE_VERSION}"
+fi
+
+if [ "${RUN_HOOKS}" != "" ]
+then
+ if [ "${BUILD_RPM}" != "" ] || [ "${1}" == "rpm" ] || [ "${2}" == "rpm" ]
+ then
+ run-parts "${SOURCE_DIR}/build-hooks/rpm"
+ fi
+ if [ "${docker_registry}" != "" ] || [ "${1}" == "docker-image" ] || [ "${2}" == "docker-image" ]
+ then
+ run-parts "${SOURCE_DIR}/build-hooks/docker-image"
+ fi
+fi
+cleanup
\ No newline at end of file
diff --git a/juice/account/resources/css/account.css b/juice/account/resources/css/account.css
new file mode 100644
index 0000000..3878e43
--- /dev/null
+++ b/juice/account/resources/css/account.css
@@ -0,0 +1,277 @@
+html {
+ height: 100%;
+}
+
+body {
+ background-color: #F9F9F9;
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+
+header .navbar {
+ margin-bottom: 0;
+ min-height: inherit;
+}
+
+.header .container {
+ position: relative;
+}
+
+.navbar-title {
+ background-image: url('../img/logo.png');
+ height: 25px;
+ background-repeat: no-repeat;
+ width: 123px;
+ margin: 3px 10px 5px;
+ text-indent: -99999px;
+}
+
+.navbar-pf .navbar-utility {
+ right: 20px;
+ top: -34px;
+ font-size: 12px;
+}
+
+.navbar-pf .navbar-utility > li > a {
+ color: #fff !important;
+ padding-bottom: 12px;
+ padding-top: 11px;
+ border-left: medium none;
+}
+
+.container {
+ height: 100%;
+}
+
+.content-area {
+ background-color: #fff;
+ border-color: #CECECE;
+ border-style: solid;
+ border-width: 0 1px;
+ height: 100%;
+ padding: 0 30px;
+}
+
+.margin-bottom {
+ margin-bottom: 10px;
+}
+
+/* Sidebar */
+
+.bs-sidebar {
+ background-color: #f9f9f9;
+ padding-top: 44px;
+ padding-right: 0;
+ padding-left: 0;
+ z-index: 20;
+}
+.bs-sidebar ul {
+ list-style: none;
+ padding-left: 12px;
+}
+
+.bs-sidebar ul li {
+ margin-bottom: 0.5em;
+ margin-left: -1em;
+}
+.bs-sidebar ul li a {
+ font-size: 14px;
+ padding-left: 25px;
+ color: #4d5258;
+ line-height: 28px;
+ display: block;
+ border-width: 1px 0 1px 1px;
+ border-style: solid;
+ border-color: #f9f9f9;
+}
+.bs-sidebar ul li a:hover,
+.bs-sidebar ul li a:focus {
+ text-decoration: none;
+ color: #777777;
+ border-right: 2px solid #aaa;
+}
+.bs-sidebar ul li.active a {
+ background-color: #c7e5f0;
+ border-color: #56bae0;
+ font-weight: bold;
+ background-image: url(../img/icon-sidebar-active.png);
+ background-repeat: no-repeat;
+ background-position: right center;
+}
+
+.bs-sidebar ul li.active a:hover {
+ border-right: none;
+}
+
+
+.content-area h2 {
+ font-family: "Open Sans", sans-serif;
+ font-weight: 100;
+ font-size: 24px;
+ margin-bottom: 25px;
+ margin-top: 25px;
+}
+
+.subtitle {
+ text-align: right;
+ margin-top: 30px;
+ color: #909090;
+}
+
+.required {
+ color: #CB2915;
+}
+
+
+.alert {
+ margin-top: 30px;
+ margin-bottom: 0;
+}
+
+.feedback-aligner .alert {
+ background-position: 1.27273em center;
+ background-repeat: no-repeat;
+ border-radius: 2px;
+ border-width: 1px;
+ color: #4D5258;
+ display: inline-block;
+ font-size: 1.1em;
+ line-height: 1.4em;
+ margin: 0;
+ padding: 0.909091em 3.63636em;
+ position: relative;
+ text-align: left;
+}
+.alert.alert-success {
+ background-color: #E4F1E1;
+ border-color: #4B9E39;
+}
+.alert.alert-error {
+ background-color: #F8E7E7;
+ border-color: #B91415;
+}
+.alert.alert-warning {
+ background-color: #FEF1E9;
+ border-color: #F17528;
+}
+.alert.alert-info {
+ background-color: #E4F3FA;
+ border-color: #5994B2;
+}
+
+.form-horizontal {
+ border-top: 1px solid #E9E8E8;
+ padding-top: 23px;
+}
+
+.form-horizontal .control-label {
+ color: #909090;
+ line-height: 1.4em;
+ padding-top: 5px;
+ position: relative;
+ text-align: right;
+ width: 100%;
+}
+
+.form-group {
+ position: relative;
+}
+
+.control-label + .required {
+ position: absolute;
+ right: -2px;
+ top: 0;
+}
+
+#kc-form-buttons {
+ text-align: right;
+ margin-top: 10px;
+}
+
+#kc-form-buttons .btn-primary {
+ float: right;
+ margin-left: 8px;
+}
+
+/* Authenticator page */
+
+ol {
+ padding-left: 40px;
+}
+
+ol li {
+ font-size: 13px;
+ margin-bottom: 10px;
+ position: relative;
+}
+
+ol li img {
+ margin-top: 15px;
+ margin-bottom: 5px;
+ border: 1px solid #eee;
+}
+
+hr + .form-horizontal {
+ border: none;
+ padding-top: 0;
+}
+
+.kc-dropdown{
+ position: relative;
+}
+.kc-dropdown > a{
+ display:block;
+ padding: 11px 10px 12px;
+ line-height: 12px;
+ font-size: 12px;
+ color: #fff !important;
+ text-decoration: none;
+}
+.kc-dropdown > a::after{
+ content: "\2c5";
+ margin-left: 4px;
+}
+.kc-dropdown:hover > a{
+ background-color: rgba(0,0,0,0.2);
+}
+.kc-dropdown ul li a{
+ padding: 1px 11px;
+ font-size: 12px;
+ color: #000 !important;
+ border: 1px solid #fff;
+ text-decoration: none;
+ display:block;
+ line-height: 20px;
+}
+.kc-dropdown ul li a:hover{
+ color: #4d5258;
+ background-color: #d4edfa;
+ border-color: #b3d3e7;
+}
+.kc-dropdown ul{
+ position: absolute;
+ z-index: 2000;
+ list-style:none;
+ display:none;
+ padding: 5px 0px;
+ margin: 0px;
+ background-color: #fff !important;
+ border: 1px solid #b6b6b6;
+ border-radius: 1px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ background-clip: padding-box;
+ min-width: 100px;
+}
+.kc-dropdown:hover ul{
+ display:block;
+}
+
+
+#kc-totp-secret-key {
+ border: 1px solid #eee;
+ font-size: 16px;
+ padding: 10px;
+ margin: 50px 0;
+}
\ No newline at end of file
diff --git a/juice/account/resources/img/icon-sidebar-active.png b/juice/account/resources/img/icon-sidebar-active.png
new file mode 100644
index 0000000..e7b9b08
Binary files /dev/null and b/juice/account/resources/img/icon-sidebar-active.png differ
diff --git a/juice/account/resources/img/keycloak-logo.png b/juice/account/resources/img/keycloak-logo.png
new file mode 100644
index 0000000..9555748
Binary files /dev/null and b/juice/account/resources/img/keycloak-logo.png differ
diff --git a/juice/account/resources/img/logo.png b/juice/account/resources/img/logo.png
new file mode 100644
index 0000000..a698c54
Binary files /dev/null and b/juice/account/resources/img/logo.png differ
diff --git a/juice/account/theme.properties b/juice/account/theme.properties
new file mode 100644
index 0000000..e7f1147
--- /dev/null
+++ b/juice/account/theme.properties
@@ -0,0 +1,14 @@
+parent=base
+import=common/keycloak
+
+styles=css/account.css
+stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css
+
+##### css classes for form buttons
+# main class used for all buttons
+kcButtonClass=btn
+# classes defining priority of the button - primary or default (there is typically only one priority button for the form)
+kcButtonPrimaryClass=btn-primary
+kcButtonDefaultClass=btn-default
+# classes defining size of the button
+kcButtonLargeClass=btn-lg
diff --git a/juice/admin/resources/css/styles.css b/juice/admin/resources/css/styles.css
new file mode 100644
index 0000000..681b3fc
--- /dev/null
+++ b/juice/admin/resources/css/styles.css
@@ -0,0 +1,480 @@
+html,body {
+ height: 100%;
+}
+
+form {
+ margin-top: 20px;
+}
+
+table {
+ margin-top: 20px;
+}
+
+.required {
+ color: #f00;
+}
+
+.tooltip-inner {
+ min-width: 200px;
+}
+
+.margin-top {
+ margin-top: 20px;
+}
+
+.no-margin-top {
+ margin-top: 0px !important;
+}
+
+table {
+ max-width: 100%;
+}
+
+td.clip {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 0;
+}
+
+th.w-10 {
+ width: 10%;
+}
+
+th.w-15 {
+ width: 15%;
+}
+
+th.w-20 {
+ width: 20%;
+}
+
+
+th.w-25 {
+ width: 25%;
+}
+
+th.w-30 {
+ width: 30%;
+}
+
+
+th.w-35 {
+ width: 35%;
+}
+
+th.w-40 {
+ width: 40%;
+}
+
+/*********** Loading ***********/
+
+.loading {
+ background-color: #f5f5f5;
+ border: 1px solid #eee;
+ position: absolute;
+ bottom: 0px;
+ left: 0px;
+ padding: 2px 200px 2px 5px;
+}
+
+/*********** Feedback ***********/
+
+.feedback-aligner {
+ position: fixed;
+ top: 15px;
+ text-align: center;
+ width: 100%;
+ height: 0;
+ z-index: 100;
+}
+.feedback-aligner .alert {
+ border-radius: 2px;
+ border-width: 1px;
+ display: inline-block;
+ position: relative;
+}
+
+/*********** On-Off Switch ***********/
+
+.onoffswitch {
+ -moz-user-select: none;
+ height: 26px;
+ position: relative;
+ width: 62px;
+}
+.onoffswitch .onoffswitch-checkbox {
+ display: none;
+}
+.onoffswitch .onoffswitch-label {
+ border: 1px solid #bbb;
+ border-radius: 2px;
+ cursor: pointer;
+ display: block;
+ overflow: hidden;
+ width: 62px;
+}
+.onoffswitch .onoffswitch-inner {
+ display: block;
+ margin-left: -100%;
+ transition: margin 0.3s ease-in 0s;
+ width: 200%;
+}
+.onoffswitch .onoffswitch-inner > span {
+ -moz-box-sizing: border-box;
+ color: white;
+ float: left;
+ font-size: 11px;
+ font-family: "Open Sans", sans-serif;
+ font-weight: bold;
+ height: 24px;
+ line-height: 24px;
+ padding: 0;
+ width: 50%;
+}
+.onoffswitch .onoffswitch-switch {
+ background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
+ background-image: -o-linear-gradient(top, #fafafa 0%, #ededed 100%);
+ background-image: -moz-linear-gradient(top, #fafafa 0%, #ededed 100%);
+ background-image: -webkit-linear-gradient(top, #fafafa 0%, #ededed 100%);
+ background-image: -ms-linear-gradient(top, #fafafa 0%, #ededed 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #ededed));
+ border: 1px solid #aaa;
+ border-radius: 2px;
+ bottom: 0;
+ margin: 0;
+ position: absolute;
+ right: 39px;
+ top: 0;
+ transition: all 0.3s ease-in 0s;
+ -webkit-transition: all 0.3s ease-in 0s;
+ width: 23px;
+}
+.onoffswitch .onoffswitch-inner .onoffswitch-active {
+ background-image: linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+ background-image: -o-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+ background-image: -moz-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+ background-image: -webkit-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+ background-image: -ms-linear-gradient(top, #00a9ec 0%, #009bd3 100%);
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #00a9ec), color-stop(1, 0, #009bd3));
+ color: #FFFFFF;
+ padding-left: 10px;
+}
+.onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-inner .onoffswitch-active,
+.onoffswitch-checkbox:disabled + .onoffswitch-label .onoffswitch-inner .onoffswitch-inactive {
+ background-image: none;
+ background-color: #e5e5e5;
+ color: #9d9fa1;
+}
+.onoffswitch .onoffswitch-inner .onoffswitch-inactive {
+ background: linear-gradient(#fefefe, #e8e8e8) repeat scroll 0 0 transparent;
+ color: #4d5258;
+ padding-right: 10px;
+ text-align: right;
+}
+.onoffswitch .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
+ margin-left: 0;
+}
+.onoffswitch .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
+ right: 0;
+}
+
+
+/*********** Select 2 ***********/
+
+.select2-container {
+ width: 100%;
+}
+
+.select2-container-multi .select2-choices .select2-search-field {
+ height: 26px;
+}
+
+/*********** html select ********/
+.overflow-select {
+ overflow: auto;
+}
+
+
+/*********** New Menu ***********/
+
+
+.sidebar-pf-left{
+ background: #292e34;
+}
+
+.sidebar-pf .nav-pills > li a i, .sidebar-pf .nav-pills > li a span{
+ color: #72767b;
+ display: inline-block;
+ margin-right: 10px;
+}
+.sidebar-pf .nav-pills > li > a{
+ color: #dbdada;
+ padding: 0px 20px 0 30px!important;
+ line-height: 30px;
+ border-left-width: 12px;
+ border-left-style: solid;
+ border-left-color: #292e34;
+ margin-left: -6px;
+}
+
+.sidebar-pf .nav-pills > li > a:hover{
+ background: #393f44;
+ border-color:#292e34;
+ border-left-color: #393f44;
+ color: #fff;
+}
+
+.sidebar-pf .nav-pills > li > a:after{
+ display: none!important;
+}
+
+
+.sidebar-pf .nav-pills > li.active > a {
+ color: #fff;
+ background: #393f44!important;
+ border-bottom: 1px solid #000!important;
+ border-top: 1px solid #000!important;
+ border-left-color: #39a5dc!important;
+}
+
+.sidebar-pf .nav-pills > li.active a i, .sidebar-pf .nav-pills > li.active a span{
+ color: #39a5dc;
+}
+
+/*********** Realm selector ***********/
+
+.realm-selector{
+ color: #fff;
+ margin: 0 -20px;
+ position: relative;
+}
+
+.realm-dropmenu{
+ display: none;
+ cursor: pointer;
+ position: absolute;
+ top: 60px;
+ left: 0;
+ right: 0;
+ z-index: 999;
+ background: #fff;
+}
+
+.realm-selector:hover .realm-dropmenu{
+ display: block;
+}
+
+.realm-add{
+ padding: 10px;
+}
+
+.realm-selector h2{
+ font-size: 16px;
+ line-height: 60px;
+ padding: 0 20px;
+ margin: 0;
+ border-bottom: 1px solid #d5d5d6;
+}
+
+.realm-selector h2 i{
+ display: inline-block;
+ float: right;
+ line-height: 60px;
+}
+
+
+.realm-selector ul{
+ padding-left: 0;
+ margin: 0;
+ list-style: none;
+ max-height: 200px;
+ overflow-y:auto;
+}
+
+
+.realm-selector ul li a{
+ line-height: 60px;
+ padding: 0 20px;
+ border-bottom: 1px solid #d5d5d6;
+ line-height: 39px;
+ display: block;
+ font-size: 14px;
+}
+
+
+/*********** Overwrites header defaults ***********/
+
+.navbar-pf{
+ border-top: none!important;
+}
+
+.navbar-pf .navbar-brand {
+ padding: 0;
+ height: 56px;
+ line-height: 56px;
+ background-position: center center;
+ background-image: url('../img/keyclok-logo.png');
+ background-size: 148px 30px;
+ background-repeat: no-repeat;
+ width: 148px;
+}
+
+.navbar-pf .navbar-utility .dropdown-toggle {
+ padding: 23px !important;
+}
+
+.clickable {
+ cursor: pointer;
+}
+
+h1 i {
+ color: #999999;
+ font-size: 18px;
+ margin-left: 10px;
+}
+
+/* Action cell */
+.kc-action-cell {
+ background-color: #eeeeee;
+ background-image: linear-gradient(to bottom, #fafafa 0%, #ededed 100%);
+ background-repeat: repeat-x;
+
+ text-align: center;
+ vertical-align: middle;
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ cursor:pointer;
+}
+
+.kc-action-cell:hover {
+ background-color: #eeeeee;
+ background-image: none;
+}
+
+.kc-sorter span {
+ margin-left: 10px;
+}
+
+
+/* Time selector */
+
+.time-selector input {
+ display: inline-block;
+ width: 120px;
+ padding-right: 0;
+ margin-right: 0;
+}
+
+.time-selector select {
+ display: inline-block;
+ width: 80px;
+ margin-left: 0;
+ padding-left: 0;
+}
+
+.ace_editor {
+ height: 600px;
+ width: 100%;
+}
+
+.kc-button-input-file input {
+ float: left;
+ width: 73%;
+}
+
+.kc-button-input-file label {
+ float: left;
+ margin-left: 2%;
+ width: 25%;
+}
+
+table.kc-authz-table-expanded {
+ margin-top: 0px !important;
+}
+
+.no-gutter > [class*='col-'] {
+ padding-right:0!important;
+ padding-left:0!important;
+}
+
+.password-conceal {
+ font-family: 'text-security-disc';
+ font-size: 14px;
+}
+
+/* Deactivation styles for user-group membership tree models */
+
+div[tree-model] li .deactivate {
+ color: #4a5053;
+ opacity: 0.4;
+}
+
+div[tree-model] li .deactivate_selected {
+ background-color: #dcdcdc;
+ font-weight: bold;
+ padding: 1px 5px;
+}
+
+/* search highlighting */
+
+div[tree-model] li .highlight {
+ background-color: #aaddff;
+}
+
+/* Manage credentials */
+table.credentials-table {
+ margin-top: 0;
+ margin-bottom: 20px;
+}
+
+table.credentials-table td {
+ vertical-align: middle !important;
+}
+
+table.credentials-table input[type='text'] {
+ width: 100%;
+}
+
+td.credential-arrows-cell {
+ width: 75px;
+}
+
+td.credential-label-cell {
+ padding: 5px !important;
+}
+
+td.credential-action-cell {
+ padding: 0px !important;
+}
+
+td.credential-action-cell div.kc-action-cell {
+ width: 100%;
+ height: 36px;
+ line-height: 34px;
+}
+
+td.credential-action-cell.expanded div.kc-action-cell {
+ border-bottom: 1px solid #d1d1d1;
+}
+
+table.credential-data-table td {
+ word-break: break-all;
+}
+
+table.credential-data-table tr:first-child td {
+ border-top: 0;
+}
+
+table.credential-data-table td:first-child {
+ width: 150px;
+}
+
+table.credential-data-table td.key {
+ text-align: right;
+ font-weight: bold;
+}
+
diff --git a/juice/admin/resources/img/keyclok-logo.png b/juice/admin/resources/img/keyclok-logo.png
new file mode 100644
index 0000000..ca53f0a
Binary files /dev/null and b/juice/admin/resources/img/keyclok-logo.png differ
diff --git a/juice/admin/resources/img/keyclok-logo.svg b/juice/admin/resources/img/keyclok-logo.svg
new file mode 100644
index 0000000..05fa87b
--- /dev/null
+++ b/juice/admin/resources/img/keyclok-logo.svg
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/juice/admin/resources/img/select-arrow.png b/juice/admin/resources/img/select-arrow.png
new file mode 100644
index 0000000..a865a6f
Binary files /dev/null and b/juice/admin/resources/img/select-arrow.png differ
diff --git a/juice/admin/theme.properties b/juice/admin/theme.properties
new file mode 100644
index 0000000..d196266
--- /dev/null
+++ b/juice/admin/theme.properties
@@ -0,0 +1,5 @@
+parent=base
+import=common/keycloak
+
+styles=css/styles.css css/mytheme.css
+stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css node_modules/select2/select2.css lib/angular/treeview/css/angular.treeview.css node_modules/text-security/text-security.css css/mytheme.css
diff --git a/juice/email/theme.properties b/juice/email/theme.properties
new file mode 100644
index 0000000..f1dbb72
--- /dev/null
+++ b/juice/email/theme.properties
@@ -0,0 +1 @@
+parent=base
\ No newline at end of file
diff --git a/juice/login/error.ftl b/juice/login/error.ftl
new file mode 100644
index 0000000..7115b16
--- /dev/null
+++ b/juice/login/error.ftl
@@ -0,0 +1,13 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=false; section>
+ <#if section = "header">
+
+
+
+
+ #if>
+@layout.registrationLayout>
+
+
diff --git a/juice/login/messages/messages_de.properties b/juice/login/messages/messages_de.properties
new file mode 100644
index 0000000..496ce3b
--- /dev/null
+++ b/juice/login/messages/messages_de.properties
@@ -0,0 +1,35 @@
+welcome=Willkommen!
+error=Fehler
+signinTerms=Mit der Registrierung erkl\u00E4ren Sie sich mit den Nutzungsbedingungen und der Datenschutzerkl\u00E4rung von Juice einverstanden.
+signupTerms=Mit der Registrierung erkl\u00E4ren Sie sich mit den Nutzungsbedingungen und der Datenschutzerkl\u00E4rung von Juice einverstanden.
+signInHere=Hier anmelden
+signUpHere=Mit Email registrieren
+passwordRecovery=Passwort wiederherstellen
+
+loginAccountTitle=Anmelden
+doRegister=Registrieren
+doForgotPassword=Passwort vergessen?
+noAccount=Haben Sie ein Konto?
+
+emailForgotTitle=Ihr Passwort
+emailForgotTitle1=zur\u00FCcksetzen
+emailInstruction=Bitte geben Sie Ihre E-Mail Adresse ein
+emailInstruction1=Wir senden Ihnen dann eine E-Mail, um Ihr Passwort zur\u00FCckzusetzen.
+
+doSubmit=E-Mail senden
+backToLogin=Zur\u00FCck
+haveAccount=Haben Sie ein Konto?
+
+registerTitle=Welcome to the
+registerTitle1=JUICE WORLD
+
+emailVerifyTitle=Verifizieren Sie Ihre E-Mail
+emailVerifyInstruction1=Sie m\u00FCssen Ihre E-Mail-Adresse verifizieren, um Ihr Konto zu aktivieren. Eine E-Mail mit Anweisungen zur Verifizierung Ihrer E-Mail-Adresse wurde an Sie gesendet
+emailVerifyInstruction3=Senden Sie die E-Mail erneut
+identity-provider-login-label=Oder registrieren/anmelden mit
+
+pageExpiredTitle=Ihre Sitzung ist abgelaufen
+pageExpiredMsg1=Keine Sorge, melden Sie sich einfach erneut an
+
+pageExpiredButton=Gehen Sie zu Anmelden
+
diff --git a/juice/login/messages/messages_en.properties b/juice/login/messages/messages_en.properties
new file mode 100644
index 0000000..cade1f7
--- /dev/null
+++ b/juice/login/messages/messages_en.properties
@@ -0,0 +1,33 @@
+welcome=Welcome!
+error=Error
+signinTerms=By signing up, you agree to Juice''s ToS and Privacy
+signupTerms=By signing up, you agree to Juice''s Terms of Use
+signInHere=Sign in here
+signUpHere=Sign up with the email
+passwordRecovery=Password recovery
+
+loginAccountTitle=Sign in
+doRegister=Sign up
+doForgotPassword=Forgot password?
+noAccount=Not a member?
+
+emailForgotTitle=Reset
+emailForgotTitle1=your password
+emailInstruction=Please enter your email address.
+emailInstruction1=We will send you an email to reset your password.
+
+doSubmit=Send email
+backToLogin=Go back
+haveAccount=Have an account?
+
+registerTitle=Welcome to the
+registerTitle1=JUICE WORLD
+
+emailVerifyTitle=Verify your email
+emailVerifyInstruction1=You need to verify your email address to activate your account. An email with instructions to verify your email address has been sent to you
+emailVerifyInstruction3=Re-send the email
+
+pageExpiredTitle=Your session has expired
+pageExpiredMsg1=No worry, simply sign in again
+
+pageExpiredButton=Go to Sign In
\ No newline at end of file
diff --git a/juice/login/register.ftl b/juice/login/register.ftl
new file mode 100644
index 0000000..03c8027
--- /dev/null
+++ b/juice/login/register.ftl
@@ -0,0 +1,139 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section>
+ <#if section = "header">
+
+
+ <#if realm.internationalizationEnabled && locale.supported?size gt 1>
+
+ #if>
+
+
+ <#elseif section = "form">
+
+ #if>
+@layout.registrationLayout>
+
+
\ No newline at end of file
diff --git a/juice/login/resources/css/styles.css b/juice/login/resources/css/styles.css
new file mode 100644
index 0000000..b1027e4
--- /dev/null
+++ b/juice/login/resources/css/styles.css
@@ -0,0 +1,758 @@
+@font-face {
+ font-family: "Swis721 Th Bt";
+ src: local("Swiss721ThinBT.ttf"), url("../fonts/Swiss721ThinBT.ttf") format("truetype");
+ font-weight: 300;
+}
+@font-face {
+ font-family: "Swis721 Md BT";
+ src: local("Swiss721MediumBT.ttf"), url("../fonts/Swiss721MediumBT.ttf") format("truetype");
+ font-weight: 500;
+}
+@font-face {
+ font-family: "Roboto";
+ src: local("Roboto-Medium.ttf"), url("../fonts/Roboto-Medium.ttf") format("truetype");
+ font-weight: 300;
+}
+
+.login-pf {
+ height: 100%;
+ background: none;
+}
+
+.login-pf body {
+ background: url(../img/juice-bc.svg);
+ background-repeat: no-repeat;
+ background-color: #202020;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.login-pf body::before {
+ position: absolute;
+ width: 56px;
+ height: 56px;
+ content: "";
+ top: 56px;
+ left: 63px;
+ background: url(../img/juice.svg);
+ background-repeat: no-repeat;
+}
+
+.login-pf-page {
+ padding-top: 0px;
+}
+
+.h1, .h2, .h3, h1, h2, h3 {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+#kc-header-wrapper {
+ padding: 0px 10px;
+}
+
+.card-login {
+ margin: 0 auto;
+ box-shadow: -8px -8px 32px rgba(101, 101, 101, 0.12), -2px -2px 16px rgba(101, 101, 101, 0.24), 4px 4px 16px rgba(0, 0, 0, 0.3), 8px 8px 32px rgba(0, 0, 0, 0.1);
+ padding: 56px;
+ min-width: 440px;
+ max-width: 440px;
+ border-radius: 16px;
+ background-color: #212121;
+}
+
+.login-pf-page-header {
+ background: url(../img/juice.svg);
+ background-position-x: 63px;
+ background-position-y: 56px;
+ background-repeat: no-repeat;
+}
+.header-login-welcome {
+ font-family: "Swis721 Th Bt";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #98989E;
+}
+.header-login-title {
+ padding-top: 4px;
+ font-family: "Swis721 Th Bt";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 38px;
+ line-height: 48px;
+ letter-spacing: -0.75px;
+ background: linear-gradient(180deg, #FF9900 0%, #FF7100 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.error-welcome {
+ background: #F52E2E;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+.error-image {
+ width: 96px;
+ height: 96px;
+ background: url(../img/error.svg);
+ margin: 32px 0px 12px;
+}
+.verify-image {
+ width: 96px;
+ height: 96px;
+ background: url(../img/verify-email.svg);
+ margin: 32px 0px 12px;
+}
+.instruction {
+ margin: 0px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 19px;
+ line-height: 28px;
+ display: flex;
+ align-items: center;
+ letter-spacing: -0.1px;
+ color: #BDBFBF;
+}
+#kc-error-message .instruction {
+ margin: 0px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 19px;
+ line-height: 28px;
+ display: flex;
+ align-items: center;
+ letter-spacing: -0.1px;
+ color: #BDBFBF;
+}
+#kc-info {
+ margin: 0px;
+}
+.verify-email-question {
+ margin-top: 32px;
+ margin-bottom: 8px;
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 16px;
+ display: flex;
+ align-items: center;
+ color: #BDBFBF;
+}
+.login-pf-page .login-pf-signup a {
+ margin-left: 0px;
+}
+.login-pf-page .login-pf-settings {
+ flex-wrap: nowrap;
+}
+.verify-email-link {
+ margin-left: 0px;
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 16px;
+ display: flex;
+ align-items: center;
+ text-align: right;
+ color: #FF7100;
+}
+a.verify-email-link:hover {
+ color: #FF7100;
+}
+
+#kc-page-title{
+ text-align: left;
+}
+
+#kc-form-buttons {
+ margin-top: 32px;
+ margin-bottom: 32px;
+}
+
+.pf-c-button {
+ height: 56px;
+ background: #181818;
+ box-shadow: -4px -4px 8px rgba(101, 101, 101, 0.08), -1px -1px 3px rgba(101, 101, 101, 0.12), 1px 1px 4px rgba(0, 0, 0, 0.3), 4px 4px 8px rgba(0, 0, 0, 0.4);
+ border-radius: 4px;
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+ text-align: center;
+ color: #BDBFBF;
+ text-transform: capitalize;
+}
+.pf-c-button:hover,
+.pf-c-button:focus {
+ background: #292929;
+}
+.pf-c-button:disabled {
+ background: #292929;
+ opacity: 0.3;
+}
+
+.pf-c-form-control{
+ position: relative;
+ min-height: 56px;
+ border: 1px solid #333333;
+ box-sizing: border-box;
+ border-radius: 4px;
+ background: #212121;
+ font-family: "Swis721 Th BT";
+ padding-left: 16px;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 24px;
+ color: #BDBFBF;
+}
+
+.form-group {
+ position: relative;
+ margin-bottom: 24px;
+}
+
+#input-error,
+#input-error-username,
+#input-error-email,
+#input-error-password,
+#input-error-password-confirm {
+ margin-top: 8px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #BDBFBF;
+}
+
+
+.pf-c-form-control:focus, .pf-c-form-control:hover{
+ outline: none;
+ border: 1px solid rgba(255, 113, 0, 1);
+ caret-color: rgba(255, 113, 0, 1);
+ font-family: Swis721 Th BT;
+}
+.pf-c-form-control[aria-invalid="true"] {
+ border: 1px solid rgba(245, 46, 46, 1);
+ background: #212121;
+}
+.login-form-input-label {
+ position: absolute;
+ display: none;
+ padding: 0px 4px;
+ top: -10px;
+ left: 12px;
+ color:rgba(255, 113, 0, 1);
+ z-index: 100;
+ background-color: #212121;
+}
+.pf-c-form-control:focus ~ .login-form-input-label,
+.pf-c-form-control:hover ~ .login-form-input-label,
+.pf-c-form-control[aria-invalid="true"] ~ .login-form-input-label {
+ display: block;
+}
+
+.pf-c-form-control[aria-invalid="true"] ~ .login-form-input-label {
+ color: rgba(245, 46, 46, 1);
+}
+#kc-form-wrapper a {
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 16px;
+ color: rgba(189, 191, 191, 1);
+}
+#kc-registration {
+ width: 200px;
+}
+#kc-registration span {
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 16px;
+ color: #414142;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+}
+
+#kc-registration a {
+ padding-left: 8px;
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 16px;
+ color: #FFFFFF;
+}
+#kc-registration a::after {
+ content: " →";
+}
+
+#kc-info-wrapper {
+ padding: 0px;
+ background-color: #212121;
+}
+.register-form {
+ margin-bottom: 32px;
+}
+
+.juice-footer {
+ position: absolute;
+ right: 56px;
+ bottom: 56px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 12px;
+ line-height: 16px;
+ color: #BDBFBF;
+}
+
+.juice-footer span {
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 20px;
+ color: #BDBFBF;
+}
+
+/* password */
+#kc-form-options {
+ display: flex;
+ align-items: center;
+ width: 145px;
+}
+
+#kc-form-options.signup-password {
+ width: 140px;
+}
+
+#kc-form-options a {
+ font-family: "Swis721 Md BT";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 16px;
+ color: #FFFFFF;
+}
+
+.email-instruction {
+ padding-top: 12px;
+ padding-bottom: 32px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 19px;
+ line-height: 28px;
+ display: flex;
+ align-items: center;
+ letter-spacing: -0.1px;
+ color: #98989E;
+}
+
+/* sign-up */
+
+.signup-terms {
+ margin: 0 auto;
+ width: 272px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 12px;
+ line-height: 16px;
+ text-align: center;
+ color: #98989E;
+}
+.login-signup-terms {
+ margin: 32px auto;
+}
+
+.form-horizontal .form-group {
+ margin-right: 0px;
+ margin-left: 0px;
+}
+
+/* alert */
+
+.pf-c-alert {
+ position: absolute;
+ bottom: 2.34%;
+
+ width: 328px;
+ min-height: 68px;
+ background: rgba(0, 0, 0, 0.87);
+ box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.14), 0px 1px 18px rgba(0, 0, 0, 0.12), 0px 3px 5px rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+ z-index: 100;
+}
+.pf-c-alert.pf-m-inline {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: none;
+ margin-bottom: 0px;
+ padding: 0px;
+}
+.pf-c-alert__title {
+ width: 321px;
+ min-height: 68px;
+ padding-right: 8px;
+ padding-left: 15px;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #FFFFFF;
+}
+.pf-c-alert.pf-m-inline::before {
+ content: none;
+}
+.alert-button {
+ width: 65,79px;
+ height: 36px;
+ margin-right: 9px;
+ padding: 10px 9px 10px 30px;
+ font-family: "Roboto";
+ font-style: normal;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 16px;
+ text-align: right;
+ letter-spacing: 1.25px;
+ text-transform: uppercase;
+ border: none;
+ outline: none;
+ background: linear-gradient(180deg, #FF9900 0%, #FF7100 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+.welcome-container {
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+}
+
+#kc-current-locale-link {
+ position: relative;
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #BDBFBF;
+}
+#kc-locale-dropdown a {
+ color: #BDBFBF;
+}
+
+#kc-current-locale-link::before {
+ content: '';
+ position: absolute;
+ background: url(../img/locale.svg);
+ width: 16px;
+ height: 16px;
+ top: 2px;
+ left: -17px;
+}
+
+#kc-current-locale-link::after {
+ display: none;
+}
+
+#kc-locale ul {
+ list-style: none;
+ top: 20px;
+ padding: 2px 0;
+ width: 136px;
+ background: rgba(23, 23, 23, 0.92);
+ box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.32);
+ border-radius: 8px;
+ border: none;
+}
+.kc-dropdown-item {
+ position: relative;
+ height: 40px;
+ padding-left: 30px;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+}
+.kc-dropdown-item::before {
+ position: absolute;
+ width: 16px;
+ height: 12px;
+ content: '';
+ left: 16px;
+}
+.kc-dropdown-item.English::before {
+ background: url(../img/english.svg);
+}
+.kc-dropdown-item.Deutsch::before {
+ background: url(../img/deutsch.svg);
+}
+
+#kc-locale ul li a {
+ font-family: "Swis721 Th BT";
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #BDBFBF !important;
+}
+#kc-locale .kc-dropdown-item.selected a {
+ font-family: "Swis721 Md BT";
+}
+
+#kc-locale ul li a:hover {
+ background: rgba(23, 23, 23, 0.92);
+ color: #FF7100 !important;
+}
+.kc-social-grid {
+ display: flex;
+ justify-content: center;
+ flex-wrap: nowrap;
+ grid-column-gap: 0px;
+}
+.social-button {
+ margin: 0 11px;
+ width: 48px !important;
+ height: 48px;
+ padding: 16px;
+ background: #212121;
+ border: none;
+ border-radius: 8px;
+}
+.social-button:last-child {
+ margin-right: 0;
+}
+.social-button:first-child {
+ margin-left: 0;
+}
+.separate-container {
+ margin: 32px 0px 32px;
+ display: flex;
+ align-items: center;
+}
+.separate-text {
+ margin: 0px 8px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 24px;
+ display: flex;
+ align-items: center;
+ text-align: center;
+ color: #98989E;
+}
+.separator {
+ flex: auto;
+ max-width: 109px;
+ min-width: 10px;
+ height: 1px;
+ background-color: #333333;
+}
+
+.login-sign-image {
+ background: url(../img/apple.svg);
+ width: 16px;
+ height: 16px;
+ margin-right: 16px;
+}
+
+.login-sign-image.apple {
+ background: url(../img/apple.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.google {
+ background: url(../img/google.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.facebook {
+ background: url(../img/facebook.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.linkedin {
+ background: url(../img/linkedin.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.twitter {
+ background: url(../img/twitter.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+.kc-social-provider-logo {
+ font-size: 17px;
+ width: 13px;
+ height: 13px;
+ float: left;
+}
+.expired-image {
+ width: 96px;
+ height: 96px;
+ background: url(../img/clock.svg);
+ margin: 32px 0px 2px;
+}
+.expired-button {
+ margin-top: 32px;
+ text-align: center;
+ padding-top: 16px;
+ text-transform: none;
+}
+.expired-title {
+ width: 215px;
+}
+.login-pf a:hover {
+ text-decoration: none;
+ color: #BDBFBF;
+}
+
+@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+ .header-login-title {
+ background: #202020;
+ color: #FF7100;
+ }
+ .error-welcome {
+ background: #202020;
+ color: #F52E2E;
+ }
+ .alert-button {
+ background: #202020;
+ color: #FF7100;
+ }
+}
+
+@media (max-height: 900px) and (max-width: 880px) {
+ .login-pf body::before {
+ display: none;
+ }
+ .juice-footer {
+ display: none;
+ }
+}
+
+@media (max-height: 680px) {
+ .login-pf body {
+ align-items: unset;
+ }
+}
+
+@media (max-width: 767px) {
+ #kc-locale {
+ position: relative !important;
+ width: inherit;
+ top: 0px;
+ right: 0px;
+ text-align: none;
+ }
+
+ #kc-info-wrapper {
+ border-top: none;
+ }
+}
+
+@media (min-width: 768px) {
+ .login-pf-page .login-pf-page-header {
+ margin-bottom: 0px;
+ }
+}
+
+
+@media (max-width: 440px) {
+ .login-pf body::before {
+ display: none;
+ }
+
+ .login-pf body {
+ align-items: unset;
+ }
+ .login-pf-page .login-pf-header {
+ margin-bottom: 32px;
+ }
+ #kc-header {
+ display: none;
+ }
+ .card-login {
+ min-width: 328px;
+ max-width: 448px;
+ padding: 16px 10px;
+ box-shadow: none;
+ }
+ .juice-footer {
+ display: none;
+ }
+}
+
+@media (max-height: 440px) {
+ .login-pf body::before {
+ display: none;
+ }
+ .juice-footer {
+ display: none;
+ }
+}
+
+@media (max-width: 360px) {
+ .card-login {
+ width: 360px;
+ padding: 16px 10px;
+ box-shadow: none;
+ }
+}
+
+@media (max-width: 359px) {
+ .card-login {
+ min-width: 248px;
+ max-width: 304px;
+ padding: 16px 10px;
+ box-shadow: none;
+ }
+ #kc-registration {
+ width: 155px;
+ }
+ .social-button {
+ padding: 16px 12px;
+ }
+}
+
+@media (max-width: 300px) {
+ .card-login {
+ min-width: 238px;
+ max-width: 278px;
+ padding: 16px 10px;
+ box-shadow: none;
+ }
+ .social-button {
+ padding: 16px 10px;
+ }
+}
+
+@media (max-width: 279px) {
+ .login-pf-page {
+ overflow-y: auto;
+ overflow-x: auto;
+ }
+}
\ No newline at end of file
diff --git a/juice/login/resources/fonts/Roboto-Medium.ttf b/juice/login/resources/fonts/Roboto-Medium.ttf
new file mode 100644
index 0000000..f714a51
Binary files /dev/null and b/juice/login/resources/fonts/Roboto-Medium.ttf differ
diff --git a/juice/login/resources/fonts/Swiss721MediumBT.ttf b/juice/login/resources/fonts/Swiss721MediumBT.ttf
new file mode 100644
index 0000000..31dfb5c
Binary files /dev/null and b/juice/login/resources/fonts/Swiss721MediumBT.ttf differ
diff --git a/juice/login/resources/fonts/Swiss721ThinBT.ttf b/juice/login/resources/fonts/Swiss721ThinBT.ttf
new file mode 100644
index 0000000..ebc5cf5
Binary files /dev/null and b/juice/login/resources/fonts/Swiss721ThinBT.ttf differ
diff --git a/juice/login/resources/img/apple.svg b/juice/login/resources/img/apple.svg
new file mode 100644
index 0000000..3376f9f
--- /dev/null
+++ b/juice/login/resources/img/apple.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/clock.svg b/juice/login/resources/img/clock.svg
new file mode 100644
index 0000000..4abc291
--- /dev/null
+++ b/juice/login/resources/img/clock.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/deutsch.svg b/juice/login/resources/img/deutsch.svg
new file mode 100644
index 0000000..9cb0d95
--- /dev/null
+++ b/juice/login/resources/img/deutsch.svg
@@ -0,0 +1,9 @@
+
+
+
+ Flag of Germany
+
+
+
+
diff --git a/juice/login/resources/img/english.svg b/juice/login/resources/img/english.svg
new file mode 100644
index 0000000..75a198d
--- /dev/null
+++ b/juice/login/resources/img/english.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/juice/login/resources/img/error.svg b/juice/login/resources/img/error.svg
new file mode 100644
index 0000000..1970e12
--- /dev/null
+++ b/juice/login/resources/img/error.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/facebook.svg b/juice/login/resources/img/facebook.svg
new file mode 100644
index 0000000..f4ba330
--- /dev/null
+++ b/juice/login/resources/img/facebook.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/google.svg b/juice/login/resources/img/google.svg
new file mode 100644
index 0000000..8649a02
--- /dev/null
+++ b/juice/login/resources/img/google.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/juice/login/resources/img/juice-bc copy.svg b/juice/login/resources/img/juice-bc copy.svg
new file mode 100644
index 0000000..3c65812
--- /dev/null
+++ b/juice/login/resources/img/juice-bc copy.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/juice/login/resources/img/juice-bc.svg b/juice/login/resources/img/juice-bc.svg
new file mode 100644
index 0000000..3c65812
--- /dev/null
+++ b/juice/login/resources/img/juice-bc.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/juice/login/resources/img/juice.svg b/juice/login/resources/img/juice.svg
new file mode 100644
index 0000000..ee1ec0d
--- /dev/null
+++ b/juice/login/resources/img/juice.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/juice/login/resources/img/linkedin.svg b/juice/login/resources/img/linkedin.svg
new file mode 100644
index 0000000..c87b46e
--- /dev/null
+++ b/juice/login/resources/img/linkedin.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/locale.svg b/juice/login/resources/img/locale.svg
new file mode 100644
index 0000000..79484b7
--- /dev/null
+++ b/juice/login/resources/img/locale.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/twitter.svg b/juice/login/resources/img/twitter.svg
new file mode 100644
index 0000000..3727d76
--- /dev/null
+++ b/juice/login/resources/img/twitter.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/img/verify-email.svg b/juice/login/resources/img/verify-email.svg
new file mode 100644
index 0000000..0ff2735
--- /dev/null
+++ b/juice/login/resources/img/verify-email.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/login/resources/js/script.js b/juice/login/resources/js/script.js
new file mode 100644
index 0000000..1c235cf
--- /dev/null
+++ b/juice/login/resources/js/script.js
@@ -0,0 +1,5 @@
+
+function hideSnackbar() {
+ var x = document.getElementById("snackbar");
+ document.getElementById('snackbar').style.display = 'none';
+}
\ No newline at end of file
diff --git a/juice/login/template.ftl b/juice/login/template.ftl
new file mode 100644
index 0000000..904136a
--- /dev/null
+++ b/juice/login/template.ftl
@@ -0,0 +1,134 @@
+<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false showAnotherWayIfPresent=true>
+
+
+
+
+
+
+
+
+ <#if properties.meta?has_content>
+ <#list properties.meta?split(' ') as meta>
+
+ #list>
+ #if>
+
${msg("loginTitle",(realm.displayName!''))}
+
+ <#if properties.stylesCommon?has_content>
+ <#list properties.stylesCommon?split(' ') as style>
+
+ #list>
+ #if>
+ <#if properties.styles?has_content>
+ <#list properties.styles?split(' ') as style>
+
+ #list>
+ #if>
+ <#if properties.scripts?has_content>
+ <#list properties.scripts?split(' ') as script>
+
+ #list>
+ #if>
+ <#if scripts??>
+ <#list scripts as script>
+
+ #list>
+ #if>
+
+
+
+
+ <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
+
+
+
${kcSanitize(message.summary)?no_esc}
+
OK
+
+ #if>
+
+
+#macro>
diff --git a/juice/login/theme.properties b/juice/login/theme.properties
new file mode 100644
index 0000000..68bb1a9
--- /dev/null
+++ b/juice/login/theme.properties
@@ -0,0 +1,8 @@
+parent=keycloak
+import=common/keycloak
+
+styles=css/login.css css/styles.css
+
+scripts=js/script.js
+
+kcFormCardClass=card-login
\ No newline at end of file
diff --git a/juice/welcome/index.ftl b/juice/welcome/index.ftl
new file mode 100644
index 0000000..2999b2e
--- /dev/null
+++ b/juice/welcome/index.ftl
@@ -0,0 +1,68 @@
+
+
+
+
+
Welcome to ${productNameFull}
+
+
+
+
+
+
+
+
+ <#if properties.stylesCommon?has_content>
+ <#list properties.stylesCommon?split(' ') as style>
+
+ #list>
+ #if>
+ <#if properties.styles?has_content>
+ <#list properties.styles?split(' ') as style>
+
+ #list>
+ #if>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
By signing up you agree to the Terms of Service and Privacy Policy
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/juice/welcome/messages/messages_en.properties b/juice/welcome/messages/messages_en.properties
new file mode 100644
index 0000000..c5e2d19
--- /dev/null
+++ b/juice/welcome/messages/messages_en.properties
@@ -0,0 +1,20 @@
+loginAccountTitle=Sign in
+
+doRegister=Sign up
+doForgotPassword=Forgot password?
+noAccount=Not a member?
+
+emailForgotTitle=Reset
+emailForgotTitle1=your password
+emailInstruction=Please enter your email address.
+emailInstruction1=We will send you an email to reset your password.
+doSubmit=Send email
+backToLogin=Go back
+haveAccount=Have an account?
+
+registerTitle=Join
+registerTitle1=The Community
+
+emailVerifyTitle=Verify your email
+emailVerifyInstruction1=You need to verify your email address to activate your account. An email with instructions to verify your email address has been sent to you
+emailVerifyInstruction3=Re-send the email
\ No newline at end of file
diff --git a/juice/welcome/messages/messages_ru.properties b/juice/welcome/messages/messages_ru.properties
new file mode 100644
index 0000000..c5e2d19
--- /dev/null
+++ b/juice/welcome/messages/messages_ru.properties
@@ -0,0 +1,20 @@
+loginAccountTitle=Sign in
+
+doRegister=Sign up
+doForgotPassword=Forgot password?
+noAccount=Not a member?
+
+emailForgotTitle=Reset
+emailForgotTitle1=your password
+emailInstruction=Please enter your email address.
+emailInstruction1=We will send you an email to reset your password.
+doSubmit=Send email
+backToLogin=Go back
+haveAccount=Have an account?
+
+registerTitle=Join
+registerTitle1=The Community
+
+emailVerifyTitle=Verify your email
+emailVerifyInstruction1=You need to verify your email address to activate your account. An email with instructions to verify your email address has been sent to you
+emailVerifyInstruction3=Re-send the email
\ No newline at end of file
diff --git a/juice/welcome/resources/apple.svg b/juice/welcome/resources/apple.svg
new file mode 100644
index 0000000..fe11cc5
--- /dev/null
+++ b/juice/welcome/resources/apple.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/welcome/resources/css/styles.css b/juice/welcome/resources/css/styles.css
new file mode 100644
index 0000000..d32ffef
--- /dev/null
+++ b/juice/welcome/resources/css/styles.css
@@ -0,0 +1,330 @@
+.login-pf {
+ height: 100%;
+ background: none;
+}
+
+body {
+ background: url(../juice-bc.svg);
+ background-repeat: no-repeat;
+ background-color: #202020;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+body::before {
+ position: absolute;
+ width: 56px;
+ height: 56px;
+ content: "";
+ top: 56px;
+ left: 63px;
+ background: url(../juice.svg);
+ background-repeat: no-repeat;
+}
+
+.login-pf-page {
+ padding-top: 0px;
+}
+
+.h1, .h2, .h3, h1, h2, h3 {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+#kc-header-wrapper {
+ padding: 0px 10px;
+}
+
+.card-login {
+ margin: 0 auto;
+ box-shadow: -8px -8px 32px rgba(101, 101, 101, 0.12), -2px -2px 16px rgba(101, 101, 101, 0.24), 4px 4px 16px rgba(0, 0, 0, 0.3), 8px 8px 32px rgba(0, 0, 0, 0.1);
+ padding: 56px;
+ min-width: 440px;
+ max-width: 440px;
+ border-radius: 16px;
+ background-color: #212121;
+}
+
+.group-form {
+ padding: 32px 0px;
+}
+
+.login-sign-button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 56px;
+ margin-bottom: 24px;
+ background: #212121;
+ box-shadow: -2px -2px 4px rgba(101, 101, 101, 0.12), -1px -1px 3px rgba(101, 101, 101, 0.24), 1px 1px 2px rgba(0, 0, 0, 0.3), 2px 2px 4px rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 24px;
+ text-align: center;
+ color: #BDBFBF;
+ cursor: pointer;
+}
+.login-sign-button:hover,
+.login-sign-button:focus {
+ background: #292929;
+}
+
+.login-sign-image {
+ background: url(../apple.svg);
+ width: 24px;
+ height: 24px;
+ margin-right: 16px;
+}
+
+.login-sign-image.apple {
+ background: url(../apple.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.google {
+ background: url(../google.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.facebook {
+ background: url(../facebook.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.linkedin {
+ background: url(../linkedin.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-sign-image.twitter {
+ background: url(../twitter.svg);
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+.login-social-sign-container {
+ display: flex;
+ justify-content: space-between;
+}
+
+.login-social-sign-button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 156px;
+ height: 56px;
+ background: #212121;
+ border: 1px solid #292929;
+ box-sizing: border-box;
+ border-radius: 4px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 24px;
+ text-align: center;
+ color: #BDBFBF;
+ cursor: pointer;
+}
+.login-social-sign-button:hover,
+.login-social-sign-button:focus {
+ background: #292929;
+}
+.large-social-sign-button:hover {
+ color: #BDBFBF;
+ text-decoration: none;
+}
+
+.login-sign-link {
+ padding-left: 3px;
+ font-weight: 500;
+}
+
+.separate-text {
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 24px;
+ display: flex;
+ align-items: center;
+ text-align: center;
+ color: #98989E;
+}
+
+.separate-text::before {
+ width: 147px;
+ height: 1px;
+ margin-right: 8px;
+ content: "";
+ background-color: #333333;
+}
+.separate-text::after {
+ width: 147px;
+ height: 1px;
+ margin-left: 8px;
+ content: "";
+ background-color: #333333;
+}
+
+.large-social-sign-button {
+ width: 328px;
+}
+
+.signup-terms-container {
+ display: flex;
+ justify-content: center;
+}
+
+.login-pf-page-header {
+ background: url(../img/juice.svg);
+ background-position-x: 63px;
+ background-position-y: 56px;
+ background-repeat: no-repeat;
+}
+.header-login-welcome {
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #98989E;
+}
+.header-login-title {
+ padding-top: 4px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 38px;
+ line-height: 48px;
+ letter-spacing: -0.75px;
+ background: linear-gradient(180deg, #FF9900 0%, #FF7100 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+#kc-page-title{
+ text-align: left;
+}
+
+#kc-form-buttons {
+ margin-top: 32px;
+ margin-bottom: 32px;
+}
+
+.pf-c-button {
+ height: 56px;
+ background: #181818;
+ box-shadow: -4px -4px 8px rgba(101, 101, 101, 0.08), -1px -1px 3px rgba(101, 101, 101, 0.12), 1px 1px 4px rgba(0, 0, 0, 0.3), 4px 4px 8px rgba(0, 0, 0, 0.4);
+ border-radius: 4px;
+ font-family: Swis721 Md BT;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 24px;
+ text-align: center;
+ color: #BDBFBF;
+}
+
+.pf-c-form-control{
+ position: relative;
+ min-height: 56px;
+ border: 1px solid #333333;
+ box-sizing: border-box;
+ border-radius: 4px;
+ background: #212121;
+ font-family: Swis721 Th BT;
+ padding-left: 16px;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 24px;
+ color: #757575;
+}
+
+.form-group {
+ position: relative;
+ margin-bottom: 24px;
+}
+
+#input-error {
+ margin-top: 8px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 20px;
+ color: #BDBFBF;
+}
+
+
+.pf-c-form-control:focus, .pf-c-form-control:hover{
+ outline: none;
+ border: 1px solid rgba(255, 113, 0, 1);
+ caret-color: rgba(255, 113, 0, 1);
+ /* font-family: Swis721 Th BT; */
+}
+.pf-c-form-control[aria-invalid="true"] {
+ border: 1px solid rgba(245, 46, 46, 1);
+ background: #212121;
+}
+.login-form-input-label {
+ position: absolute;
+ display: none;
+ padding: 0px 4px;
+ top: -10px;
+ left: 12px;
+ color:rgba(255, 113, 0, 1);
+ z-index: 100;
+ background-color: #212121;
+}
+.pf-c-form-control:focus ~ .login-form-input-label,
+.pf-c-form-control:hover ~ .login-form-input-label,
+.pf-c-form-control[aria-invalid="true"] ~ .login-form-input-label {
+ display: block;
+}
+
+.pf-c-form-control[aria-invalid="true"] ~ .login-form-input-label {
+ color: rgba(245, 46, 46, 1);
+}
+
+.juice-footer {
+ position: absolute;
+ right: 56px;
+ bottom: 56px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 12px;
+ line-height: 16px;
+ color: #BDBFBF;
+}
+
+.juice-footer span {
+ font-family: Swis721 Md BT;
+ font-style: normal;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 20px;
+ color: #BDBFBF;
+}
+
+.signup-terms {
+ width: 290px;
+ font-family: Swis721 Th BT;
+ font-style: normal;
+ font-weight: 300;
+ font-size: 12px;
+ line-height: 16px;
+ text-align: center;
+ color: #BDBFBF;
+}
diff --git a/juice/welcome/resources/facebook.svg b/juice/welcome/resources/facebook.svg
new file mode 100644
index 0000000..0c95dd9
--- /dev/null
+++ b/juice/welcome/resources/facebook.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/welcome/resources/google.svg b/juice/welcome/resources/google.svg
new file mode 100644
index 0000000..c3ac925
--- /dev/null
+++ b/juice/welcome/resources/google.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/juice/welcome/resources/js/realm.js b/juice/welcome/resources/js/realm.js
new file mode 100644
index 0000000..fad9d27
--- /dev/null
+++ b/juice/welcome/resources/js/realm.js
@@ -0,0 +1,3219 @@
+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: currentRealm, 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('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, realm, roles, Notifications, ClientRole, Client) {
+
+ console.log('RealmDefaultRolesCtrl');
+
+ $scope.realm = realm;
+
+ $scope.availableRealmRoles = [];
+ $scope.selectedRealmRoles = [];
+ $scope.selectedRealmDefRoles = [];
+
+ $scope.availableClientRoles = [];
+ $scope.selectedClientRoles = [];
+ $scope.selectedClientDefRoles = [];
+
+ if (!$scope.realm.hasOwnProperty('defaultRoles') || $scope.realm.defaultRoles === null) {
+ $scope.realm.defaultRoles = [];
+ }
+
+ // Populate available roles. Available roles are neither already assigned
+ for (var i = 0; i < roles.length; i++) {
+ var item = roles[i].name;
+
+ if ($scope.realm.defaultRoles.indexOf(item) < 0) {
+ $scope.availableRealmRoles.push(item);
+ }
+ }
+
+ $scope.addRealmDefaultRole = 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.selectedRealmRoles.length; i++) {
+ var selectedRole = $scope.selectedRealmRoles[i];
+
+ $scope.realm.defaultRoles.push(selectedRole);
+
+ var index = $scope.availableRealmRoles.indexOf(selectedRole);
+ if (index > -1) {
+ $scope.availableRealmRoles.splice(index, 1);
+ }
+ }
+
+ $scope.selectedRealmRoles = [];
+
+ // Update/save the realm with new default roles.
+ Realm.update($scope.realm, function () {
+ Notifications.success("Realm default roles updated.");
+ });
+ };
+
+ $scope.deleteRealmDefaultRole = 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.selectedRealmDefRoles.length; i++) {
+ $scope.availableRealmRoles.push($scope.selectedRealmDefRoles[i]);
+
+ var index = $scope.realm.defaultRoles.indexOf($scope.selectedRealmDefRoles[i]);
+ if (index > -1) {
+ $scope.realm.defaultRoles.splice(index, 1);
+ }
+ }
+
+ $scope.selectedRealmDefRoles = [];
+
+ // Update/save the realm with new default roles.
+ //var realmCopy = angular.copy($scope.realm);
+ Realm.update($scope.realm, function () {
+ Notifications.success("Realm default roles updated.");
+ });
+ };
+
+ $scope.changeClient = function (client) {
+ $scope.selectedClient = client;
+ $scope.selectedClientRoles = [];
+ $scope.selectedClientDefRoles = [];
+ if (!client || !client.id) {
+ $scope.selectedClient = null;
+ return;
+ }
+
+ // Populate available roles for selected client
+ if ($scope.selectedClient) {
+ ClientRole.query({realm: $scope.realm.realm, client: $scope.selectedClient.id}, function (appDefaultRoles) {
+ if (!$scope.selectedClient.hasOwnProperty('defaultRoles') || $scope.selectedClient.defaultRoles === null) {
+ $scope.selectedClient.defaultRoles = [];
+ }
+
+ $scope.availableClientRoles = [];
+ console.log('default roles', appDefaultRoles);
+ for (var i = 0; i < appDefaultRoles.length; i++) {
+
+ var roleName = appDefaultRoles[i].name;
+ if ($scope.selectedClient.defaultRoles.indexOf(roleName) < 0) {
+ $scope.availableClientRoles.push(roleName);
+ }
+ }
+ });
+ } else {
+ $scope.availableClientRoles = null;
+ }
+ };
+
+ $scope.addClientDefaultRole = 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.selectedClientRoles.length; i++) {
+ var role = $scope.selectedClientRoles[i];
+
+ var idx = $scope.selectedClient.defaultRoles.indexOf(role);
+ if (idx < 0) {
+ $scope.selectedClient.defaultRoles.push(role);
+ }
+
+ idx = $scope.availableClientRoles.indexOf(role);
+
+ if (idx != -1) {
+ $scope.availableClientRoles.splice(idx, 1);
+ }
+ }
+
+ $scope.selectedClientRoles = [];
+
+ // Update/save the selected client with new default roles.
+ delete $scope.selectedClient.text;
+ Client.update({
+ realm: $scope.realm.realm,
+ client: $scope.selectedClient.id
+ }, $scope.selectedClient, function () {
+ Notifications.success("Your changes have been saved to the client.");
+ Client.get({realm: realm.realm, client: $scope.selectedClient.id}, function(response) {
+ response.text = response.clientId;
+ $scope.changeClient(response);
+ });
+ });
+ };
+
+ $scope.rmClientDefaultRole = function () {
+
+ // Remove selected roles from the app default roles and add them to app available roles (move from right to left).
+ for (var i = 0; i < $scope.selectedClientDefRoles.length; i++) {
+ var role = $scope.selectedClientDefRoles[i];
+ var idx = $scope.selectedClient.defaultRoles.indexOf(role);
+ if (idx != -1) {
+ $scope.selectedClient.defaultRoles.splice(idx, 1);
+ }
+ idx = $scope.availableClientRoles.indexOf(role);
+ if (idx < 0) {
+ $scope.availableClientRoles.push(role);
+ }
+ }
+
+ $scope.selectedClientDefRoles = [];
+
+ // Update/save the selected client with new default roles.
+ delete $scope.selectedClient.text;
+ Client.update({
+ realm: $scope.realm.realm,
+ client: $scope.selectedClient.id
+ }, $scope.selectedClient, function () {
+ Notifications.success("Your changes have been saved to the client.");
+ Client.get({realm: realm.realm, client: $scope.selectedClient.id}, function(response) {
+ response.text = response.clientId;
+ $scope.changeClient(response);
+ });
+ });
+ };
+
+ 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:transient",
+ name: "Transient"
+ },
+ */
+ {
+ format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
+ name: "Persistent"
+
+ },
+ {
+ 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.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.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();
+
+ 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.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.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('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/juice/welcome/resources/juice-bc.svg b/juice/welcome/resources/juice-bc.svg
new file mode 100644
index 0000000..3c65812
--- /dev/null
+++ b/juice/welcome/resources/juice-bc.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/juice/welcome/resources/juice.svg b/juice/welcome/resources/juice.svg
new file mode 100644
index 0000000..ee1ec0d
--- /dev/null
+++ b/juice/welcome/resources/juice.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/juice/welcome/resources/linkedin.svg b/juice/welcome/resources/linkedin.svg
new file mode 100644
index 0000000..e74895b
--- /dev/null
+++ b/juice/welcome/resources/linkedin.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/welcome/resources/twitter.svg b/juice/welcome/resources/twitter.svg
new file mode 100644
index 0000000..a6b0441
--- /dev/null
+++ b/juice/welcome/resources/twitter.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/juice/welcome/theme.properties b/juice/welcome/theme.properties
new file mode 100644
index 0000000..805c36a
--- /dev/null
+++ b/juice/welcome/theme.properties
@@ -0,0 +1,11 @@
+styles=css/welcome.css css/styles.css
+import=common/keycloak
+
+stylesCommon=node_modules/patternfly/dist/css/patternfly.css node_modules/patternfly/dist/css/patternfly-additions.css
+scripts=realm.js
+signInUrl=/auth/realms/demo/account
+displayCommunityLinks=true
+
+welcomeTitle=Welcome
+
+locales=ca,cs,da,de,en,es,fr,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN
diff --git a/restart.sh b/restart.sh
new file mode 100755
index 0000000..46e17f5
--- /dev/null
+++ b/restart.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+docker build -t juice-keycloak .
+
+docker stop juice-keycloak || true
+
+docker run --rm -it -d \
+ -p 8080:8080 \
+ -e KEYCLOAK_USER=admin \
+ -e KEYCLOAK_PASSWORD=admin \
+ -e DB_VENDOR=postgres \
+ -e DB_ADDR=host.docker.internal \
+ -e DB_DATABASE=keycloak \
+ -e DB_USER=keycloak \
+ -e DB_PASSWORD=keycloak \
+ --name juice-keycloak \
+ juice-keycloak
\ No newline at end of file
diff --git a/test/url.test.js b/test/url.test.js
new file mode 100644
index 0000000..17d1a01
--- /dev/null
+++ b/test/url.test.js
@@ -0,0 +1,110 @@
+// installable packages
+const base64url = require("base64url");
+const { v4: uuidv4 } = require("uuid");
+const fetch = require("node-fetch").default;
+
+// native packages
+const crypto = require("crypto");
+const { URL, URLSearchParams } = require("url");
+
+// config
+const BASE_URL = "http://localhost:8080";
+const REALM = "demorealm";
+const REDIRECT_URI = "com.juice.booster3://auth";
+const CLIENT_ID = "native-webview";
+
+const sha256encrypt = (code) => {
+ const base64Digest = crypto
+ .createHash("sha256")
+ .update(code)
+ .digest("base64");
+
+ return base64url.fromBase64(base64Digest);
+};
+
+const nonceFactory = () => {
+ const timestamp = Date.now();
+ return sha256encrypt(timestamp.toString());
+};
+
+function generateAuthUrl() {
+ const nonce = uuidv4();
+ const state = uuidv4();
+
+ const url = new URL(
+ `${BASE_URL}/auth/realms/${REALM}/protocol/openid-connect/auth`,
+ );
+ url.searchParams.append("client_id", CLIENT_ID);
+ url.searchParams.append("redirect_uri", REDIRECT_URI);
+ url.searchParams.append("state", state);
+ url.searchParams.append("response_mode", "fragment");
+ url.searchParams.append("response_type", "code");
+ url.searchParams.append("scope", "openid");
+ url.searchParams.append("nonce", nonce);
+
+ console.log(url.href);
+}
+
+async function getTokenByUrl(urlString) {
+ let url = new URL(urlString);
+
+ let code = new URLSearchParams(url.hash).get("code");
+ console.log(`Auth with code is: ${code}`);
+
+ url = new URL(
+ `${BASE_URL}/auth/realms/${REALM}/protocol/openid-connect/token`,
+ );
+ const payload = {
+ client_id: CLIENT_ID,
+ code,
+ grant_type: "authorization_code",
+ redirect_uri: REDIRECT_URI,
+ };
+
+ let response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Accept: "*/*",
+ },
+ redirect: "manual",
+ body: new URLSearchParams(payload).toString(),
+ });
+
+ const token = await response.json();
+ console.log(token);
+}
+
+async function refresh(refresh_token) {
+ const url = new URL(
+ `${BASE_URL}/auth/realms/${REALM}/protocol/openid-connect/token`,
+ );
+ const payload = {
+ client_id: CLIENT_ID,
+ refresh_token,
+ grant_type: "refresh_token",
+ redirect_uri: REDIRECT_URI,
+ };
+
+ let response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Accept: "*/*",
+ },
+ redirect: "manual",
+ body: new URLSearchParams(payload).toString(),
+ });
+
+ const token = await response.json();
+ console.log(token);
+}
+
+if (require.main === module) {
+ const redirectUri = process.argv[2];
+ if (redirectUri == null) {
+ generateAuthUrl();
+ } else {
+ getTokenByUrl(redirectUri);
+ }
+}
diff --git a/version.ini b/version.ini
new file mode 100644
index 0000000..6c6aa7c
--- /dev/null
+++ b/version.ini
@@ -0,0 +1 @@
+0.1.0
\ No newline at end of file