From b1ebf237deb31b4154010fde24aa2a0d4a8ded73 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 5 Feb 2014 17:28:48 +0000 Subject: [PATCH] KEYCLOAK-10 Added JS library --- .../login/sunrise/resources/css/styles.css | 7 +- .../org/keycloak/freemarker/ThemeLoader.java | 2 +- .../theme/DefaultLoginThemeProvider.java | 4 - integration/js/pom.xml | 31 ++ .../META-INF/resources/js/keycloak.js | 249 ++++++++++++++ integration/pom.xml | 1 + misc/logo/login-bg.svg | 318 ++++++++++++++++++ server/pom.xml | 5 + testsuite/integration/pom.xml | 5 + .../keycloak/testutils/KeycloakServer.java | 6 +- 10 files changed, 617 insertions(+), 11 deletions(-) create mode 100755 integration/js/pom.xml create mode 100644 integration/js/src/main/resources/META-INF/resources/js/keycloak.js create mode 100644 misc/logo/login-bg.svg diff --git a/examples/themes/login/sunrise/resources/css/styles.css b/examples/themes/login/sunrise/resources/css/styles.css index bf9d004bd5..f7d01e8117 100644 --- a/examples/themes/login/sunrise/resources/css/styles.css +++ b/examples/themes/login/sunrise/resources/css/styles.css @@ -17,13 +17,13 @@ a { .content { position: absolute; - top: 25%; + top: 30%; left: 50%; width: 550px; margin-left: -225px; } -h2 { +h2#kc-header { position: fixed; top: 50px; left: 0; @@ -149,7 +149,7 @@ div.feedback p { padding: 1em; } -div.rcue-logo { +h1.kc-title { background-image: url('../img/logo.png'); background-repeat: no-repeat; height: 500px; @@ -158,6 +158,7 @@ div.rcue-logo { top: 30px; width: 500px; z-index: -1; + text-indent: -9999px; } div.social-login span { diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java index f62b4f9ebe..ce9f34a11e 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ThemeLoader.java @@ -17,8 +17,8 @@ import java.util.Properties; public class ThemeLoader { private static final Logger logger = Logger.getLogger(ThemeLoader.class); + private static String DEFAULT = "keycloak"; public static final String BASE = "base"; - public static String DEFAULT = BASE; public static Theme createTheme(String name, Theme.Type type) throws FreeMarkerException { if (name == null) { diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java b/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java index 7d98d3d90c..fbdb7684f8 100644 --- a/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/DefaultLoginThemeProvider.java @@ -17,10 +17,6 @@ public class DefaultLoginThemeProvider implements ThemeProvider { public static final String RCUE = "rcue"; public static final String KEYCLOAK = "keycloak"; - static { - ThemeLoader.DEFAULT = KEYCLOAK; - } - private static Set defaultThemes = new HashSet(); static { diff --git a/integration/js/pom.xml b/integration/js/pom.xml new file mode 100755 index 0000000000..4eba178b15 --- /dev/null +++ b/integration/js/pom.xml @@ -0,0 +1,31 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-2-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-js-adapter + Keycloak JS Integration + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + diff --git a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js new file mode 100644 index 0000000000..47a69fd8bf --- /dev/null +++ b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js @@ -0,0 +1,249 @@ +var Keycloak = function (options) { + options = options || {}; + + if (!(this instanceof Keycloak)) { + return new Keycloak(options); + } + + var instance = this; + + if (!options.url) { + var scripts = document.getElementsByTagName('script'); + for (var i = 0; i < scripts.length; i++) { + if (scripts[i].src.match(/.*keycloak\.js/)) { + options.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/auth/js/keycloak.js')); + break; + } + } + } + + if (!options.url) { + throw 'url missing'; + } + + if (!options.realm) { + throw 'realm missing'; + } + + if (!options.clientId) { + throw 'clientId missing'; + } + + if (!options.clientSecret) { + throw 'clientSecret missing'; + } + + this.init = function (successCallback, errorCallback) { + if (window.oauth.callback) { + delete sessionStorage.oauthToken; + processCallback(successCallback, errorCallback); + } else if (options.token) { + setToken(options.token, successCallback); + } else if (sessionStorage.oauthToken) { + setToken(sessionStorage.oauthToken, successCallback); + } else if (options.onload) { + switch (options.onload) { + case 'login-required' : + window.location = createLoginUrl(true); + break; + case 'check-sso' : + window.location = createLoginUrl(false); + break; + } + } + } + + this.login = function () { + window.location.href = createLoginUrl(true); + } + + this.logout = function () { + setToken(undefined); + window.location.href = createLogoutUrl(); + } + + this.hasRealmRole = function (role) { + var access = this.realmAccess; + return access && access.roles.indexOf(role) >= 0 || false; + } + + this.hasResourceRole = function (role, resource) { + if (!this.resourceAccess) { + return false; + } + + var access = this.resourceAccess[resource || options.clientId]; + return access && access.roles.indexOf(role) >= 0 || false; + } + + this.loadUserProfile = function (success, error) { + var url = getRealmUrl() + '/account'; + var req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Accept', 'application/json'); + req.setRequestHeader('Authorization', 'bearer ' + this.token); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + instance.profile = JSON.parse(req.responseText); + success && success(instance.profile) + } else { + var response = { status: req.status, statusText: req.status }; + if (req.responseText) { + response.data = JSON.parse(req.responseText); + } + error && error(response); + } + } + } + + req.send(); + } + + function getRealmUrl() { + return options.url + '/auth/rest/realms/' + encodeURIComponent(options.realm); + } + + function processCallback(successCallback, errorCallback) { + var code = window.oauth.code; + var error = window.oauth.error; + var prompt = window.oauth.prompt; + + if (code) { + var params = 'code=' + code + '&client_id=' + encodeURIComponent(options.clientId) + '&password=' + encodeURIComponent(options.clientSecret); + var url = getRealmUrl() + '/tokens/access/codes'; + + var req = new XMLHttpRequest(); + req.open('POST', url, true); + req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + setToken(JSON.parse(req.responseText)['access_token'], successCallback); + } else { + errorCallback && errorCallback({ authenticated: false, status: req.status, statusText: req.statusText }); + } + } + }; + + req.send(params); + } else if (error) { + if (prompt != 'none') { + setTimeout(function() { + errorCallback && errorCallback({ authenticated: false, error: error }) + }, 0); + } + } + } + + function setToken(token, successCallback) { + if (token) { + sessionStorage.oauthToken = token; + window.oauth.token = token; + instance.token = token; + + instance.tokenParsed = JSON.parse(atob(token.split('.')[1])); + instance.authenticated = true; + instance.username = instance.tokenParsed.sub; + instance.realmAccess = instance.tokenParsed.realm_access; + instance.resourceAccess = instance.tokenParsed.resource_access; + + setTimeout(function() { + successCallback && successCallback({ authenticated: instance.authenticated, username: instance.username }); + }, 0); + } else { + delete sessionStorage.oauthToken; + delete window.oauth.token; + delete instance.token; + } + } + + function createLoginUrl(prompt) { + var state = createUUID(); + + sessionStorage.oauthState = state; + var url = getRealmUrl() + + '/tokens/login' + + '?client_id=' + encodeURIComponent(options.clientId) + + '&redirect_uri=' + getEncodedRedirectUri() + + '&state=' + encodeURIComponent(state) + + '&response_type=code'; + + if (prompt == false) { + url += '&prompt=none'; + } + + return url; + } + + function createLogoutUrl() { + var url = getRealmUrl() + + '/tokens/logout' + + '?redirect_uri=' + getEncodedRedirectUri(); + return url; + } + + function getEncodedRedirectUri() { + var url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname); + if (location.hash) { + url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); + } + return encodeURI(url); + } + + function createUUID() { + var s = []; + var hexDigits = '0123456789abcdef'; + for (var i = 0; i < 36; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + s[14] = '4'; + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = '-'; + var uuid = s.join(''); + return uuid; + } +} + +window.oauth = (function () { + var oauth = {}; + + var params = window.location.search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + switch (decodeURIComponent(p[0])) { + case 'code': + oauth.code = p[1]; + break; + case 'error': + oauth.error = p[1]; + break; + case 'state': + oauth.state = decodeURIComponent(p[1]); + break; + case 'redirect_fragment': + oauth.fragment = decodeURIComponent(p[1]); + break; + case 'prompt': + oauth.prompt = p[1]; + break; + } + } + + if (oauth.state && oauth.state == sessionStorage.oauthState) { + oauth.callback = true; + delete sessionStorage.oauthState; + } else { + oauth.callback = false; + } + + if (oauth.callback) { + window.history.replaceState({}, null, location.protocol + '//' + location.host + location.pathname + (oauth.fragment ? '#' + oauth.fragment : '')); + } else if (oauth.fragment) { + window.history.replaceState({}, null, location.protocol + '//' + location.host + location.pathname + (oauth.fragment ? '#' + oauth.fragment : '')); + } + + return oauth; +}()); \ No newline at end of file diff --git a/integration/pom.xml b/integration/pom.xml index 5eabc68227..b0e25d9fdc 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -21,6 +21,7 @@ as7-eap6/adapter undertow wildfly-subsystem + js diff --git a/misc/logo/login-bg.svg b/misc/logo/login-bg.svg new file mode 100644 index 0000000000..f710b1b8c7 --- /dev/null +++ b/misc/logo/login-bg.svg @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/pom.xml b/server/pom.xml index 5d7dbfe2ea..bd3d7ee5c5 100755 --- a/server/pom.xml +++ b/server/pom.xml @@ -113,6 +113,11 @@ keycloak-admin-ui-styles ${project.version} + + org.keycloak + keycloak-js-adapter + ${project.version} + junit junit diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index acd2316d48..115dc5df85 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -61,6 +61,11 @@ keycloak-model-jpa ${project.version} + + org.keycloak + keycloak-js-adapter + ${project.version} +