Merge pull request #299 from stianst/master
Added support for Cordova to keycloak.js
This commit is contained in:
commit
2403e1f465
22 changed files with 537 additions and 184 deletions
4
examples/cordova/.gitignore
vendored
Normal file
4
examples/cordova/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
platforms
|
||||
plugins
|
||||
www/keycloak.js
|
||||
www/keycloak.json
|
31
examples/cordova/README.md
Normal file
31
examples/cordova/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
Basic Cordova Example
|
||||
=====================
|
||||
|
||||
Before running this example you need to have Cordova installed with a phone or emulator available.
|
||||
|
||||
Start and configure Keycloak
|
||||
----------------------------
|
||||
|
||||
Start Keycloak bound to an IP address available to the phone or emulator. For example:
|
||||
|
||||
bin/standalone.sh -b 192.168.0.10
|
||||
|
||||
Open the Keycloak admin console, click on Add Realm, click on 'Choose a JSON file', selct example-realm.json and click Upload.
|
||||
|
||||
Navigate to applications, click on 'Cordova', select 'Installation' and in the 'Format option' drop-down select 'keycloak.json'. Download this file to the www folder.
|
||||
|
||||
Download '/js/keycloak.js' from the server to the www folder as well. For example:
|
||||
|
||||
wget http://192.168.0.10:8080/auth/js/keycloak.js
|
||||
|
||||
|
||||
Install to Android phone or emulator
|
||||
------------------------------------
|
||||
|
||||
mkdir platforms plugins
|
||||
cordova plugin add org.apache.cordova.inappbrowser
|
||||
cordova platform add android
|
||||
cordova run android
|
||||
|
||||
|
||||
Once the application is opened you can login with username: 'user', and password: 'password'.
|
72
examples/cordova/example-realm.json
Executable file
72
examples/cordova/example-realm.json
Executable file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"realm": "example",
|
||||
"enabled": true,
|
||||
"sslNotRequired": true,
|
||||
"registrationAllowed": true,
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [ "password" ],
|
||||
"users" : [
|
||||
{
|
||||
"username" : "user",
|
||||
"enabled": true,
|
||||
"email" : "sample-user@example",
|
||||
"firstName": "Sample",
|
||||
"lastName": "User",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name": "user",
|
||||
"description": "User privileges"
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Administrator privileges"
|
||||
}
|
||||
]
|
||||
},
|
||||
"roleMappings": [
|
||||
{
|
||||
"username": "user",
|
||||
"roles": ["user"]
|
||||
}
|
||||
],
|
||||
"scopeMappings": [
|
||||
{
|
||||
"client": "cordova",
|
||||
"roles": ["user"]
|
||||
}
|
||||
],
|
||||
"applications": [
|
||||
{
|
||||
"name": "cordova",
|
||||
"enabled": true,
|
||||
"publicClient": true,
|
||||
"redirectUris": [
|
||||
"http://localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"applicationRoleMappings": {
|
||||
"account": [
|
||||
{
|
||||
"username": "user",
|
||||
"roles": ["view-profile", "manage-account"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"applicationScopeMappings": {
|
||||
"account": [
|
||||
{
|
||||
"client": "cordova",
|
||||
"roles": ["view-profile"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
14
examples/cordova/www/config.xml
Normal file
14
examples/cordova/www/config.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:gap="http://phonegap.com/ns/1.0" id="org.keycloak.examples.cordova" version="1.0.0">
|
||||
<name>Keycloak Auth</name>
|
||||
<description>Keycloak Cordova Example</description>
|
||||
<author href="http://www.keycloak.org">Keycloak Team</author>
|
||||
|
||||
<feature name="http://api.phonegap.com/1.0/device" />
|
||||
|
||||
<preference name="permissions" value="none"/>
|
||||
|
||||
<gap:plugin name="org.apache.cordova.inappbrowser" />
|
||||
|
||||
<access origin="http://*"/>
|
||||
</widget>
|
78
examples/cordova/www/index.html
Normal file
78
examples/cordova/www/index.html
Normal file
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Authentication Example</title>
|
||||
|
||||
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="keycloak.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
var keycloak = new Keycloak();
|
||||
|
||||
keycloak.onReady = updateState;
|
||||
keycloak.onAuthSuccess = updateState;
|
||||
keycloak.onAuthRefreshSuccess = updateState;
|
||||
keycloak.onAuthLogout = updateState;
|
||||
|
||||
function updateState() {
|
||||
console.debug('Updating state');
|
||||
|
||||
if (keycloak.authenticated) {
|
||||
document.getElementById('authenticated').style.display = 'block';
|
||||
document.getElementById('not-authenticated').style.display = 'none';
|
||||
|
||||
document.getElementById('subject').innerText = keycloak.subject;
|
||||
document.getElementById('username').innerText = keycloak.idToken.preferred_username;
|
||||
document.getElementById('tokenExpires').innerText = new Date(keycloak.tokenParsed.exp * 1000).toLocaleString();
|
||||
document.getElementById('tokenRefreshExpires').innerText = new Date(keycloak.refreshTokenParsed.exp * 1000).toLocaleString();
|
||||
} else {
|
||||
document.getElementById('authenticated').style.display = 'none';
|
||||
document.getElementById('not-authenticated').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("deviceready", function() {
|
||||
console.debug('Device ready');
|
||||
|
||||
keycloak.init('check-sso');
|
||||
}, false);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="authenticated" style="display: none;">
|
||||
<div>
|
||||
<button onclick="keycloak.logout()">Log out</button>
|
||||
<button onclick="keycloak.updateToken()">Refresh token</button>
|
||||
<button onclick="keycloak.accountManagement()">Manage account</button>
|
||||
</div>
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Subject</td>
|
||||
<td id="subject"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td id="username"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Token expires</td>
|
||||
<td id="tokenExpires"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Refresh token expires</td>
|
||||
<td id="tokenRefreshExpires"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="not-authenticated" style="display: none;">
|
||||
<div>
|
||||
<button onclick="keycloak.login()">Log in</button>
|
||||
</div>
|
||||
<div>
|
||||
<p>Not authenticated</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -14,14 +14,6 @@
|
|||
<name>Customer Portal CLI</name>
|
||||
<description/>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jboss</id>
|
||||
<name>jboss repo</name>
|
||||
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
@ -32,6 +24,14 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<version>7.4.Final</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
</head>
|
||||
<body bgcolor="#E3F6CE">
|
||||
|
||||
<p>Goto: <a href="#" onclick="keycloak.logout()">logout</a></p>
|
||||
<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="#" onclick="keycloak.logout()">logout</a> | <a href="#" onclick="keycloak.accountManagement()">manage acct</a></p>
|
||||
|
||||
User <b id="subject"></b> made this request.
|
||||
<p><b>User details (from <span id="profileType"></span>)</b></p>
|
||||
<p>Username: <span id="username"></span></p>
|
||||
|
@ -18,16 +19,11 @@ User <b id="subject"></b> made this request.
|
|||
<div id="customers"></div>
|
||||
|
||||
<script>
|
||||
var keycloak = Keycloak({
|
||||
clientId: 'customer-portal-js',
|
||||
realm: 'demo',
|
||||
onload: 'login-required'
|
||||
});
|
||||
var keycloak = Keycloak('../keycloak.json');
|
||||
|
||||
var loadData = function () {
|
||||
document.getElementById('subject').innerText = keycloak.subject;
|
||||
|
||||
console.debug(keycloak.idToken);
|
||||
if (keycloak.idToken) {
|
||||
document.getElementById('profileType').innerText = 'IDToken';
|
||||
document.getElementById('username').innerText = keycloak.idToken.preferred_username;
|
||||
|
@ -74,14 +70,14 @@ User <b id="subject"></b> made this request.
|
|||
|
||||
var loadFailure = function () {
|
||||
document.getElementById('customers').innerHTML = '<b>Failed to load data. Check console log</b>';
|
||||
|
||||
};
|
||||
|
||||
var reloadData = function () {
|
||||
keycloak.onValidAccessToken(loadData, loadFailure);
|
||||
keycloak.updateToken(10).success(loadData).error(loadFailure);
|
||||
}
|
||||
|
||||
keycloak.init(loadData, loadFailure);
|
||||
keycloak.onAuthSuccess = loadData;
|
||||
keycloak.init('login-required');
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"realm" : "demo",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url" : "http://localhost:8080/auth",
|
||||
"ssl-not-required" : true,
|
||||
"resource" : "customer-portal-js",
|
||||
"public-client" : true
|
||||
}
|
|
@ -51,6 +51,10 @@
|
|||
"client": "customer-portal",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "customer-portal-js",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "product-portal",
|
||||
"roles": ["user"]
|
||||
|
|
17
examples/js-console/README.md
Normal file
17
examples/js-console/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
Basic JavaScript Example
|
||||
========================
|
||||
|
||||
Start and configure Keycloak
|
||||
----------------------------
|
||||
|
||||
Start Keycloak:
|
||||
|
||||
bin/standalone.sh
|
||||
|
||||
Open the Keycloak admin console, click on Add Realm, click on 'Choose a JSON file', selct example-realm.json and click Upload.
|
||||
|
||||
Deploy the JS Console to Keycloak by running:
|
||||
|
||||
mvn install jboss-as:deploy
|
||||
|
||||
Open the console at http://localhost:8080/js-console and login with username: 'user', and password: 'password'.
|
|
@ -1,6 +1,6 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="http://localhost:8080/auth/js/keycloak.js"></script>
|
||||
<script src="/auth/js/keycloak.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
|
||||
function refreshToken(minValidity) {
|
||||
keycloak.refreshAccessToken(minValidity).success(function(refreshed) {
|
||||
keycloak.updateToken(minValidity).success(function(refreshed) {
|
||||
if (refreshed) {
|
||||
output(keycloak.tokenParsed);
|
||||
} else {
|
||||
|
|
|
@ -25,6 +25,6 @@ public interface Account {
|
|||
|
||||
public Account setRealm(RealmModel realm);
|
||||
|
||||
public Account setReferrer(String referrer);
|
||||
public Account setReferrer(String[] referrer);
|
||||
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public class FreeMarkerAccount implements Account {
|
|||
private UserModel user;
|
||||
private Response.Status status = Response.Status.OK;
|
||||
private RealmModel realm;
|
||||
private String referrer;
|
||||
private String[] referrer;
|
||||
|
||||
public static enum MessageType {SUCCESS, WARNING, ERROR}
|
||||
|
||||
|
@ -82,9 +82,8 @@ public class FreeMarkerAccount implements Account {
|
|||
attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
|
||||
}
|
||||
|
||||
ApplicationModel referrerApp = getReferrer();
|
||||
if (referrerApp != null) {
|
||||
attributes.put("referrer", new ReferrerBean(referrerApp));
|
||||
if (referrer != null) {
|
||||
attributes.put("referrer", new ReferrerBean(referrer));
|
||||
}
|
||||
|
||||
attributes.put("url", new UrlBean(realm, theme, baseUri));
|
||||
|
@ -114,16 +113,6 @@ public class FreeMarkerAccount implements Account {
|
|||
}
|
||||
}
|
||||
|
||||
private ApplicationModel getReferrer() {
|
||||
if (referrer != null) {
|
||||
ApplicationModel app = realm.getApplicationByName(referrer);
|
||||
if (app != null) {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account setError(String message) {
|
||||
this.message = message;
|
||||
|
@ -164,7 +153,7 @@ public class FreeMarkerAccount implements Account {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Account setReferrer(String referrer) {
|
||||
public Account setReferrer(String[] referrer) {
|
||||
this.referrer = referrer;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
package org.keycloak.account.freemarker.model;
|
||||
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ReferrerBean {
|
||||
|
||||
private ApplicationModel referrer;
|
||||
private String[] referrer;
|
||||
|
||||
public ReferrerBean(ApplicationModel referrer) {
|
||||
public ReferrerBean(String[] referrer) {
|
||||
this.referrer = referrer;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return referrer.getName();
|
||||
return referrer[0];
|
||||
}
|
||||
|
||||
public String getBaseUrl() {
|
||||
return referrer.getBaseUrl();
|
||||
public String getUrl() {
|
||||
return referrer[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="navbar-collapse navbar-collapse-1">
|
||||
<div class="container">
|
||||
<ul class="nav navbar-nav navbar-utility">
|
||||
<#if referrer?has_content && referrer.baseUrl?has_content><li><a href="${referrer.baseUrl}">Back to ${referrer.name}</a></li></#if>
|
||||
<#if referrer?has_content && referrer.url?has_content><li><a href="${referrer.url}" id="referrer">Back to ${referrer.name}</a></li></#if>
|
||||
<li><a href="${url.logoutUrl}">Sign Out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -4,107 +4,118 @@ var Keycloak = function (config) {
|
|||
}
|
||||
|
||||
var kc = this;
|
||||
kc.authenticated = false;
|
||||
|
||||
var configPromise = createPromise();
|
||||
configPromise.name = 'config';
|
||||
|
||||
if (!config) {
|
||||
loadConfig('keycloak.json', configPromise);
|
||||
} else if (typeof config === 'string') {
|
||||
loadConfig(config, configPromise);
|
||||
} else {
|
||||
if (!config['url']) {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.match(/.*keycloak\.js/)) {
|
||||
config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.realm) {
|
||||
throw 'realm missing';
|
||||
}
|
||||
|
||||
if (!config.clientId) {
|
||||
throw 'clientId missing';
|
||||
}
|
||||
|
||||
kc.authServerUrl = config.url;
|
||||
kc.realm = config.realm;
|
||||
kc.clientId = config.clientId;
|
||||
|
||||
configPromise.setSuccess();
|
||||
}
|
||||
var adapter;
|
||||
|
||||
kc.init = function (init) {
|
||||
kc.authenticated = false;
|
||||
|
||||
if (window.Cordova) {
|
||||
adapter = loadAdapter('cordova');
|
||||
} else {
|
||||
adapter = loadAdapter();
|
||||
}
|
||||
|
||||
var promise = createPromise();
|
||||
var callback = parseCallback(window.location.href);
|
||||
|
||||
var initPromise = createPromise();
|
||||
initPromise.promise.success(function() {
|
||||
kc.onReady && kc.onReady(kc.authenticated);
|
||||
promise.setSuccess(kc.authenticated);
|
||||
}).error(promise.error);
|
||||
|
||||
var configPromise = loadConfig(config);
|
||||
|
||||
function processInit() {
|
||||
var callback = parseCallback(window.location.href);
|
||||
if (callback) {
|
||||
window.history.replaceState({}, null, location.protocol + '//' + location.host + location.pathname + (callback.fragment ? '#' + callback.fragment : ''));
|
||||
processCallback(callback, promise);
|
||||
processCallback(callback, initPromise);
|
||||
return;
|
||||
} else if (init) {
|
||||
if (init.code || init.error) {
|
||||
processCallback(init, promise);
|
||||
processCallback(init, initPromise);
|
||||
return;
|
||||
} else if (init.token || init.refreshToken) {
|
||||
setToken(init.token, init.refreshToken);
|
||||
initPromise.setSuccess();
|
||||
} else if (init == 'login-required') {
|
||||
kc.login();
|
||||
return;
|
||||
var p = kc.login();
|
||||
if (p) {
|
||||
p.success(function() {
|
||||
initPromise.setSuccess();
|
||||
}).error(function() {
|
||||
initPromise.setError();
|
||||
});
|
||||
};
|
||||
} else if (init == 'check-sso') {
|
||||
window.location = kc.createLoginUrl() + '&prompt=none';
|
||||
return;
|
||||
var p = kc.login({ prompt: 'none' });
|
||||
if (p) {
|
||||
p.success(function() {
|
||||
initPromise.setSuccess();
|
||||
}).error(function() {
|
||||
initPromise.setSuccess();
|
||||
});
|
||||
};
|
||||
} else {
|
||||
throw 'invalid init: ' + init;
|
||||
}
|
||||
} else {
|
||||
initPromise.setSuccess();
|
||||
}
|
||||
|
||||
promise.setSuccess(false);
|
||||
}
|
||||
|
||||
configPromise.promise.success(processInit);
|
||||
configPromise.success(processInit);
|
||||
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
kc.login = function (redirectUri) {
|
||||
window.location.href = kc.createLoginUrl(redirectUri);
|
||||
kc.login = function (options) {
|
||||
return adapter.login(options);
|
||||
}
|
||||
|
||||
kc.createLoginUrl = function(redirectUri) {
|
||||
kc.createLoginUrl = function(options) {
|
||||
var state = createUUID();
|
||||
|
||||
sessionStorage.oauthState = state;
|
||||
var url = getRealmUrl()
|
||||
+ '/tokens/login'
|
||||
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
||||
+ '&redirect_uri=' + getEncodedRedirectUri(redirectUri)
|
||||
+ '&redirect_uri=' + encodeURIComponent(adapter.redirectUri(options))
|
||||
+ '&state=' + encodeURIComponent(state)
|
||||
+ '&response_type=code';
|
||||
|
||||
if (options && options.prompt) {
|
||||
url += '&prompt=' + options.prompt;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
kc.logout = function(redirectUri) {
|
||||
setToken(null, null);
|
||||
window.location.href = kc.createLogoutUrl(redirectUri);
|
||||
kc.logout = function(options) {
|
||||
return adapter.logout(options);
|
||||
}
|
||||
|
||||
kc.clearToken = function() {
|
||||
setToken(null, null);
|
||||
}
|
||||
|
||||
kc.createLogoutUrl = function(redirectUri) {
|
||||
kc.createLogoutUrl = function(options) {
|
||||
var url = getRealmUrl()
|
||||
+ '/tokens/logout'
|
||||
+ '?redirect_uri=' + getEncodedRedirectUri(redirectUri);
|
||||
+ '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options));
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
kc.createAccountUrl = function(options) {
|
||||
var url = getRealmUrl()
|
||||
+ '/account'
|
||||
+ '?referrer=' + kc.clientId
|
||||
+ '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options));
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
kc.accountManagement = function() {
|
||||
return adapter.accountManagement();
|
||||
}
|
||||
|
||||
kc.hasRealmRole = function (role) {
|
||||
var access = kc.realmAccess;
|
||||
return access && access.roles.indexOf(role) >= 0 || false;
|
||||
|
@ -144,7 +155,20 @@ var Keycloak = function (config) {
|
|||
return promise.promise;
|
||||
}
|
||||
|
||||
kc.refreshAccessToken = function(minValidity) {
|
||||
kc.isTokenExpired = function(minValidity) {
|
||||
if (!kc.tokenParsed || !kc.refreshToken) {
|
||||
throw 'Not authenticated';
|
||||
}
|
||||
|
||||
var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000);
|
||||
if (minValidity) {
|
||||
expiresIn -= minValidity;
|
||||
}
|
||||
|
||||
return expiresIn < 0;
|
||||
}
|
||||
|
||||
kc.updateToken = function(minValidity) {
|
||||
if (!kc.tokenParsed || !kc.refreshToken) {
|
||||
throw 'Not authenticated';
|
||||
}
|
||||
|
@ -152,8 +176,7 @@ var Keycloak = function (config) {
|
|||
var promise = createPromise();
|
||||
|
||||
if (minValidity) {
|
||||
var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000);
|
||||
if (expiresIn > minValidity) {
|
||||
if (!kc.isTokenExpired(minValidity)) {
|
||||
promise.setSuccess(false);
|
||||
return promise.promise;
|
||||
}
|
||||
|
@ -191,15 +214,6 @@ var Keycloak = function (config) {
|
|||
return promise.promise;
|
||||
}
|
||||
|
||||
kc.processCallback = function(url) {
|
||||
var callback = parseCallback(url);
|
||||
if (callback) {
|
||||
var promise = createPromise();
|
||||
processCallback(callback, promise);
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
function getRealmUrl() {
|
||||
return kc.authServerUrl + '/rest/realms/' + encodeURIComponent(kc.realm);
|
||||
}
|
||||
|
@ -231,10 +245,10 @@ var Keycloak = function (config) {
|
|||
var tokenResponse = JSON.parse(req.responseText);
|
||||
setToken(tokenResponse['access_token'], tokenResponse['refresh_token']);
|
||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||
promise.setSuccess(true);
|
||||
promise && promise.setSuccess();
|
||||
} else {
|
||||
kc.onAuthError && kc.onAuthError();
|
||||
promise.setError();
|
||||
promise && promise.setError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -243,33 +257,75 @@ var Keycloak = function (config) {
|
|||
} else if (error) {
|
||||
if (prompt != 'none') {
|
||||
kc.onAuthError && kc.onAuthError();
|
||||
promise.setError();
|
||||
promise && promise.setError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfig(url, configPromise) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url, true);
|
||||
req.setRequestHeader('Accept', 'application/json');
|
||||
function loadConfig(url) {
|
||||
var promise = createPromise();
|
||||
var configUrl;
|
||||
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
var config = JSON.parse(req.responseText);
|
||||
if (!config) {
|
||||
configUrl = 'keycloak.json';
|
||||
} else if (typeof config === 'string') {
|
||||
configUrl = config;
|
||||
}
|
||||
|
||||
kc.authServerUrl = config['auth-server-url'];
|
||||
kc.realm = config['realm'];
|
||||
kc.clientId = config['resource'];
|
||||
if (configUrl) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', configUrl, true);
|
||||
req.setRequestHeader('Accept', 'application/json');
|
||||
|
||||
configPromise.setSuccess();
|
||||
} else {
|
||||
configPromise.setError();
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
var config = JSON.parse(req.responseText);
|
||||
|
||||
kc.authServerUrl = config['auth-server-url'];
|
||||
kc.realm = config['realm'];
|
||||
kc.clientId = config['resource'];
|
||||
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
promise.setError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.send();
|
||||
} else {
|
||||
if (!config['url']) {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
if (scripts[i].src.match(/.*keycloak\.js/)) {
|
||||
config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.send();
|
||||
if (!config.realm) {
|
||||
throw 'realm missing';
|
||||
}
|
||||
|
||||
if (!config.clientId) {
|
||||
throw 'clientId missing';
|
||||
}
|
||||
|
||||
kc.authServerUrl = config.url;
|
||||
kc.realm = config.realm;
|
||||
kc.clientId = config.clientId;
|
||||
|
||||
promise.setSuccess();
|
||||
}
|
||||
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
setToken(null, null);
|
||||
kc.onAuthLogout && kc.onAuthLogout();
|
||||
}
|
||||
|
||||
function setToken(token, refreshToken) {
|
||||
|
@ -310,21 +366,6 @@ var Keycloak = function (config) {
|
|||
}
|
||||
}
|
||||
|
||||
function getEncodedRedirectUri(redirectUri) {
|
||||
var url;
|
||||
if (redirectUri) {
|
||||
url = redirectUri;
|
||||
} else if (kc.redirectUri) {
|
||||
url = kc.redirectUri;
|
||||
} else {
|
||||
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';
|
||||
|
@ -339,7 +380,6 @@ var Keycloak = function (config) {
|
|||
}
|
||||
|
||||
function parseCallback(url) {
|
||||
|
||||
if (url.indexOf('?') != -1) {
|
||||
var oauth = {};
|
||||
|
||||
|
@ -365,7 +405,7 @@ var Keycloak = function (config) {
|
|||
}
|
||||
}
|
||||
|
||||
if (oauth.state && oauth.state == sessionStorage.oauthState) {
|
||||
if ((oauth.code || oauth.error) && oauth.state && oauth.state == sessionStorage.oauthState) {
|
||||
delete sessionStorage.oauthState;
|
||||
return oauth;
|
||||
}
|
||||
|
@ -412,6 +452,103 @@ var Keycloak = function (config) {
|
|||
return p;
|
||||
}
|
||||
|
||||
function loadAdapter(type) {
|
||||
if (!type || type == 'default') {
|
||||
return {
|
||||
login: function(options) {
|
||||
window.location.href = kc.createLoginUrl(options);
|
||||
},
|
||||
|
||||
logout: function(options) {
|
||||
window.location.href = kc.createLogoutUrl(options);
|
||||
},
|
||||
|
||||
accountManagement : function() {
|
||||
window.location.href = kc.createAccountUrl();
|
||||
},
|
||||
|
||||
redirectUri: function(options) {
|
||||
if (options && options.redirectUri) {
|
||||
return options.redirectUri;
|
||||
} else if (kc.redirectUri) {
|
||||
return kc.redirectUri;
|
||||
} else {
|
||||
var url = (location.protocol + '//' + location.hostname + (location.port && (':' + location.port)) + location.pathname);
|
||||
if (location.hash) {
|
||||
url += '?redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (type == 'cordova') {
|
||||
console.debug('Enabling Cordova support');
|
||||
|
||||
return {
|
||||
login: function(options) {
|
||||
var promise = createPromise();
|
||||
|
||||
var o = 'location=no';
|
||||
if (options && options.prompt == 'none') {
|
||||
o += ',hidden=yes';
|
||||
}
|
||||
|
||||
var loginUrl = kc.createLoginUrl(options);
|
||||
var ref = window.open(loginUrl, '_blank', o);
|
||||
ref.addEventListener('loadstart', function(event) {
|
||||
if (event.url.indexOf('http://localhost') == 0) {
|
||||
var callback = parseCallback(event.url);
|
||||
ref.close();
|
||||
processCallback(callback);
|
||||
|
||||
if (callback.code) {
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
promise.setError();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return promise.promise;
|
||||
},
|
||||
|
||||
logout: function(options) {
|
||||
var promise = createPromise();
|
||||
|
||||
var logoutUrl = kc.createLogoutUrl(options);
|
||||
var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes');
|
||||
ref.addEventListener('loadstart', function(event) {
|
||||
if (event.url.indexOf('http://localhost') == 0) {
|
||||
ref.close();
|
||||
clearToken();
|
||||
promise.setSuccess();
|
||||
}
|
||||
});
|
||||
|
||||
return promise.promise;
|
||||
},
|
||||
|
||||
accountManagement : function() {
|
||||
var accountUrl = kc.createAccountUrl();
|
||||
var ref = window.open(accountUrl, '_blank', 'location=no');
|
||||
ref.addEventListener('loadstart', function(event) {
|
||||
if (event.url.indexOf('http://localhost') == 0) {
|
||||
ref.close();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
redirectUri: function(options) {
|
||||
return 'http://localhost';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw 'invalid adapter type: ' + type;
|
||||
}
|
||||
|
||||
var idTokenProperties = [
|
||||
"name",
|
||||
"given_name",
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"realm" : "test",
|
||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDddHi8/MoYNtdafydQ+e4P0qrvClCW0o/x9fAbZ09dET/VBZCU28M54LmgJT7snXfreyYBpaQpHkW52/tyuEcJ5KO28LIcvObDQFDq3Z7esrovHl4NRETJBF9Xqwt+XLTZF6m37fYzaUK6MVJzUHP9qmu90LYyyvQ+hBJD0GSw1QIDAQAB",
|
||||
"auth-server-url" : "http://localhost:8081/auth",
|
||||
"ssl-not-required" : true,
|
||||
"resource" : "test",
|
||||
"public-client" : true
|
||||
}
|
|
@ -99,7 +99,7 @@ public class AccountService {
|
|||
|
||||
Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
|
||||
|
||||
String referrer = getReferrer();
|
||||
String[] referrer = getReferrer();
|
||||
if (referrer != null) {
|
||||
account.setReferrer(referrer);
|
||||
}
|
||||
|
@ -377,13 +377,17 @@ public class AccountService {
|
|||
uriBuilder.queryParam("path", path);
|
||||
}
|
||||
|
||||
String referrer = getReferrer();
|
||||
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
||||
if (referrer != null) {
|
||||
uriBuilder.queryParam("referrer", referrer);
|
||||
}
|
||||
|
||||
URI accountUri = uriBuilder.build(realm.getName());
|
||||
String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
|
||||
if (referrerUri != null) {
|
||||
uriBuilder.queryParam("referrer_uri", referrerUri);
|
||||
}
|
||||
|
||||
URI accountUri = uriBuilder.build(realm.getName());
|
||||
|
||||
oauth.setStateCookiePath(accountUri.getRawPath());
|
||||
return oauth.redirect(uriInfo, accountUri.toString());
|
||||
|
@ -397,20 +401,34 @@ public class AccountService {
|
|||
return auth;
|
||||
}
|
||||
|
||||
private String getReferrer() {
|
||||
private String[] getReferrer() {
|
||||
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
||||
if (referrer != null) {
|
||||
return referrer;
|
||||
if (referrer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String referrerUrl = headers.getHeaderString("Referer");
|
||||
if (referrerUrl != null) {
|
||||
for (ApplicationModel a : realm.getApplications()) {
|
||||
if (a.getBaseUrl() != null && referrerUrl.startsWith(a.getBaseUrl())) {
|
||||
return a.getName();
|
||||
String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
|
||||
|
||||
ApplicationModel application = realm.getApplicationByName(referrer);
|
||||
if (application != null) {
|
||||
if (referrerUri != null) {
|
||||
referrerUri = TokenService.verifyRedirectUri(referrerUri, application);
|
||||
} else {
|
||||
referrerUri = application.getBaseUrl();
|
||||
}
|
||||
|
||||
if (referrerUri != null) {
|
||||
return new String[] { referrer, referrerUri };
|
||||
}
|
||||
} else if (referrerUri != null) {
|
||||
ClientModel client = realm.getOAuthClient(referrer);
|
||||
if (client != null) {
|
||||
referrerUri = TokenService.verifyRedirectUri(referrerUri, application);
|
||||
|
||||
if (referrerUri != null) {
|
||||
return new String[] { referrer, referrerUri };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -107,19 +107,7 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void returnToAppFromHeader() {
|
||||
appPage.open();
|
||||
appPage.openAccount();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
Assert.assertTrue(profilePage.isCurrent());
|
||||
profilePage.backToApplication();
|
||||
|
||||
Assert.assertTrue(appPage.isCurrent());
|
||||
}
|
||||
|
||||
//@Test
|
||||
@Test
|
||||
public void returnToAppFromQueryParam() {
|
||||
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
@ -127,6 +115,13 @@ public class AccountTest {
|
|||
profilePage.backToApplication();
|
||||
|
||||
Assert.assertTrue(appPage.isCurrent());
|
||||
|
||||
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app&referrer_uri=http://localhost:8081/app?test");
|
||||
Assert.assertTrue(profilePage.isCurrent());
|
||||
profilePage.backToApplication();
|
||||
|
||||
Assert.assertTrue(appPage.isCurrent());
|
||||
Assert.assertEquals(appPage.baseUrl + "?test", driver.getCurrentUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -42,7 +42,7 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
|
|||
private WebElement emailInput;
|
||||
|
||||
|
||||
@FindBy(linkText = "Back to application")
|
||||
@FindBy(id = "referrer")
|
||||
private WebElement backToApplicationLink;
|
||||
|
||||
@FindBy(css = "button[type=\"submit\"]")
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
|
|||
*/
|
||||
public class AppPage extends AbstractPage {
|
||||
|
||||
private String baseUrl = "http://localhost:8081/app";
|
||||
public static final String baseUrl = "http://localhost:8081/app";
|
||||
|
||||
@FindBy(id = "account")
|
||||
private WebElement accountLink;
|
||||
|
|
Loading…
Reference in a new issue