keycloak-memoizeit
Changes
examples/js/index.html 5(+3 -2)
examples/js/keycloak.js 242(+123 -119)
examples/js/testrealm.json 2(+2 -0)
examples/js-google/index.html 24(+24 -0)
examples/js-google/keycloak.js 139(+139 -0)
examples/js-google/keycloak.js.orig 222(+222 -0)
examples/js-google/kinvey.html 13(+13 -0)
examples/js-google/testrealm.json 60(+60 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/customer-login-screen-bg.svg 164(+0 -164)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-arrow-down.svg 11(+0 -11)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/feedback-error-sign.svg 19(+0 -19)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png 0(+0 -0)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.svg 7(+0 -7)
forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-social-separators.svg 19(+0 -19)
forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl 30(+21 -9)
pom.xml 5(+5 -0)
services/pom.xml 4(+4 -0)
testsuite/integration/pom.xml 4(+4 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java 50(+48 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractAccountPage.java 50(+11 -39)
testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java 12(+11 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java 2(+1 -1)
Details
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
index 8c6f178..343a462 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
@@ -196,6 +196,20 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application,
}
}, true);
+ $scope.deleteWebOrigin = function(index) {
+ $scope.application.webOrigins.splice(index, 1);
+ }
+ $scope.addWebOrigin = function() {
+ $scope.application.webOrigins.push($scope.newWebOrigin);
+ $scope.newWebOrigin = "";
+ }
+ $scope.deleteRedirectUri = function(index) {
+ $scope.application.redirectUris.splice(index, 1);
+ }
+ $scope.addRedirectUri = function() {
+ $scope.application.redirectUris.push($scope.newRedirectUri);
+ $scope.newRedirectUri = "";
+ }
$scope.save = function() {
if ($scope.applicationForm.$valid) {
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
index bc23ca3..8bca606 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html
@@ -50,7 +50,7 @@
</div>
</div>
<div class="form-group">
- <label for="adminUrl" class="control-label">Base URL</label>
+ <label for="baseUrl" class="control-label">Base URL</label>
<div class="controls">
<input class="input-small" type="text" name="baseUrl" id="baseUrl"
@@ -65,6 +65,36 @@
data-ng-model="application.adminUrl">
</div>
</div>
+ <div class="form-group">
+ <label for="newWebOrigin" class="control-label">Web Origin</label>
+ <div class="controls">
+ <div ng-repeat="webOrigin in application.webOrigins" class="item-deletable">
+ <input class="input-small" type="text" data-ng-class="{'input-below':!$first}"
+ name="webOrigin" id="webOrigin" data-ng-model="webOrigin" readonly />
+ <button type="button" data-ng-click="deleteWebOrigin($index)" class="btn-delete">
+ Delete</button>
+ </div>
+ <input class="input-small" type="text" name="newWebOrigin" id="newWebOrigin"
+ placeholder="New Web Origin..." data-ng-model="newWebOrigin"
+ data-ng-class="{'input-below':application.webOrigins.length}" />
+ <button data-ng-click="addWebOrigin()" ng-show="newWebOrigin.length > 0">Add</button>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="newRedirectUri" class="control-label">Redirect URI</label>
+ <div class="controls">
+ <div ng-repeat="redirectUri in application.redirectUris" class="item-deletable">
+ <input class="input-small" type="text" data-ng-class="{'input-below':!$first}"
+ name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
+ <button type="button" data-ng-click="deleteRedirectUri($index)" class="btn-delete">
+ Delete</button>
+ </div>
+ <input class="input-small" type="text" name="newRedirectUri" id="newRedirectUri"
+ placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
+ data-ng-class="{'input-below':application.redirectUris.length}" />
+ <button data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
+ </div>
+ </div>
</fieldset>
<div class="form-actions" data-ng-show="create">
<button type="submit" data-ng-click="save()" class="primary">Save
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
index f3d97f8..8c5fa9c 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html
@@ -76,6 +76,20 @@
</div>
</div>
<div class="form-group clearfix block">
+ <label for="accountManagement" class="control-label">User account management</label>
+ <div class="onoffswitch">
+ <input type="checkbox" data-ng-model="realm.accountManagement" class="onoffswitch-checkbox"
+ name="accountManagement" id="accountManagement">
+ <label for="accountManagement" class="onoffswitch-label">
+ <span class="onoffswitch-inner">
+ <span class="onoffswitch-active">ON</span>
+ <span class="onoffswitch-inactive">OFF</span>
+ </span>
+ <span class="onoffswitch-switch"></span>
+ </label>
+ </div>
+ </div>
+ <div class="form-group clearfix block">
<label for="requireSsl" class="control-label">Require SSL</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.requireSsl" class="onoffswitch-checkbox" name="requireSsl" id="requireSsl">
diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
index 92216b7..5c03ee5 100644
--- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
+++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css
@@ -42,6 +42,13 @@ input[type="password"].error:focus,
input[type="email"].error:focus {
box-shadow: 0 0 5px #ba1212;
}
+.input-below {
+ clear: both;
+ display: inline-block;
+ margin-left: 10.9090909090909em;
+ margin-top: 0.45454545454545em;
+ padding-left: 3.63636363636364em;
+}
input[type="button"],
button,
a.button {
@@ -776,3 +783,11 @@ input[type="email"].tiny {
.breadcrumb > li + li:before {
content: "» ";
}
+
+.item-deletable:hover .btn-delete {
+ display: inline-block;
+}
+
+.btn-delete {
+ display: none;
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
index 71b58d7..0fde587 100755
--- a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
+++ b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java
@@ -22,7 +22,14 @@ import java.net.URI;
public class JaxrsOAuthClient extends AbstractOAuthClient {
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
public Response redirect(UriInfo uriInfo, String redirectUri) {
+ return redirect(uriInfo, redirectUri, null);
+ }
+
+ public Response redirect(UriInfo uriInfo, String redirectUri, String path) {
String state = getStateCode();
+ if (path != null) {
+ state += "#" + path;
+ }
URI url = UriBuilder.fromUri(authUrl)
.queryParam("client_id", clientId)
@@ -58,7 +65,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
return uriInfo.getQueryParameters().getFirst("code");
}
- public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
+ public String checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
Cookie stateCookie = headers.getCookies().get(stateCookieName);
if (stateCookie == null) throw new BadRequestException("state cookie not set");
String state = uriInfo.getQueryParameters().getFirst("state");
@@ -66,5 +73,10 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
if (!state.equals(stateCookie.getValue())) {
throw new BadRequestException("state parameter invalid");
}
+ if (state.indexOf('#') != -1) {
+ return state.substring(state.indexOf('#') + 1);
+ } else {
+ return null;
+ }
}
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 7b7fa9d..878092e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -21,6 +21,7 @@ public class ApplicationRepresentation {
protected List<UserRoleMappingRepresentation> roleMappings;
protected List<ScopeMappingRepresentation> scopeMappings;
protected List<String> redirectUris;
+ protected List<String> webOrigins;
public String getSelf() {
return self;
@@ -155,4 +156,12 @@ public class ApplicationRepresentation {
public void setRedirectUris(List<String> redirectUris) {
this.redirectUris = redirectUris;
}
+
+ public List<String> getWebOrigins() {
+ return webOrigins;
+ }
+
+ public void setWebOrigins(List<String> webOrigins) {
+ this.webOrigins = webOrigins;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 14d7d50..45d47d8 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -16,6 +16,7 @@ public class RealmRepresentation {
protected Integer accessCodeLifespan;
protected Integer accessCodeLifespanUserAction;
protected Boolean enabled;
+ protected Boolean accountManagement;
protected Boolean sslNotRequired;
protected Boolean cookieLoginAllowed;
protected Boolean registrationAllowed;
@@ -101,6 +102,14 @@ public class RealmRepresentation {
this.enabled = enabled;
}
+ public Boolean isAccountManagement() {
+ return accountManagement;
+ }
+
+ public void setAccountManagement(Boolean accountManagement) {
+ this.accountManagement = accountManagement;
+ }
+
public Boolean isSslNotRequired() {
return sslNotRequired;
}
examples/js/index.html 5(+3 -2)
diff --git a/examples/js/index.html b/examples/js/index.html
index bfe60fe..4a7b8d2 100644
--- a/examples/js/index.html
+++ b/examples/js/index.html
@@ -10,13 +10,14 @@
clientId : 'test-app',
clientSecret : 'password',
baseUrl : 'http://localhost:8081/auth-server',
- realm : 'test'
+ realm : 'test',
+ redirectUri : 'http://localhost/js'
});
if (keycloak.authenticated) {
document.write('User: ' + keycloak.user);
} else {
- document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
+ document.write('<a href="#" id="login" onclick="keycloak.login(location.hash)">Login</a>');
}
</script>
examples/js/keycloak.js 242(+123 -119)
diff --git a/examples/js/keycloak.js b/examples/js/keycloak.js
index dfb6ebb..cdd9374 100644
--- a/examples/js/keycloak.js
+++ b/examples/js/keycloak.js
@@ -1,120 +1,124 @@
-window.keycloak = (function() {
- var kc = {};
- var config = null;
-
- kc.init = function(c) {
- config = c;
-
- var token = getTokenFromCode();
- if (token) {
- var t = parseToken(token);
- kc.user = t.prn;
- kc.authenticated = true;
- } else {
- kc.authenticated = false;
- }
- }
-
- kc.login = function() {
- var clientId = encodeURIComponent(config.clientId);
- var redirectUri = encodeURIComponent(window.location.href);
- var state = encodeURIComponent(createUUID());
- var realm = encodeURIComponent(config.realm);
- var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
- + '&state=' + state;
- window.location.href = url;
- }
-
- return kc;
-
- function parseToken(token) {
- var t = base64Decode(token.split('.')[1]);
- return JSON.parse(t);
- }
-
- function getTokenFromCode() {
- var code = getQueryParam('code');
- if (code) {
- window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
-
- var clientId = encodeURIComponent(config.clientId);
- var clientSecret = encodeURIComponent(config.clientSecret);
- var realm = encodeURIComponent(config.realm);
-
- var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
- var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
-
- var http = new XMLHttpRequest();
- http.open('POST', url, false);
- http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-
- http.send(params);
- if (http.status == 200) {
- return JSON.parse(http.responseText)['access_token'];
- }
- }
- return undefined;
- }
-
- function getQueryParam(name) {
- var params = window.location.search.substring(1).split('&');
- for ( var i = 0; i < params.length; i++) {
- var p = params[i].split('=');
- if (decodeURIComponent(p[0]) == name) {
- return p[1];
- }
- }
- }
-
- 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;
- }
-
- function base64Decode (data) {
- var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
- ac = 0,
- dec = "",
- tmp_arr = [];
-
- if (!data) {
- return data;
- }
-
- data += '';
-
- do {
- h1 = b64.indexOf(data.charAt(i++));
- h2 = b64.indexOf(data.charAt(i++));
- h3 = b64.indexOf(data.charAt(i++));
- h4 = b64.indexOf(data.charAt(i++));
-
- bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
-
- o1 = bits >> 16 & 0xff;
- o2 = bits >> 8 & 0xff;
- o3 = bits & 0xff;
-
- if (h3 == 64) {
- tmp_arr[ac++] = String.fromCharCode(o1);
- } else if (h4 == 64) {
- tmp_arr[ac++] = String.fromCharCode(o1, o2);
- } else {
- tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
- }
- } while (i < data.length);
-
- dec = tmp_arr.join('');
-
- return dec;
- }
+window.keycloak = (function () {
+ var kc = {};
+ var config = {
+ baseUrl: null,
+ clientId: null,
+ clientSecret: null,
+ realm: null,
+ redirectUri: null
+ };
+
+ kc.init = function (c) {
+ for (var prop in config) {
+ if (c[prop]) {
+ config[prop] = c[prop];
+ }
+
+ if (!config[prop]) {
+ throw new Error(prop + 'not defined');
+ }
+ }
+
+ processCallback();
+ }
+
+ kc.login = function () {
+ window.location.href = getLoginUrl();
+ }
+
+ return kc;
+
+ function getLoginUrl(fragment) {
+ var state = createUUID();
+ if (fragment) {
+ state += '#' + fragment;
+ }
+ sessionStorage.state = state;
+ var url = config.baseUrl + '/rest/realms/' + encodeURIComponent(config.realm) + '/tokens/login?response_type=code&client_id='
+ + encodeURIComponent(config.clientId) + '&redirect_uri=' + encodeURIComponent(config.redirectUri) + '&state=' + encodeURIComponent(state);
+ return url;
+ }
+
+ function parseToken(token) {
+ return JSON.parse(atob(token.split('.')[1]));
+ }
+
+ function processCallback() {
+ var code = getQueryParam('code');
+ var error = getQueryParam('error');
+ var state = getQueryParam('state');
+
+ if (!(code || error)) {
+ return false;
+ }
+
+ if (state != sessionStorage.state) {
+ console.error('Invalid state');
+ return true;
+ }
+
+ if (code) {
+ console.info('Received code');
+
+ var clientId = encodeURIComponent(config.clientId);
+ var clientSecret = encodeURIComponent(config.clientSecret);
+ var realm = encodeURIComponent(config.realm);
+
+ var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
+ var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+ var http = new XMLHttpRequest();
+ http.open('POST', url, false);
+ http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+ http.send(params);
+ if (http.status == 200) {
+ kc.token = JSON.parse(http.responseText)['access_token'];
+ kc.tokenParsed = parseToken(kc.token);
+ kc.authenticated = true;
+ kc.user = kc.tokenParsed.prn;
+
+ console.info('Authenticated');
+ }
+
+ updateLocation(state);
+ return true;
+ } else if (error) {
+ console.info('Error ' + error);
+ updateLocation(state);
+ return true;
+ }
+ }
+
+ function updateLocation(state) {
+ var fragment = '';
+ if (state && state.indexOf('#') != -1) {
+ fragment = state.substr(state.indexOf('#'));
+ }
+
+ window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname + fragment);
+ }
+
+ function getQueryParam(name) {
+ var params = window.location.search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ if (decodeURIComponent(p[0]) == name) {
+ return p[1];
+ }
+ }
+ }
+
+ 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;
+ }
})();
\ No newline at end of file
examples/js/testrealm.json 2(+2 -0)
diff --git a/examples/js/testrealm.json b/examples/js/testrealm.json
index 824fe14..38225c6 100755
--- a/examples/js/testrealm.json
+++ b/examples/js/testrealm.json
@@ -8,6 +8,7 @@
"sslNotRequired": true,
"cookieLoginAllowed": true,
"registrationAllowed": true,
+ "accountManagement": true,
"resetPasswordAllowed": 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",
@@ -48,6 +49,7 @@
"enabled": true,
"adminUrl": "http://localhost:8081/app/logout",
"useRealmMappings": true,
+ "webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
"credentials": [
{
"type": "password",
examples/js-google/index.html 24(+24 -0)
diff --git a/examples/js-google/index.html b/examples/js-google/index.html
new file mode 100644
index 0000000..7a72120
--- /dev/null
+++ b/examples/js-google/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="keycloak.js"></script>
+</head>
+<body>
+ <script>
+ keycloak.init({
+ clientId : '57572475438.apps.googleusercontent.com',
+ clientSecret : 'xyfsPS9maRTz5fj0pOxf0zjD'
+ });
+
+ if (keycloak.authenticated) {
+ document.write('<h2>Token</h2><pre>' + keycloak.token + '</pre>');
+ document.write('<h2>Token info</h2><pre>' + JSON.stringify(keycloak.tokenInfo, undefined, 4) + '</pre>');
+ document.write('<h2>Profile</h2><pre>' + JSON.stringify(keycloak.profile(true), undefined, 4) + '</pre>');
+ document.write('<h2>Contacts</h2><pre>' + keycloak.contacts(true) + '</pre>');
+ } else {
+ document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
+ }
+ </script>
+
+</body>
+</html>
examples/js-google/keycloak.js 139(+139 -0)
diff --git a/examples/js-google/keycloak.js b/examples/js-google/keycloak.js
new file mode 100644
index 0000000..172557d
--- /dev/null
+++ b/examples/js-google/keycloak.js
@@ -0,0 +1,139 @@
+window.keycloak = (function () {
+ var kc = {};
+ var config = {
+ clientId: null,
+ clientSecret: null
+ };
+
+ kc.init = function (c) {
+ for (var prop in config) {
+ if (c[prop]) {
+ config[prop] = c[prop];
+ }
+
+ if (!config[prop]) {
+ throw new Error(prop + ' not defined');
+ }
+ }
+
+ loadToken();
+
+ if (kc.token) {
+ kc.user = kc.tokenInfo.user_id;
+ kc.authenticated = true;
+ } else {
+ kc.authenticated = false;
+ kc.user = null;
+ }
+ }
+
+ kc.login = function () {
+ var clientId = encodeURIComponent(config.clientId);
+ var redirectUri = encodeURIComponent(window.location.href);
+ var state = encodeURIComponent(createUUID());
+ var scope = encodeURIComponent('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login');
+ var url = 'https://accounts.google.com/o/oauth2/auth?response_type=token&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ + '&state=' + state + '&scope=' + scope;
+
+ sessionStorage.state = state;
+
+ window.location.href = url;
+ }
+
+ function parseToken(token) {
+ return JSON.parse(atob(token.split('.')[1]));
+ }
+
+ kc.profile = function(header) {
+ var url = 'https://www.googleapis.com/oauth2/v1/userinfo'
+
+ if (!header) {
+ url = url + '?access_token=' + kc.token;
+ }
+
+ var http = new XMLHttpRequest();
+ http.open('GET', url, false);
+ if (header) {
+ http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
+ }
+
+ http.send();
+ if (http.status == 200) {
+ return JSON.parse(http.responseText);
+ }
+ }
+
+ kc.contacts = function(header) {
+ var url = 'https://www.googleapis.com/plus/v1/people/me';
+
+ if (!header) {
+ url = url + '?access_token=' + kc.token;
+ }
+
+ var http = new XMLHttpRequest();
+ http.open('GET', url, false);
+ if (header) {
+ http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
+ }
+
+ http.send();
+ if (http.status == 200) {
+ return http.responseText;
+ }
+ }
+
+ return kc;
+
+ function loadToken() {
+ var params = {}
+ var queryString = location.hash.substring(1)
+ var regex = /([^&=]+)=([^&]*)/g, m;
+ while (m = regex.exec(queryString)) {
+ params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
+ }
+
+ var token = params['access_token'];
+ var state = params['state'];
+
+ if (token && state === sessionStorage.state) {
+ window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+ kc.token = token;
+
+ var url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token;
+
+ var http = new XMLHttpRequest();
+ http.open('GET', url, false);
+
+ http.send();
+ if (http.status == 200) {
+ kc.tokenInfo = JSON.parse(http.responseText);
+ }
+ }
+ return undefined;
+ }
+
+ function getQueryParam(name) {
+ console.debug(window.location.hash);
+ var params = window.location.hash.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ if (decodeURIComponent(p[0]) == name) {
+ return p[1];
+ }
+ }
+ }
+
+ 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;
+ }
+})();
examples/js-google/keycloak.js.orig 222(+222 -0)
diff --git a/examples/js-google/keycloak.js.orig b/examples/js-google/keycloak.js.orig
new file mode 100644
index 0000000..439d2af
--- /dev/null
+++ b/examples/js-google/keycloak.js.orig
@@ -0,0 +1,222 @@
+<<<<<<< Updated upstream
+window.keycloak = (function() {
+ var kc = {};
+ var config = null;
+
+ kc.init = function(c) {
+ config = c;
+
+ var token = getTokenFromCode();
+ if (token) {
+ var t = parseToken(token);
+ kc.user = t.prn;
+ kc.authenticated = true;
+ } else {
+ kc.authenticated = false;
+ }
+ }
+
+ kc.login = function() {
+ var clientId = encodeURIComponent(config.clientId);
+ var redirectUri = encodeURIComponent(window.location.href);
+ var state = encodeURIComponent(createUUID());
+ var realm = encodeURIComponent(config.realm);
+ var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ + '&state=' + state;
+ window.location.href = url;
+ }
+
+ return kc;
+
+ function parseToken(token) {
+ return JSON.parse(atob(token.split('.')[1]));
+ }
+
+ function getTokenFromCode() {
+ var code = getQueryParam('code');
+ if (code) {
+ window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+ var clientId = encodeURIComponent(config.clientId);
+ var clientSecret = encodeURIComponent(config.clientSecret);
+ var realm = encodeURIComponent(config.realm);
+
+ var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
+ var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+ var http = new XMLHttpRequest();
+ http.open('POST', url, false);
+ http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+ http.send(params);
+ if (http.status == 200) {
+ return JSON.parse(http.responseText)['access_token'];
+ }
+ }
+ return undefined;
+ }
+
+ function getQueryParam(name) {
+ var params = window.location.search.substring(1).split('&');
+ for ( var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ if (decodeURIComponent(p[0]) == name) {
+ return p[1];
+ }
+ }
+ }
+
+ 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.keycloak = (function () {
+ var kc = {};
+ var config = {
+ baseUrl : null,
+ clientId : null,
+ clientSecret: null,
+ realm: null
+ };
+
+ kc.init = function (c) {
+ for (var prop in config) {
+ if (c[prop]) {
+ config[prop] = c[prop];
+ }
+
+ if (!config[prop]) {
+ throw new Error(prop + 'not defined');
+ }
+ }
+
+ var token = getTokenFromCode();
+ if (token) {
+ var t = parseToken(token);
+ kc.user = t.prn;
+ kc.authenticated = true;
+ } else {
+ kc.authenticated = false;
+ }
+ }
+
+ kc.login = function () {
+ var clientId = encodeURIComponent(config.clientId);
+ var redirectUri = encodeURIComponent(window.location.href);
+ var realm = encodeURIComponent(config.realm);
+ var state = encodeURIComponent(createUUID());
+ var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ + '&state=' + state;
+
+ sessionStorage.state = state;
+
+ window.location.href = url;
+ }
+
+ return kc;
+
+ function parseToken(token) {
+ var t = base64Decode(token.split('.')[1]);
+ return JSON.parse(t);
+ }
+
+ function getTokenFromCode() {
+ var code = getQueryParam('code');
+ var state = getQueryParam('state');
+
+ if (code) {
+ if (state && state === sessionStorage.state) {
+ window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
+
+ var clientId = encodeURIComponent(config.clientId);
+ var clientSecret = encodeURIComponent(config.clientSecret);
+ var realm = encodeURIComponent(config.realm);
+
+ var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
+ var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
+
+ var http = new XMLHttpRequest();
+ http.open('POST', url, false);
+ http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+ http.send(params);
+ if (http.status == 200) {
+ return JSON.parse(http.responseText)['access_token'];
+ }
+ }
+ }
+ return undefined;
+ }
+
+ function getQueryParam(name) {
+ var params = window.location.search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ if (decodeURIComponent(p[0]) == name) {
+ return p[1];
+ }
+ }
+ }
+
+ 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;
+ }
+
+ function base64Decode(data) {
+ var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
+ ac = 0,
+ dec = "",
+ tmp_arr = [];
+
+ if (!data) {
+ return data;
+ }
+
+ data += '';
+
+ do {
+ h1 = b64.indexOf(data.charAt(i++));
+ h2 = b64.indexOf(data.charAt(i++));
+ h3 = b64.indexOf(data.charAt(i++));
+ h4 = b64.indexOf(data.charAt(i++));
+
+ bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
+
+ o1 = bits >> 16 & 0xff;
+ o2 = bits >> 8 & 0xff;
+ o3 = bits & 0xff;
+
+ if (h3 == 64) {
+ tmp_arr[ac++] = String.fromCharCode(o1);
+ } else if (h4 == 64) {
+ tmp_arr[ac++] = String.fromCharCode(o1, o2);
+ } else {
+ tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
+ }
+ } while (i < data.length);
+
+ dec = tmp_arr.join('');
+
+ return dec;
+ }
+>>>>>>> Stashed changes
+})();
\ No newline at end of file
examples/js-google/kinvey.html 13(+13 -0)
diff --git a/examples/js-google/kinvey.html b/examples/js-google/kinvey.html
new file mode 100644
index 0000000..9e1324c
--- /dev/null
+++ b/examples/js-google/kinvey.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="http://code.jquery.com/jquery-2.0.3.js"></script>
+</head>
+<body>
+
+ <script>
+ $.ajax('https://baas.kinvey.com/appdata/kid_PVD-jo1HqO');
+ </script>
+
+</body>
+</html>
examples/js-google/testrealm.json 60(+60 -0)
diff --git a/examples/js-google/testrealm.json b/examples/js-google/testrealm.json
new file mode 100755
index 0000000..2468f48
--- /dev/null
+++ b/examples/js-google/testrealm.json
@@ -0,0 +1,60 @@
+{
+ "id": "test",
+ "realm": "test",
+ "enabled": true,
+ "tokenLifespan": 300,
+ "accessCodeLifespan": 10,
+ "accessCodeLifespanUserAction": 600,
+ "sslNotRequired": true,
+ "cookieLoginAllowed": true,
+ "registrationAllowed": true,
+ "resetPasswordAllowed": 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" ],
+ "requiredApplicationCredentials": [ "password" ],
+ "requiredOAuthClientCredentials": [ "password" ],
+ "defaultRoles": [ "user" ],
+ "users" : [
+ {
+ "username" : "test-user@localhost",
+ "enabled": true,
+ "email" : "test-user@localhost",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ]
+ }
+ ],
+ "roles": [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Have Administrator privileges"
+ }
+ ],
+ "roleMappings": [
+ {
+ "username": "test-user@localhost",
+ "roles": ["user"]
+ }
+ ],
+ "applications": [
+ {
+ "name": "test-app",
+ "enabled": true,
+ "adminUrl": "http://localhost:8081/app/logout",
+ "useRealmMappings": true,
+ "webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
+ "credentials": [
+ {
+ "type": "password",
+ "value": "password"
+ }
+ ]
+ }
+ ]
+}
diff --git a/forms/src/main/java/org/keycloak/forms/TotpBean.java b/forms/src/main/java/org/keycloak/forms/TotpBean.java
index 9919ff4..a283c2e 100644
--- a/forms/src/main/java/org/keycloak/forms/TotpBean.java
+++ b/forms/src/main/java/org/keycloak/forms/TotpBean.java
@@ -78,7 +78,7 @@ public class TotpBean {
public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
String contents = URLEncoder.encode("otpauth://totp/keycloak?secret=" + totpSecretEncoded, "utf-8");
- return contextUrl + "/forms/qrcode" + "?size=246x246&contents=" + contents;
+ return contextUrl + "/rest/qrcode" + "?size=246x246&contents=" + contents;
}
public UserBean getUser() {
diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java
index f527d38..8d25c3d 100755
--- a/forms/src/main/java/org/keycloak/forms/UrlBean.java
+++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java
@@ -124,6 +124,10 @@ public class UrlBean {
return Urls.accountTotpRemove(baseURI, realm.getId()).toString();
}
+ public String getLogoutUrl() {
+ return Urls.accountLogout(baseURI, realm.getId()).toString();
+ }
+
public String getLoginPasswordResetUrl() {
return Urls.loginPasswordReset(baseURI, realm.getId()).toString();
}
diff --git a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
index 9489448..64624e4 100644
--- a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
+++ b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java
@@ -32,7 +32,7 @@ import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.jboss.resteasy.logging.Logger;
-import org.keycloak.forms.ErrorBean;
+import org.keycloak.forms.MessageBean;
import org.keycloak.forms.LoginBean;
import org.keycloak.forms.OAuthGrantBean;
import org.keycloak.forms.RealmBean;
@@ -69,8 +69,7 @@ public class FormServiceImpl implements FormService {
commandMap.put(Pages.TOTP, new CommandTotp());
commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp());
- commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandLoginTotp());
- commandMap.put(Pages.ERROR, new CommandError());
+ commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandVerifyEmail());
commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant());
}
@@ -82,8 +81,8 @@ public class FormServiceImpl implements FormService {
Map<String, Object> attributes = new HashMap<String, Object>();
- if (dataBean.getError() != null){
- attributes.put("message", new ErrorBean(dataBean.getError(), dataBean.getErrorType()));
+ if (dataBean.getMessage() != null){
+ attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType()));
}
RealmBean realm = new RealmBean(dataBean.getRealm());
@@ -161,9 +160,6 @@ public class FormServiceImpl implements FormService {
private class CommandLoginTotp implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
- if (dataBean.getError() != null){
- attributes.put("error", new ErrorBean(dataBean.getError()));
- }
RealmBean realm = new RealmBean(dataBean.getRealm());
@@ -206,10 +202,6 @@ public class FormServiceImpl implements FormService {
private class CommandLogin implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
- if (dataBean.getError() != null){
- attributes.put("error", new ErrorBean(dataBean.getError()));
- }
-
RealmBean realm = new RealmBean(dataBean.getRealm());
attributes.put("realm", realm);
@@ -230,9 +222,6 @@ public class FormServiceImpl implements FormService {
private class CommandRegister implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
- if (dataBean.getError() != null){
- attributes.put("error", new ErrorBean(dataBean.getError()));
- }
RealmBean realm = new RealmBean(dataBean.getRealm());
@@ -252,14 +241,6 @@ public class FormServiceImpl implements FormService {
}
}
- private class CommandError implements Command {
- public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
- if (dataBean.getError() != null){
- attributes.put("error", new ErrorBean(dataBean.getError()));
- }
- }
- }
-
private class CommandOAuthGrant implements Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
@@ -274,6 +255,20 @@ public class FormServiceImpl implements FormService {
}
}
+ private class CommandVerifyEmail implements Command {
+ public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
+
+ RealmBean realm = new RealmBean(dataBean.getRealm());
+
+ attributes.put("realm", realm);
+
+ UrlBean url = new UrlBean(realm, dataBean.getBaseURI());
+ url.setSocialRegistration(dataBean.getSocialRegistration());
+
+ attributes.put("url", url);
+ }
+ }
+
private interface Command {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean);
}
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl
index e5a0a6e..4e7caac 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl
@@ -15,7 +15,7 @@
</div>
<div class="form-group">
<label for="email">${rb.getString('email')}</label><span class="required">*</span>
- <input type="email" id="email" name="email" autofocus value="${user.email!''}"/>
+ <input type="text" id="email" name="email" autofocus value="${user.email!''}"/>
</div>
<div class="form-group">
<label for="lastName">${rb.getString('lastName')}</label><span class="required">*</span>
@@ -27,7 +27,7 @@
</div>
</fieldset>
<div class="form-actions">
- <a href="#">« Back to my application</a>
+ <#--a href="#">« Back to my application</a-->
<button type="submit" class="primary">Save</button>
<button type="submit">Cancel</button>
</div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/admin-console.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/admin-console.css
index af63a63..476a082 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/admin-console.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/admin-console.css
@@ -185,43 +185,6 @@ table a:hover {
font-weight: normal;
margin-left: 1em;
}
-.tooltip-box {
- position: absolute;
- font-size: 1em;
- background-image: url("img/tooltip-box-arrow-right-up.svg");
- background-position: right top;
- background-repeat: no-repeat;
- padding-top: 1em;
- right: 0;
- top: 1.5em;
- font-size: 0.90909090909091em;
-}
-.tooltip-box fieldset {
- width: 30.8em;
- padding-left: 1.5em;
- padding-right: 1.5em;
- padding-top: .5em;
- background-color: #fff;
- border: 1px solid #b6b6b6;
- border-top: none;
- border-radius: 0 2px 2px 2px;
- box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.15);
-}
-.tooltip-box fieldset legend {
- display: none;
-}
-.tooltip-box fieldset label {
- width: 6em;
-}
-.tooltip-box fieldset .form-actions {
- margin: 0;
- padding: 1em 1.5em 1em 0;
- background-color: #f8f8f8;
- display: block;
- float: none;
- margin-right: -1.5em;
- margin-left: -1.5em;
-}
td.token-cell button {
margin-top: -1px;
}
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/forms.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/forms.css
index 0d6bfbf..1dc3c5d 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/forms.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/forms.css
@@ -307,9 +307,7 @@ button[class^="icon-"] {
padding: 0;
line-height: 1em;
}
-button[class^="icon-"]:hover {
- background-image: url(img/sprites.png);
-}
+
legend {
font-size: 1em;
border-width: 1px 0 0 0;
@@ -328,14 +326,7 @@ legend .text {
font-weight: bold;
font-size: 1.25em;
}
-legend .icon-info {
- background-image: url(img/sprites-gray.png);
- margin-left: 1em;
- vertical-align: baseline;
-}
-legend .icon-info:hover {
- background-image: url(img/sprites.png);
-}
+
.form-group {
display: block;
margin-bottom: 1em;
@@ -377,7 +368,7 @@ legend + table {
margin-top: 1em;
}
.code {
- font-family: Courier, monospace;
+ font-family: Courier, monospace;
}
.onoffswitch {
-moz-user-select: none;
@@ -460,20 +451,6 @@ input[type="email"].tiny {
min-width: 40px;
width: 40px;
}
-.select-rcue,
-.select2-container .select2-choice {
- height: 26px;
- border: 1px #b6b6b6 solid;
- border-radius: 2px;
- box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
- color: #333;
- background: #ffffff url(img/select-arrow.png) no-repeat right center;
- overflow: hidden;
- min-width: 75px;
- padding: 0 0.9em 0 0;
- display: inline-block;
- font-family: "Open Sans", sans-serif;
-}
.select-rcue:hover {
border-color: #62afdb;
}
@@ -510,93 +487,7 @@ input[type="email"].tiny {
.select-rcue option:hover {
background-color: #d5ecf9;
}
-.select2-container {
- float: left;
- margin-top: 0.3em;
- margin-bottom: 0.3em;
-}
-.select2-container .select2-choice > .select2-chosen {
- line-height: 2.1em;
- padding-left: 0.90909090909091em;
- margin-right: 0;
- font-size: 1.1em;
- padding-right: 2.36363636363636em;
- padding-right: 26px;
-}
-.select2-container .select2-choice .select2-arrow {
- display: none;
-}
-.select2-dropdown-open {
- background-color: #fff;
-}
-.select2-dropdown-open .select2-choice,
-.select2-dropdown-open .select2-choices {
- border-bottom: none;
- border-radius: 2px 2px 0 0;
- background-image: url(img/chosen-arrow-down.png);
- background-color: transparent;
- background-repeat: no-repeat;
- background-position: right top;
- box-shadow: none;
-}
-.select2-dropdown-open .select2-choice,
-.select2-dropdown-open.select2-drop-above .select2-choice,
-.select2-dropdown-open .select2-choices,
-.select2-dropdown-open.select2-drop-above .select2-choices {
- border-color: #62AFDB;
-}
-.select2-search input {
- min-width: 0;
-}
-.select2-drop-active {
- border-radius: 0 0 2px 2px;
- margin-top: -1px;
- padding-top: 4px;
-}
-.select2-container.select2-drop-above .select2-choice {
- border-radius: 0 0 2px 2px;
- background-image: url(img/chosen-arrow-up.png);
- background-repeat: no-repeat;
- background-position: right 0;
- box-shadow: none;
-}
-.select2-drop.select2-drop-above {
- border-radius: 2px 2px 0 0;
- padding-top: 0;
- margin-top: 2px;
-}
-.select2-drop.select2-drop-above.select2-drop-active,
-.select2-drop-active {
- border-color: #62AFDB;
-}
-.select2-results {
- padding-left: 0;
- margin-right: 0;
-}
-.select2-results li {
- border-top: 1px solid transparent;
- border-bottom: 1px solid transparent;
-}
-.select2-results .select2-result-label,
-.select2-results .select2-no-results,
-.select2-results .select2-searching,
-.select2-results .select2-selection-limit {
- font-size: 1.1em;
- padding-left: 1.09090909090909em;
-}
-.select2-results .select2-no-results,
-.select2-results .select2-searching,
-.select2-results .select2-selection-limit {
- color: #838383;
- padding-top: 3px;
- padding-bottom: 4px;
-}
-.select2-results .select2-highlighted {
- background-color: #d5ecf9;
- border-top: 1px solid #a7d7f1;
- border-bottom: 1px solid #a7d7f1;
- color: #4d5258;
-}
+
.input-group input + .select-rcue {
border-radius: 0 2px 2px 0;
border-left: 0;
@@ -605,93 +496,7 @@ input[type="email"].tiny {
.input-select .input-group input {
float: left;
}
-.tokenfield.form-control {
- width: 40em;
- float: left;
- min-height: 2.6em;
- border: 1px #b6b6b6 solid;
- border-radius: 2px;
- box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
- padding: 0 0.7em;
- margin-bottom: 0;
- font-size: 1em;
- height: auto;
- outline: 0 none;
-}
-.tokenfield.form-control .token {
- display: inline-block;
- background-color: #d4ecf8;
- border: 1px solid #a3d7f0;
- border-radius: 1px;
- padding: 0 0.3em 0 0.7em;
- margin-right: 0.7em;
- margin-top: 0.3em;
- margin-bottom: 0.3em;
- outline: 0 none;
-}
-.tokenfield.form-control .token span {
- float: left;
- font-size: 1.1em;
- line-height: 1.45454545454545em;
-}
-.tokenfield.form-control .token .close {
- text-indent: -99999em;
- width: 1.6em;
- height: 1.6em;
- line-height: 1.6em;
- background: url(img/btn-close-blue.png) no-repeat center center;
- margin-left: 0.3em;
- padding: 0;
- border: none;
- font-size: 1em;
- opacity: 1;
-}
-.tokenfield.form-control input {
- padding: 0;
- border: none;
- font-size: 1.1em;
- line-height: 1.63636363636364em;
- height: 1.63636363636364em;
- margin: 0.272727272727273em 0;
- box-shadow: none;
- outline: 0 none;
- float: left;
-}
-.tokenfield.form-control:hover {
- border-color: #62afdb;
-}
-.tokenfield.form-control:focus {
- border-color: #62afdb;
- box-shadow: #62afdb 0 0 5px;
-}
-.token {
- float: left;
- background-color: #d4ecf8;
- border: 1px solid #a3d7f0;
- border-radius: 1px;
- padding: 0 0.3em 0 0.7em;
- margin-right: 0.7em;
- margin-top: 0.3em;
- margin-bottom: 0.3em;
- outline: 0 none;
-}
-.token span {
- float: left;
- font-size: 1.1em;
- line-height: 1.45454545454545em;
-}
-.token .close {
- text-indent: -9999999em;
- width: 1.6em;
- height: 1.6em;
- line-height: 1.6em;
- background: url(img/btn-close-blue.png) no-repeat center center;
- margin-left: 0.3em;
- padding: 0;
- border: none;
- font-size: 1em;
- opacity: 1;
-}
+
.form-actions {
float: right;
margin-top: 3em;
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png
new file mode 100644
index 0000000..5ea210c
Binary files /dev/null and b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/img/login-register-separator.png differ
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
index 8eacd72..488259d 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/login-register.css
@@ -3,9 +3,9 @@ body {
min-height: 60em;
min-width: 120em;
}
+
.rcue-login-register {
background-color: #1D2226;
- background-image: url("img/login-screen-background.jpg");
background-position: top left;
background-size: auto;
background-repeat: no-repeat;
@@ -17,6 +17,7 @@ body {
/* Info area */
}
+
.rcue-login-register h1 a {
position: absolute;
top: 5em;
@@ -61,12 +62,12 @@ body {
padding-right: 4.5em;
}
.rcue-login-register .form-area {
- background-image: url(img/login-register-separator.svg);
+ background-image: url(img/login-register-separator.png);
background-repeat: no-repeat;
background-position: 40.2em center;
}
.rcue-login-register .form-area.social {
- background-image: url(img/login-register-social-separators.svg);
+ background-image: url(img/login-register-social-separators.png);
background-position: 39.6em center;
}
.rcue-login-register .section > p {
@@ -267,10 +268,6 @@ a.zocial:before {
.rcue-login-register.register form > div.aside-btn p {
line-height: 1.3em;
}
-/* Customer login */
-.rcue-login-register.customer {
- background-image: url("img/customer-login-screen-bg2.jpg");
-}
.rcue-login-register p.powered {
font-size: 1.1em;
margin-top: 1.27272727272727em;
@@ -291,9 +288,6 @@ a.zocial:before {
.rcue-login-register.reset .background-area .section.app-form {
width: 43.2em;
}
-.rcue-login-register.reset .feedback {
- left: 35.7em;
-}
.rcue-login-register.oauth .form-actions {
margin-bottom: 0;
@@ -405,11 +399,6 @@ a.zocial:before {
.rcue-login-register.email label {
width: 6.78571428571429em;
}
-.rcue-login-register.email .form-area.email {
- background-position: 40.6em center;
- background-image: url(img/login-register-email-separator.svg);
- background-repeat: no-repeat;
-}
.rcue-login-register.email .feedback.bottom-left {
left: 38.3em;
}
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/sprites.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/sprites.css
index 1a35f74..1c009b7 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/sprites.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/sprites.css
@@ -3,7 +3,6 @@
display: inline-block;
width: 16px;
height: 16px;
- background-image: url(img/sprites.png); /* Modified by Gabriel */
background-repeat: no-repeat;
text-indent: -99999em;
margin-right: 0.5em;
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/tables.css b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/tables.css
index 15a1f34..b260561 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/css/tables.css
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/css/tables.css
@@ -30,14 +30,6 @@ table thead tr th {
.rcue-table-actions {
padding: 1px;
}
-/*
-.rcue-table-actions button {
- padding: 3px 8px;
- font-size: 11px;
- box-shadow: 1px 2px 2px #e3e3e3;
- margin: 4px;
-}
-*/
.rcue-table-number {
text-align: right;
width: 100px;
@@ -54,11 +46,7 @@ table thead tr th {
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #eeeeee));
border-bottom: 1px #cedede solid;
}
-/*
-table tbody tr td:first-child {
- color: #0099d3;
-}
-*/
+
table thead tr th:last-child {
border-right: none;
}
@@ -70,12 +58,7 @@ table tbody tr:first-child td {
table tbody tr td:last-child {
border-right: none;
}
-/*
-table tbody tr:hover {
- cursor: pointer;
- background-color: #ebebeb;
-}
-*/
+
/* Styles from Gabriel */
.rcue-table-actions button + button,
.rcue-table-actions .button + button {
@@ -109,23 +92,7 @@ table tbody tr td.token-cell button {
margin-bottom: 7px;
margin-top: 8px;
}
-table tbody.selectable-rows tr:hover {
- cursor: pointer;
- background-color: #ebebeb;
-}
-table tbody.selectable-rows tr:first-child td {
- padding-top: 9px;
-}
-table tbody.selectable-rows tr.selected,
-table tbody.selectable-rows tr.selected:hover {
- background-color: #eaf5fb;
-}
-table tbody.selectable-rows tr.selected td:first-child,
-table tbody.selectable-rows tr.selected:hover td:first-child {
- background-image: url(img/icon-row-selected.svg);
- background-position: 0.2em center;
- background-repeat: no-repeat;
-}
+
table tfoot tr {
border-top: 1px solid #cecece;
}
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
index 659fce1..fd3fc1e 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
@@ -12,7 +12,7 @@
<#elseif section = "form">
<p class="instruction">Something happened and we could not process your request.</p>
- <p id="error-summary" class="instruction second">${error.summary}</p>
+ <p id="error-summary" class="instruction second">${message.summary}</p>
<#elseif section = "info" >
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/img/favicon.ico b/forms/src/main/resources/META-INF/resources/forms/theme/default/img/favicon.ico
new file mode 100644
index 0000000..cbb6124
Binary files /dev/null and b/forms/src/main/resources/META-INF/resources/forms/theme/default/img/favicon.ico differ
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
index 2b1c1c5..97ff4ad 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-config-totp.ftl
@@ -8,11 +8,6 @@
Google Authenticator Setup
- <#elseif section = "feedback">
- <div class="feedback warning show">
- <p><strong>Your account is not enabled because you need to set up the Google Authenticator.</strong><br>Please follow the steps below.</p>
- </div>
-
<#elseif section = "form">
<div id="form">
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
index ad80199..af308ac 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-reset-password.ftl
@@ -11,15 +11,6 @@
<#elseif section = "form">
<div id="form">
- <#if message?has_content>
- <#if message.success>
- <div class="feedback success bottom-left show"><p><strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}</p></div>
- </#if>
- <#if message.error>
- <div class="feedback error bottom-left show"><p><strong>${rb.getString('errorHeader')}</strong><br/>${rb.getString(message.summary)}</p></div>
- </#if>
- </#if>
-
<p class="instruction">${rb.getString('emailInstruction')}</p>
<form action="${url.loginPasswordResetUrl}" method="post">
<div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
index cff75f1..0832666 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/login-verify-email.ftl
@@ -8,11 +8,6 @@
Email verification
- <#elseif section = "feedback">
- <div class="feedback warning show">
- <p><strong>Your account is not enabled because you need to verify your email.</strong><br>Please follow the steps below.</p>
- </div>
-
<#elseif section = "form">
<div class="app-form">
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl
index b2ae529..5c48264 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl
@@ -15,15 +15,15 @@
</div>
<div class="form-group">
<label for="password-new">${rb.getString('passwordNew')}</label>
- <input type="password" id="password-new" name="password-new" placeholder="At least 6 characters" class="error">
+ <input type="password" id="password-new" name="password-new">
</div>
<div class="form-group">
<label for="password-confirm" class="two-lines">${rb.getString('passwordConfirm')}</label>
- <input type="password" id="password-confirm" name="password-confirm" class="error">
+ <input type="password" id="password-confirm" name="password-confirm">
</div>
</fieldset>
<div class="form-actions">
- <a href="#">« Back to my application</a>
+ <#--a href="#">« Back to my application</a-->
<button type="submit" class="primary">Save</button>
<button type="submit">Cancel</button>
</div>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
index de023e1..d64a3c6 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login.ftl
@@ -31,10 +31,10 @@
<div class="form-area ${(realm.social)?string('social','')} clearfix">
<div class="section app-form">
<h3>Application login area</h3>
- <#if error?has_content>
+ <#if message?has_content && message.error>
<div class="feedback error bottom-left show">
<p>
- <strong id="loginError">${rb.getString(error.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
+ <strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
</p>
</div>
</#if>
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
index c2920cf..303aeeb 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-login-action.ftl
@@ -17,7 +17,11 @@
<body class="rcue-login-register ${bodyClass}">
<div class="feedback-aligner">
- <#nested "feedback">
+ <#if message?has_content && message.warning>
+ <div class="feedback warning show">
+ <p><strong>${rb.getString('actionWarningHeader')} ${rb.getString(message.summary)}</strong><br/>${rb.getString('actionFollow')}</p>
+ </div>
+ </#if>
</div>
<#if (template.themeConfig.logo)?has_content>
<h1>
@@ -33,18 +37,26 @@
<div class="background-area">
<div class="form-area clearfix">
<div class="section app-form">
+ <#if !isErrorPage && message?has_content>
+ <#if message.error>
+ <div class="feedback error bottom-left show">
+ <p>
+ <strong id="loginError">${rb.getString(message.summary)}</strong><br/>${rb.getString('emailErrorInfo')}
+ </p>
+ </div>
+ <#elseif message.success>
+ <div class="feedback success bottom-left show">
+ <p>
+ <strong>${rb.getString('successHeader')}</strong> ${rb.getString(message.summary)}
+ </p>
+ </div>
+ </#if>
+ </#if>
+
<h3>Application login area</h3>
<#nested "form">
</div>
- <#if !isErrorPage && error?has_content>
- <div class="feedback error bottom-left show">
- <p>
- <strong id="loginError">${rb.getString(error.summary)}</strong>
- </p>
- </div>
- </#if>
-
<div class="section info-area">
<h3>Info area</h3>
<#nested "info">
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
index 18ab4d5..61e24a8 100644
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl
@@ -4,16 +4,15 @@
<head>
<meta charset="utf-8">
<title>Edit Account - <#nested "title"></title>
+ <!-- TODO replace with actual logo once
<link rel="icon" href="img/favicon.ico">
<!-- Frameworks -->
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/reset.css">
- <!--link rel="stylesheet" href="bootstrap-3.0.0-wip/css/bootstrap.css"-->
<link href="${template.formsPath}/lib/bootstrap/css/bootstrap.css" rel="stylesheet" />
<link href="${template.formsPath}/theme/${template.theme}/css/zocial/zocial.css" rel="stylesheet">
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/sprites.css">
- <link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/select2.css">
<!-- TODO remove external links -->
<link rel="stylesheet" href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,600,600italic,700,700italic,800,800italic'>
@@ -21,16 +20,12 @@
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/base.css">
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/forms.css">
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/header.css">
- <link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/tabs.css">
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/icons.css">
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/tables.css">
<!-- Page styles -->
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/admin-console.css">
- <script src="${template.formsPath}/lib/jquery/jquery-2.0.3.min.js"></script>
- <script src="${template.formsPath}/lib/bootstrap/js/bootstrap.js"></script>
-
</head>
<body class="admin-console user ${bodyClass}">
@@ -54,6 +49,9 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="icon-user">Icon: user</span>
${user.firstName!''} ${user.lastName!''}</a>
</li>
+ <li>
+ <a href="${url.logoutUrl}">Logout</a>
+ </li>
</ul>
</div>
</div>
@@ -67,7 +65,7 @@
<li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li>
<li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
<#--<li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social Accounts</a></li>-->
- <li class="<#if active=='access'>active</#if>"><a href="${url.accessUrl}">Authorized Access</a></li>
+ <#--<li class="<#if active=='access'>active</#if>"><a href="${url.accessUrl}">Authorized Access</a></li>-->
</ul>
</div>
diff --git a/forms/src/main/resources/org/keycloak/forms/messages.properties b/forms/src/main/resources/org/keycloak/forms/messages.properties
index 03943dc..7c8b05f 100644
--- a/forms/src/main/resources/org/keycloak/forms/messages.properties
+++ b/forms/src/main/resources/org/keycloak/forms/messages.properties
@@ -31,6 +31,7 @@ missingLastName=Please specify last name
missingEmail=Please specify email
missingUsername=Please specify username
missingPassword=Please specify password
+notMatchPassword=Passwords don't match
missingTotp=Please specify authenticator code
invalidPasswordExisting=Invalid existing password
@@ -43,6 +44,12 @@ successTotpRemoved=Google authenticator removed.
usernameExists=Username already exists
error=A system error has occured, contact admin
+actionWarningHeader=Your account is not enabled.
+actionTotpWarning=You need to set up the Google Authenticator to activate your account.
+actionProfileWarning=You need to update your user profile to activate your account.
+actionPasswordWarning=You need to change your password to activate your account.
+actionEmailWarning=You need to verify your email address to activate your account.
+actionFollow=Please follow the steps below.
successHeader=Success!
errorHeader=Error!
@@ -54,4 +61,7 @@ emailUpdateHeader=Update password
emailSent=You should receive an email shortly with further instructions.
emailError=Invalid username or email.
emailErrorInfo=Please, fill in the fields again.
-emailInstruction=Enter your username and email address and we will send you instructions on how to create a new password.
\ No newline at end of file
+emailInstruction=Enter your username and email address and we will send you instructions on how to create a new password.
+
+accountUpdated=Your account has been updated
+accountPasswordUpdated=Your password has been updated
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 52db21f..b0af34c 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -11,4 +11,6 @@ public interface Constants {
String APPLICATION_ROLE = "KEYCLOAK_APPLICATION";
String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
String WILDCARD_ROLE = "*";
+
+ String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management";
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 8598ae7..7e33185 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -35,6 +35,14 @@ public interface UserModel {
void removeRequiredAction(RequiredAction action);
+ Set<String> getWebOrigins();
+
+ void setWebOrigins(Set<String> webOrigins);
+
+ void addWebOrigin(String webOrigin);
+
+ void removeWebOrigin(String webOrigin);
+
Set<String> getRedirectUris();
void setRedirectUris(Set<String> redirectUris);
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
index 98894ce..e44c92a 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
@@ -22,6 +22,7 @@ public class UserAdapter implements UserModel {
private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
private static final String REDIRECT_URIS = "redirectUris";
+ private static final String WEB_ORIGINS = "webOrigins";
protected User user;
protected IdentityManager idm;
@@ -162,6 +163,26 @@ public class UserAdapter implements UserModel {
}
@Override
+ public Set<String> getWebOrigins() {
+ return getAttributeSet(WEB_ORIGINS);
+ }
+
+ @Override
+ public void setWebOrigins(Set<String> webOrigins) {
+ setAttributeSet(WEB_ORIGINS, webOrigins);
+ }
+
+ @Override
+ public void addWebOrigin(String webOrigin) {
+ addToAttributeSet(WEB_ORIGINS, webOrigin);
+ }
+
+ @Override
+ public void removeWebOrigin(String webOrigin) {
+ removeFromAttributeSet(WEB_ORIGINS, webOrigin);
+ }
+
+ @Override
public boolean isTotp() {
Attribute<Boolean> a = user.getAttribute(KEYCLOAK_TOTP_ATTR);
return a != null ? a.getValue() : false;
pom.xml 5(+5 -0)
diff --git a/pom.xml b/pom.xml
index 61503c5..da29152 100755
--- a/pom.xml
+++ b/pom.xml
@@ -193,6 +193,11 @@
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.17</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
services/pom.xml 4(+4 -0)
diff --git a/services/pom.xml b/services/pom.xml
index 187d7be..52d2ab8 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -166,6 +166,10 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>com.google.zxing</groupId>
+ <artifactId>javase</artifactId>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/services/src/main/java/org/keycloak/services/FormService.java b/services/src/main/java/org/keycloak/services/FormService.java
index 6e58138..70e4c0f 100755
--- a/services/src/main/java/org/keycloak/services/FormService.java
+++ b/services/src/main/java/org/keycloak/services/FormService.java
@@ -44,9 +44,9 @@ public interface FormService {
private RealmModel realm;
private UserModel userModel;
- private String error;
+ private String message;
- private FormFlows.ErrorType errorType;
+ private FormFlows.MessageType messageType;
private MultivaluedMap<String, String> formData;
private URI baseURI;
@@ -81,11 +81,11 @@ public interface FormService {
private String contextPath;
- public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, String error){
+ public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, String message){
this.realm = realm;
this.userModel = userModel;
this.formData = formData;
- this.error = error;
+ this.message = message;
}
public URI getBaseURI() {
@@ -96,12 +96,12 @@ public interface FormService {
this.baseURI = baseURI;
}
- public String getError() {
- return error;
+ public String getMessage() {
+ return message;
}
- public void setError(String error) {
- this.error = error;
+ public void setMessage(String message) {
+ this.message = message;
}
public MultivaluedMap<String, String> getFormData() {
@@ -128,12 +128,12 @@ public interface FormService {
this.userModel = userModel;
}
- public FormFlows.ErrorType getErrorType() {
- return errorType;
+ public FormFlows.MessageType getMessageType() {
+ return messageType;
}
- public void setErrorType(FormFlows.ErrorType errorType) {
- this.errorType = errorType;
+ public void setMessageType(FormFlows.MessageType messageType) {
+ this.messageType = messageType;
}
/* OAuth Part */
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index 93c2959..9b89cf4 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -42,6 +42,11 @@ public class ApplicationManager {
resourceUser.addRedirectUri(redirectUri);
}
}
+ if (resourceRep.getWebOrigins() != null) {
+ for (String webOrigin : resourceRep.getWebOrigins()) {
+ resourceUser.addWebOrigin(webOrigin);
+ }
+ }
realm.grantRole(resourceUser, loginRole);
@@ -97,6 +102,11 @@ public class ApplicationManager {
if (redirectUris != null) {
resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
}
+
+ List<String> webOrigins = rep.getWebOrigins();
+ if (webOrigins != null) {
+ resource.getApplicationUser().setWebOrigins(new HashSet<String>(webOrigins));
+ }
}
public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) {
@@ -113,6 +123,11 @@ public class ApplicationManager {
rep.setRedirectUris(new LinkedList<String>(redirectUris));
}
+ Set<String> webOrigins = applicationModel.getApplicationUser().getWebOrigins();
+ if (webOrigins != null) {
+ rep.setWebOrigins(new LinkedList<String>(webOrigins));
+ }
+
return rep;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 925076a..1797bc0 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -13,6 +13,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserModel;
+import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.SaasService;
@@ -61,6 +62,11 @@ public class AuthenticationManager {
return createLoginCookie(realm, user, cookieName, cookiePath);
}
+ public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, URI uri) {
+ String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
+ String cookiePath = uri.getPath();
+ return createLoginCookie(realm, user, cookieName, cookiePath);
+ }
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
@@ -99,6 +105,11 @@ public class AuthenticationManager {
expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
}
+ public void expireAccountIdentityCookie(URI uri) {
+ String cookiePath = uri.getPath();
+ expireCookie(AccountService.ACCOUNT_IDENTITY_COOKIE, cookiePath);
+ }
+
public void expireCookie(String cookieName, String path) {
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
if (response == null) {
@@ -120,6 +131,11 @@ public class AuthenticationManager {
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
}
+ public UserModel authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
+ String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
+ return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
+ }
+
public UserModel authenticateSaasIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user != null) return user;
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index af6d4db..bae0cc2 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -90,6 +90,36 @@ public class RealmManager {
if (rep.getDefaultRoles() != null) {
realm.updateDefaultRoles(rep.getDefaultRoles());
}
+
+ if (rep.isAccountManagement()) {
+ enableAccountManagement(realm);
+ } else {
+ disableAccountManagement(realm);
+ }
+ }
+
+ private void enableAccountManagement(RealmModel realm) {
+ ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ if (application == null) {
+ application = realm.addApplication(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+
+ UserCredentialModel password = new UserCredentialModel();
+ password.setType(UserCredentialModel.PASSWORD);
+ password.setValue(UUID.randomUUID().toString()); // just a random password as we'll never access it
+
+ realm.updateCredential(application.getApplicationUser(), password);
+
+ RoleModel applicationRole = realm.getRole(Constants.APPLICATION_ROLE);
+ realm.grantRole(application.getApplicationUser(), applicationRole);
+ }
+ application.setEnabled(true);
+ }
+
+ private void disableAccountManagement(RealmModel realm) {
+ ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ if (application != null) {
+ application.setEnabled(false); // TODO Should we delete the application instead?
+ }
}
public RealmModel importRealm(RealmRepresentation rep, UserModel realmCreator) {
@@ -214,6 +244,10 @@ public class RealmManager {
}
}
}
+
+ if (rep.isAccountManagement() != null && rep.isAccountManagement()) {
+ enableAccountManagement(newRealm);
+ }
}
public void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
@@ -370,6 +404,9 @@ public class RealmManager {
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
+ ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
+
List<RoleModel> defaultRoles = realm.getDefaultRoles();
if (defaultRoles.size() > 0) {
String[] d = new String[defaultRoles.size()];
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index c3de829..96d0c10 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -37,16 +37,20 @@ public class ResourceAdminManager {
}
protected boolean logoutResource(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
- LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
- String token = new TokenManager().encodeToken(realm, adminAction);
- Form form = new Form();
- form.param("token", token);
String managementUrl = resource.getManagementUrl();
- logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
- Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
- boolean success = response.getStatus() == 204;
- response.close();
- return success;
+ if (managementUrl != null) {
+ LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
+ String token = new TokenManager().encodeToken(realm, adminAction);
+ Form form = new Form();
+ form.param("token", token);
+ logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
+ Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
+ boolean success = response.getStatus() == 204;
+ response.close();
+ return success;
+ } else {
+ return false;
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 3b7a760..2c659fb 100644
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -44,6 +44,8 @@ public class Messages {
public static final String MISSING_PASSWORD = "missingPassword";
+ public static final String NOTMATCH_PASSWORD = "notMatchPassword";
+
public static final String MISSING_USERNAME = "missingUsername";
public static final String MISSING_TOTP = "missingTotp";
@@ -52,6 +54,14 @@ public class Messages {
public static final String USERNAME_EXISTS = "usernameExists";
+ public static final String ACTION_WARN_TOTP = "actionTotpWarning";
+
+ public static final String ACTION_WARN_PROFILE = "actionProfileWarning";
+
+ public static final String ACTION_WARN_PASSWD = "actionPasswordWarning";
+
+ public static final String ACTION_WARN_EMAIL = "actionEmailWarning";
+
public static final String ERROR = "error";
}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 7f54e52..8857271 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -21,37 +21,35 @@
*/
package org.keycloak.services.resources;
+import java.net.URI;
import java.util.HashSet;
import java.util.Set;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
+import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.jose.jws.JWSInput;
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
+import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.AbstractOAuthClient;
+import org.keycloak.jaxrs.JaxrsOAuthClient;
+import org.keycloak.models.*;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.email.EmailSender;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.FormFlows;
+import org.keycloak.services.resources.flows.Pages;
+import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
import org.picketlink.idm.credential.util.TimeBasedOTP;
@@ -60,6 +58,10 @@ import org.picketlink.idm.credential.util.TimeBasedOTP;
*/
public class AccountService {
+ private static final Logger logger = Logger.getLogger(AccountService.class);
+
+ public static final String ACCOUNT_IDENTITY_COOKIE = "KEYCLOAK_ACCOUNT_IDENTITY";
+
private RealmModel realm;
@Context
@@ -72,50 +74,85 @@ public class AccountService {
private UriInfo uriInfo;
@Context
- protected Providers providers;
+ private Providers providers;
+
+ private AuthenticationManager authManager = new AuthenticationManager();
- protected AuthenticationManager authManager = new AuthenticationManager();
+ private ApplicationModel application;
private TokenManager tokenManager;
- public AccountService(RealmModel realm, TokenManager tokenManager) {
+ public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager) {
this.realm = realm;
+ this.application = application;
this.tokenManager = tokenManager;
}
- @Path("access")
- @GET
- public Response accessPage() {
- UserModel user = getUserFromAuthManager();
+ private Response forwardToPage(String path, String template) {
+ UserModel user = getUser(false);
if (user != null) {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccess();
+ return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template);
} else {
- return Response.status(Status.FORBIDDEN).build();
+ return login(path);
}
}
@Path("")
+ @GET
+ public Response accountPage() {
+ return forwardToPage(null, Pages.ACCOUNT);
+ }
+
+ @Path("social")
+ @GET
+ public Response socialPage() {
+ return forwardToPage("social", Pages.SOCIAL);
+ }
+
+ @Path("totp")
+ @GET
+ public Response totpPage() {
+ return forwardToPage("totp", Pages.TOTP);
+ }
+
+ @Path("password")
+ @GET
+ public Response passwordPage() {
+ return forwardToPage("password", Pages.PASSWORD);
+ }
+
+ @Path("access")
+ @GET
+ public Response accessPage() {
+ return forwardToPage("access", Pages.ACCESS);
+ }
+
+ @Path("")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
- UserModel user = getUserFromAuthManager();
- if (user == null) {
- return Response.status(Status.FORBIDDEN).build();
+
+ UserModel user = getUser(true);
+
+ String error = Validation.validateUpdateProfileForm(formData);
+ if (error != null) {
+ return Flows.forms(realm, request, uriInfo).setUser(user).setError(error).forwardToAccount();
}
user.setFirstName(formData.getFirst("firstName"));
user.setLastName(formData.getFirst("lastName"));
user.setEmail(formData.getFirst("email"));
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
+ return Flows.forms(realm, request, uriInfo).setUser(user).setError("accountUpdated")
+ .setErrorType(FormFlows.MessageType.SUCCESS).forwardToAccount();
}
@Path("totp-remove")
@GET
public Response processTotpRemove() {
- UserModel user = getUserFromAuthManager();
+ UserModel user = getUser(true);
user.setTotp(false);
- return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.ErrorType.SUCCESS)
+ return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS)
.setUser(user).forwardToTotp();
}
@@ -123,36 +160,26 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
- UserModel user = getUserFromAuthManager();
- if (user == null) {
- return Response.status(Status.FORBIDDEN).build();
- }
-
- FormFlows forms = Flows.forms(realm, request, uriInfo);
+ UserModel user = getUser(true);
String totp = formData.getFirst("totp");
String totpSecret = formData.getFirst("totpSecret");
- String error = null;
-
+ FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
if (Validation.isEmpty(totp)) {
- error = Messages.MISSING_TOTP;
+ return forms.setError(Messages.MISSING_TOTP).forwardToTotp();
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
- error = Messages.INVALID_TOTP;
- }
-
- if (error != null) {
- return forms.setError(error).setUser(user).forwardToTotp();
+ return forms.setError(Messages.INVALID_TOTP).forwardToTotp();
}
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.TOTP);
- credentials.setValue(formData.getFirst("totpSecret"));
+ credentials.setValue(totpSecret);
realm.updateCredential(user, credentials);
user.setTotp(true);
- return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.ErrorType.SUCCESS)
+ return Flows.forms(realm, request, uriInfo).setError("successTotp").setErrorType(FormFlows.MessageType.SUCCESS)
.setUser(user).forwardToTotp();
}
@@ -160,10 +187,7 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
- UserModel user = getUserFromAuthManager();
- if (user == null) {
- return Response.status(Status.FORBIDDEN).build();
- }
+ UserModel user = getUser(true);
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
@@ -172,71 +196,133 @@ public class AccountService {
String passwordConfirm = formData.getFirst("password-confirm");
if (Validation.isEmpty(passwordNew)) {
- forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+ return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
} else if (!passwordNew.equals(passwordConfirm)) {
- forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
+ return forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
}
if (Validation.isEmpty(password)) {
- forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
+ return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
} else if (!realm.validatePassword(user, password)) {
- forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
+ return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword();
}
-
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.PASSWORD);
credentials.setValue(passwordNew);
realm.updateCredential(user, credentials);
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+ return Flows.forms(realm, request, uriInfo).setUser(user).setError("accountPasswordUpdated")
+ .setErrorType(FormFlows.MessageType.SUCCESS).forwardToPassword();
}
- @Path("")
+ @Path("login-redirect")
@GET
- public Response accountPage() {
- UserModel user = getUserFromAuthManager();
- if (user != null) {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
- } else {
- return Response.status(Status.FORBIDDEN).build();
+ public Response loginRedirect(@QueryParam("code") String code,
+ @QueryParam("state") String state,
+ @QueryParam("error") String error,
+ @Context HttpHeaders headers) {
+ try {
+ if (error != null) {
+ logger.debug("error from oauth");
+ throw new ForbiddenException("error");
+ }
+ if (!realm.isEnabled()) {
+ logger.debug("realm not enabled");
+ throw new ForbiddenException();
+ }
+ UserModel client = application.getApplicationUser();
+ if (!client.isEnabled() || !application.isEnabled()) {
+ logger.debug("account management app not enabled");
+ throw new ForbiddenException();
+ }
+ if (code == null) {
+ logger.debug("code not specified");
+ throw new BadRequestException();
+ }
+ if (state == null) {
+ logger.debug("state not specified");
+ throw new BadRequestException();
+ }
+ String path = new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
+
+ JWSInput input = new JWSInput(code, providers);
+ boolean verifiedCode = false;
+ try {
+ verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
+ } catch (Exception ignored) {
+ logger.debug("Failed to verify signature", ignored);
+ }
+ if (!verifiedCode) {
+ logger.debug("unverified access code");
+ throw new BadRequestException();
+ }
+ String key = input.readContent(String.class);
+ AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
+ if (accessCode == null) {
+ logger.debug("bad access code");
+ throw new BadRequestException();
+ }
+ if (accessCode.isExpired()) {
+ logger.debug("access code expired");
+ throw new BadRequestException();
+ }
+ if (!accessCode.getToken().isActive()) {
+ logger.debug("access token expired");
+ throw new BadRequestException();
+ }
+ if (!accessCode.getRealm().getId().equals(realm.getId())) {
+ logger.debug("bad realm");
+ throw new BadRequestException();
+
+ }
+ if (!client.getLoginName().equals(accessCode.getClient().getLoginName())) {
+ logger.debug("bad client");
+ throw new BadRequestException();
+ }
+
+ UriBuilder redirectBuilder = Urls.accountBase(uriInfo.getBaseUri());
+ if (path != null) {
+ redirectBuilder.path(path);
+ }
+ URI redirectUri = redirectBuilder.build(realm.getId());
+
+ NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
+ return Response.status(302).cookie(cookie).location(redirectUri).build();
+ } finally {
+ authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
}
}
- @Path("social")
+ @Path("logout")
@GET
- public Response socialPage() {
- UserModel user = getUserFromAuthManager();
- if (user != null) {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToSocial();
- } else {
- return Response.status(Status.FORBIDDEN).build();
- }
+ public Response logout() {
+ // TODO Should use single-sign out via TokenService
+ URI baseUri = Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId());
+ authManager.expireIdentityCookie(realm, uriInfo);
+ authManager.expireAccountIdentityCookie(baseUri);
+ return Response.status(302).location(baseUri).build();
}
- @Path("totp")
- @GET
- public Response totpPage() {
- UserModel user = getUserFromAuthManager();
- if (user != null) {
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
- } else {
- return Response.status(Status.FORBIDDEN).build();
- }
- }
+ private Response login(String path) {
+ JaxrsOAuthClient oauth = new JaxrsOAuthClient();
+ String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getId()).toString();
+ oauth.setAuthUrl(authUrl);
- @Path("password")
- @GET
- public Response passwordPage() {
- UserModel user = getUserFromAuthManager();
- if (user == null) {
- return Response.status(Status.FORBIDDEN).build();
- }
- return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
+ oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+
+ URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId());
+
+ oauth.setStateCookiePath(accountUri.getPath());
+ return oauth.redirect(uriInfo, accountUri.toString(), path);
}
- private UserModel getUserFromAuthManager() {
- return authManager.authenticateIdentityCookie(realm, uriInfo, headers);
+ private UserModel getUser(boolean required) {
+ UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers);
+ if (user == null && required) {
+ throw new ForbiddenException();
+ }
+ return user;
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/Cors.java b/services/src/main/java/org/keycloak/services/resources/Cors.java
new file mode 100644
index 0000000..8c28d6f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/Cors.java
@@ -0,0 +1,43 @@
+package org.keycloak.services.resources;
+
+import java.util.Set;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.jboss.resteasy.spi.HttpRequest;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Cors {
+
+ private HttpRequest request;
+ private ResponseBuilder response;
+ private Set<String> allowedOrigins;
+
+ public Cors(HttpRequest request, ResponseBuilder response) {
+ this.request = request;
+ this.response = response;
+ }
+
+ public static Cors add(HttpRequest request, ResponseBuilder response) {
+ return new Cors(request, response);
+ }
+
+ public Cors allowedOrigins(Set<String> allowedOrigins) {
+ this.allowedOrigins = allowedOrigins;
+ return this;
+ }
+
+ public Response build() {
+ String origin = request.getHttpHeaders().getHeaderString("Origin");
+ if (origin == null || allowedOrigins == null || (!allowedOrigins.contains(origin))) {
+ return response.build();
+ }
+
+ response.header("Access-Control-Allow-Origin", origin);
+ return response.build();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
index c4fe4a6..fe924fb 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java
@@ -34,6 +34,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.services.messages.Messages;
import org.picketlink.idm.model.sample.Realm;
import javax.imageio.spi.ServiceRegistry;
@@ -58,8 +59,8 @@ public class FormFlows {
// TODO refactor/rename "error" to "message" everywhere where it makes sense
private String error;
- public static enum ErrorType {SUCCESS, WARNING, ERROR};
- private ErrorType errorType;
+ public static enum MessageType {SUCCESS, WARNING, ERROR};
+ private MessageType messageType = MessageType.ERROR;
private MultivaluedMap<String, String> formData;
@@ -79,16 +80,17 @@ public class FormFlows {
}
public Response forwardToAction(RequiredAction action) {
+
switch (action) {
case CONFIGURE_TOTP:
- return forwardToForm(Pages.LOGIN_CONFIG_TOTP);
+ return forwardToActionForm(Pages.LOGIN_CONFIG_TOTP, Messages.ACTION_WARN_TOTP);
case UPDATE_PROFILE:
- return forwardToForm(Pages.LOGIN_UPDATE_PROFILE);
+ return forwardToActionForm(Pages.LOGIN_UPDATE_PROFILE, Messages.ACTION_WARN_PROFILE);
case UPDATE_PASSWORD:
- return forwardToForm(Pages.LOGIN_UPDATE_PASSWORD);
+ return forwardToActionForm(Pages.LOGIN_UPDATE_PASSWORD, Messages.ACTION_WARN_PASSWD);
case VERIFY_EMAIL:
new EmailSender().sendEmailVerification(userModel, realm, accessCode, uriInfo);
- return forwardToForm(Pages.LOGIN_VERIFY_EMAIL);
+ return forwardToActionForm(Pages.LOGIN_VERIFY_EMAIL, Messages.ACTION_WARN_EMAIL);
default:
return Response.serverError().build();
}
@@ -103,7 +105,6 @@ public class FormFlows {
}
private Response forwardToForm(String template, FormService.FormServiceDataBean formDataBean) {
- formDataBean.setErrorType(errorType == null ? ErrorType.ERROR : errorType);
// Getting URI needed by form processing service
ResteasyUriInfo uriInfo = request.getUri();
@@ -140,11 +141,24 @@ public class FormFlows {
return Response.status(200).entity("form provider not found").build();
}
- private Response forwardToForm(String template) {
+ public Response forwardToForm(String template) {
FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error);
+ formDataBean.setMessageType(messageType);
+
return forwardToForm(template, formDataBean);
+ }
+
+ private Response forwardToActionForm(String template, String warningSummary) {
+
+ // If no other message is set, notify user about required action in the warning window
+ // so it's clear that this is a req. action form not a login form
+ if (error == null){
+ messageType = MessageType.WARNING;
+ error = warningSummary;
+ }
+ return forwardToForm(template);
}
public Response forwardToLogin() {
@@ -202,8 +216,8 @@ public class FormFlows {
return this;
}
- public FormFlows setErrorType(ErrorType errorType) {
- this.errorType = errorType;
+ public FormFlows setErrorType(MessageType errorType) {
+ this.messageType = errorType;
return this;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 200b746..b9f457a 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -35,12 +35,16 @@ public class Urls {
return accountBase(baseUri).path(AccountService.class, "accessPage").build(realmId);
}
- private static UriBuilder accountBase(URI baseUri) {
+ public static UriBuilder accountBase(URI baseUri) {
return realmBase(baseUri).path(RealmsResource.class, "getAccountService");
}
public static URI accountPage(URI baseUri, String realmId) {
- return accountBase(baseUri).path(AccountService.class, "accountPage").build(realmId);
+ return accountPageBuilder(baseUri).build(realmId);
+ }
+
+ public static UriBuilder accountPageBuilder(URI baseUri) {
+ return accountBase(baseUri).path(AccountService.class, "accountPage");
}
public static URI accountPasswordPage(URI baseUri, String realmId) {
@@ -59,6 +63,10 @@ public class Urls {
return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId);
}
+ public static URI accountLogout(URI baseUri, String realmId) {
+ return accountBase(baseUri).path(AccountService.class, "logout").build(realmId);
+ }
+
public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 7095eab..fba3aa9 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -50,6 +50,7 @@ public class KeycloakApplication extends Application {
singletons.add(new SaasService(tokenManager));
singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
classes.add(SkeletonKeyContextResolver.class);
+ classes.add(QRCodeResource.class);
}
protected KeycloakSessionFactory createSessionFactory() {
diff --git a/services/src/main/java/org/keycloak/services/resources/QRCodeResource.java b/services/src/main/java/org/keycloak/services/resources/QRCodeResource.java
new file mode 100644
index 0000000..9d02111
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/QRCodeResource.java
@@ -0,0 +1,52 @@
+package org.keycloak.services.resources;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+
+import javax.servlet.ServletException;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Path("/qrcode")
+public class QRCodeResource {
+
+ @GET
+ @Produces("image/png")
+ public Response createQrCode(@QueryParam("contents") String contents, @QueryParam("size") String size) throws ServletException, IOException, WriterException {
+ int width = 256;
+ int height = 256;
+
+ if (size != null) {
+ String[] s = size.split("x");
+ width = Integer.parseInt(s[0]);
+ height = Integer.parseInt(s[1]);
+ }
+
+ if (contents == null) {
+ return Response.status(Response.Status.BAD_REQUEST).build();
+ }
+
+ QRCodeWriter writer = new QRCodeWriter();
+ final BitMatrix bitMatrix = writer.encode(contents, BarcodeFormat.QR_CODE, width, height);
+
+ StreamingOutput stream = new StreamingOutput() {
+ @Override
+ public void write(OutputStream os) throws IOException,
+ WebApplicationException {
+ MatrixToImageWriter.writeToStream(bitMatrix, "png", os);
+ }
+ };
+
+ return Response.ok(stream).build();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 9914622..54fd5a5 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -1,6 +1,8 @@
package org.keycloak.services.resources;
import org.jboss.resteasy.logging.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.Constants;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.models.KeycloakSession;
@@ -66,7 +68,14 @@ public class RealmsResource {
logger.debug("realm not found");
throw new NotFoundException();
}
- AccountService accountService = new AccountService(realm, tokenManager);
+
+ ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
+ if (application == null || !application.isEnabled()) {
+ logger.debug("account management not enabled");
+ throw new NotFoundException();
+ }
+
+ AccountService accountService = new AccountService(realm, application, tokenManager);
resourceContext.initResource(accountService);
return accountService;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index 1517b79..eeed370 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -88,6 +88,12 @@ public class RequiredActionsService {
}
UserModel user = getUser(accessCode);
+
+ String error = Validation.validateUpdateProfileForm(formData);
+ if (error != null) {
+ return Flows.forms(realm, request, uriInfo).setError(error).forwardToAction(RequiredAction.UPDATE_PROFILE);
+ }
+
user.setFirstName(formData.getFirst("firstName"));
user.setLastName(formData.getFirst("lastName"));
user.setEmail(formData.getFirst("email"));
@@ -121,7 +127,7 @@ public class RequiredActionsService {
UserCredentialModel credentials = new UserCredentialModel();
credentials.setType(CredentialRepresentation.TOTP);
- credentials.setValue(formData.getFirst("totpSecret"));
+ credentials.setValue(totpSecret);
realm.updateCredential(user, credentials);
user.setTotp(true);
@@ -146,15 +152,14 @@ public class RequiredActionsService {
UserModel user = getUser(accessCode);
- String password = formData.getFirst("password");
String passwordNew = formData.getFirst("password-new");
String passwordConfirm = formData.getFirst("password-confirm");
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
if (Validation.isEmpty(passwordNew)) {
- forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+ return forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
} else if (!passwordNew.equals(passwordConfirm)) {
- forms.setError(Messages.MISSING_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
+ return forms.setError(Messages.NOTMATCH_PASSWORD).forwardToAction(RequiredAction.UPDATE_PASSWORD);
}
UserCredentialModel credentials = new UserCredentialModel();
@@ -257,7 +262,7 @@ public class RequiredActionsService {
new EmailSender().sendPasswordReset(user, realm, accessCode, uriInfo);
- return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.ErrorType.SUCCESS)
+ return Flows.forms(realm, request, uriInfo).setError("emailSent").setErrorType(FormFlows.MessageType.SUCCESS)
.forwardToPasswordReset();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index d72e80d..7296d8e 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -415,7 +415,8 @@ public class TokenService {
}
logger.info("accessRequest SUCCESS");
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
- return Response.ok(res).build();
+
+ return Cors.add(request, Response.ok(res)).allowedOrigins(client.getWebOrigins()).build();
}
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index 5a71f0d..c652849 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -38,6 +38,22 @@ public class Validation {
return null;
}
+ public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {
+ if (isEmpty(formData.getFirst("firstName"))) {
+ return Messages.MISSING_FIRST_NAME;
+ }
+
+ if (isEmpty(formData.getFirst("lastName"))) {
+ return Messages.MISSING_LAST_NAME;
+ }
+
+ if (isEmpty(formData.getFirst("email"))) {
+ return Messages.MISSING_EMAIL;
+ }
+
+ return null;
+ }
+
public static boolean isEmpty(String s) {
return s == null || s.length() == 0;
}
diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
index 4466079..57c3ba5 100644
--- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
+++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
@@ -1,23 +1,20 @@
package org.keycloak.test;
-import java.util.Iterator;
-import java.util.List;
-
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.*;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.services.managers.ApplicationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertNotNull;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -49,6 +46,9 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
application.getApplicationUser().addRedirectUri("redirect-1");
application.getApplicationUser().addRedirectUri("redirect-2");
+ application.getApplicationUser().addWebOrigin("origin-1");
+ application.getApplicationUser().addWebOrigin("origin-2");
+
application.updateApplication();
}
@@ -85,6 +85,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
UserModel euser = expected.getApplicationUser();
Assert.assertTrue(euser.getRedirectUris().containsAll(auser.getRedirectUris()));
+ Assert.assertTrue(euser.getWebOrigins().containsAll(auser.getWebOrigins()));
}
public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
diff --git a/services/src/test/java/org/keycloak/test/UserModelTest.java b/services/src/test/java/org/keycloak/test/UserModelTest.java
index 9922596..9029511 100644
--- a/services/src/test/java/org/keycloak/test/UserModelTest.java
+++ b/services/src/test/java/org/keycloak/test/UserModelTest.java
@@ -53,10 +53,33 @@ public class UserModelTest extends AbstractKeycloakServerTest {
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
+ user.addWebOrigin("origin-1");
+ user.addWebOrigin("origin-2");
+
UserModel persisted = manager.getRealm(realm.getId()).getUser("user");
assertEquals(user, persisted);
}
+
+ @Test
+ public void webOriginSetTest() {
+ RealmModel realm = manager.createRealm("original");
+ UserModel user = realm.addUser("user");
+
+ Assert.assertTrue(user.getWebOrigins().isEmpty());
+
+ user.addWebOrigin("origin-1");
+ Assert.assertEquals(1, user.getWebOrigins().size());
+
+ user.addWebOrigin("origin-2");
+ Assert.assertEquals(2, user.getWebOrigins().size());
+
+ user.removeWebOrigin("origin-2");
+ Assert.assertEquals(1, user.getWebOrigins().size());
+
+ user.removeWebOrigin("origin-1");
+ Assert.assertTrue(user.getWebOrigins().isEmpty());
+ }
@Test
public void testUserRequiredActions() throws Exception {
@@ -102,7 +125,7 @@ public class UserModelTest extends AbstractKeycloakServerTest {
Assert.assertEquals(expected.getLastName(), actual.getLastName());
Assert.assertArrayEquals(expected.getRedirectUris().toArray(), actual.getRedirectUris().toArray());
Assert.assertArrayEquals(expected.getRequiredActions().toArray(), actual.getRequiredActions().toArray());
-
+ Assert.assertArrayEquals(expected.getWebOrigins().toArray(), actual.getWebOrigins().toArray());
}
public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
testsuite/integration/pom.xml 4(+4 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 3614016..0e121c7 100644
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -102,6 +102,10 @@
<artifactId>picketlink-config</artifactId>
</dependency>
<dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<exclusions>
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
new file mode 100644
index 0000000..b3c1c5c
--- /dev/null
+++ b/testsuite/integration/src/main/resources/log4j.properties
@@ -0,0 +1,5 @@
+log4j.rootLogger=debug, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 08a2bfa..c567e99 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -44,8 +44,8 @@ import org.openqa.selenium.WebDriver;
*/
public class RequiredActionUpdateProfileTest {
- @ClassRule
- public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
+ @Rule
+ public KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
@@ -83,4 +83,50 @@ public class RequiredActionUpdateProfileTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
}
+ @Test
+ public void updateProfileMissingFirstName() {
+ loginPage.open();
+
+ loginPage.login("test-user@localhost", "password");
+
+ updateProfilePage.assertCurrent();
+
+ updateProfilePage.update("", "New last", "new@email.com");
+
+ updateProfilePage.assertCurrent();
+
+ Assert.assertEquals("Please specify first name", updateProfilePage.getError());
+ }
+
+ @Test
+ public void updateProfileMissingLastName() {
+ loginPage.open();
+
+ loginPage.login("test-user@localhost", "password");
+
+ updateProfilePage.assertCurrent();
+
+ updateProfilePage.update("New first", "", "new@email.com");
+
+ updateProfilePage.assertCurrent();
+
+ Assert.assertEquals("Please specify last name", updateProfilePage.getError());
+ }
+
+ @Test
+ public void updateProfileMissingEmail() {
+ loginPage.open();
+
+ loginPage.login("test-user@localhost", "password");
+
+ updateProfilePage.assertCurrent();
+
+ updateProfilePage.update("New first", "New last", "");
+
+ updateProfilePage.assertCurrent();
+
+ Assert.assertEquals("Please specify email", updateProfilePage.getError());
+ }
+
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
index 3d62d55..5b1a827 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java
@@ -21,11 +21,7 @@
*/
package org.keycloak.testsuite.forms;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.*;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.models.RealmModel;
@@ -43,6 +39,8 @@ import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
import org.picketlink.idm.credential.util.TimeBasedOTP;
/**
@@ -97,13 +95,22 @@ public class AccountTest {
@Test
public void changePassword() {
- loginPage.open();
+ changePasswordPage.open();
loginPage.login("test-user@localhost", "password");
- changePasswordPage.open();
+ changePasswordPage.changePassword("", "new-password", "new-password");
+
+ Assert.assertTrue(profilePage.isError());
+
+ changePasswordPage.changePassword("password", "new-password", "new-password2");
+
+ Assert.assertTrue(profilePage.isError());
+
changePasswordPage.changePassword("password", "new-password", "new-password");
- oauth.openLogout();
+ Assert.assertTrue(profilePage.isSuccess());
+
+ changePasswordPage.logout();
loginPage.open();
loginPage.login("test-user@localhost", "password");
@@ -118,17 +125,38 @@ public class AccountTest {
@Test
public void changeProfile() {
- loginPage.open();
+ profilePage.open();
loginPage.login("test-user@localhost", "password");
- profilePage.open();
+ Assert.assertEquals("", profilePage.getFirstName());
+ Assert.assertEquals("", profilePage.getLastName());
+ Assert.assertEquals("test-user@localhost", profilePage.getEmail());
+
+ // All fields are required, so there should be an error when something is missing.
+ profilePage.updateProfile("", "New last", "new@email.com");
+
+ Assert.assertTrue(profilePage.isError());
+ Assert.assertEquals("", profilePage.getFirstName());
+ Assert.assertEquals("", profilePage.getLastName());
+ Assert.assertEquals("test-user@localhost", profilePage.getEmail());
+
+ profilePage.updateProfile("New first", "", "new@email.com");
+
+ Assert.assertTrue(profilePage.isError());
+ Assert.assertEquals("", profilePage.getFirstName());
+ Assert.assertEquals("", profilePage.getLastName());
+ Assert.assertEquals("test-user@localhost", profilePage.getEmail());
+ profilePage.updateProfile("New first", "New last", "");
+
+ Assert.assertTrue(profilePage.isError());
Assert.assertEquals("", profilePage.getFirstName());
Assert.assertEquals("", profilePage.getLastName());
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
profilePage.updateProfile("New first", "New last", "new@email.com");
+ Assert.assertTrue(profilePage.isSuccess());
Assert.assertEquals("New first", profilePage.getFirstName());
Assert.assertEquals("New last", profilePage.getLastName());
Assert.assertEquals("new@email.com", profilePage.getEmail());
@@ -136,17 +164,22 @@ public class AccountTest {
@Test
public void setupTotp() {
- loginPage.open();
- loginPage.login("test-user@localhost", "password");
-
totpPage.open();
+ loginPage.login("test-user@localhost", "password");
Assert.assertTrue(totpPage.isCurrent());
Assert.assertFalse(driver.getPageSource().contains("Remove Google"));
+ // Error with false code
+ totpPage.configure(totp.generate(totpPage.getTotpSecret()+"123"));
+
+ Assert.assertTrue(profilePage.isError());
+
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
+ Assert.assertTrue(profilePage.isSuccess());
+
Assert.assertTrue(driver.getPageSource().contains("Remove Google"));
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 9b5ca0f..396f3d9 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -123,7 +123,7 @@ public class ResetPasswordTest {
resetPasswordPage.assertCurrent();
Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
- Assert.assertEquals("Error!", resetPasswordPage.getMessage());
+ Assert.assertEquals("Invalid username or email.", resetPasswordPage.getMessage());
}
@Test
@@ -138,7 +138,7 @@ public class ResetPasswordTest {
resetPasswordPage.assertCurrent();
Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
- Assert.assertEquals("Error!", resetPasswordPage.getMessage());
+ Assert.assertEquals("Invalid username or email.", resetPasswordPage.getMessage());
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index b53e88f..0a94649 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class AccountPasswordPage extends Page {
+public class AccountPasswordPage extends AbstractAccountPage {
private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/password";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
index 11cc502..171e6ef 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class AccountTotpPage extends Page {
+public class AccountTotpPage extends AbstractAccountPage {
private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/totp";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index c890f2f..a9f6495 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class AccountUpdateProfilePage extends Page {
+public class AccountUpdateProfilePage extends AbstractAccountPage {
private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account";
@@ -44,6 +44,9 @@ public class AccountUpdateProfilePage extends Page {
@FindBy(css = "button[type=\"submit\"]")
private WebElement submitButton;
+ @FindBy(css = ".feedback > p > strong")
+ private WebElement feedbackMessage;
+
public void updateProfile(String firstName, String lastName, String email) {
firstNameInput.clear();
firstNameInput.sendKeys(firstName);
@@ -75,4 +78,11 @@ public class AccountUpdateProfilePage extends Page {
driver.navigate().to(PATH);
}
+ public boolean isSuccess(){
+ return feedbackMessage != null && "Success!".equals(feedbackMessage.getText());
+ }
+
+ public boolean isError(){
+ return feedbackMessage != null && "Error!".equals(feedbackMessage.getText());
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
index 4270640..3faa197 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java
@@ -25,7 +25,7 @@ package org.keycloak.testsuite.pages;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class AppPage extends Page {
+public class AppPage extends AbstractPage {
private String baseUrl = "http://localhost:8081/app";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
index bfe3201..cc06da5 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -23,14 +23,13 @@ package org.keycloak.testsuite.pages;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.rule.WebResource;
-import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class ErrorPage extends Page {
+public class ErrorPage extends AbstractPage {
@WebResource
protected OAuthClient oauth;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java
index f2e2300..5b1613a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class LoginConfigTotpPage extends Page {
+public class LoginConfigTotpPage extends AbstractPage {
@FindBy(id = "totpSecret")
private WebElement totpSecret;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index d099690..28e87cf 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class LoginPage extends Page {
+public class LoginPage extends AbstractPage {
@WebResource
protected OAuthClient oauth;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
index 45fc30a..fc62c3c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class LoginPasswordResetPage extends Page {
+public class LoginPasswordResetPage extends AbstractPage {
@FindBy(id = "username")
private WebElement usernameInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
index 100b891..be5df8c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class LoginPasswordUpdatePage extends Page {
+public class LoginPasswordUpdatePage extends AbstractPage {
@FindBy(id = "password-new")
private WebElement newPasswordInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
index 67b3801..2a7ed57 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java
@@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class LoginTotpPage extends Page {
+public class LoginTotpPage extends AbstractPage {
@FindBy(id = "totp")
private WebElement totpInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
index 9a4b454..d25959d 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class LoginUpdateProfilePage extends Page {
+public class LoginUpdateProfilePage extends AbstractPage {
@FindBy(id = "firstName")
private WebElement firstNameInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
index 8749a91..7fdbe06 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class OAuthGrantPage extends Page {
+public class OAuthGrantPage extends AbstractPage {
@FindBy(css = "input[name=\"accept\"]")
private WebElement acceptButton;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index 7ff4d7f..c1b52c5 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class RegisterPage extends Page {
+public class RegisterPage extends AbstractPage {
@FindBy(id = "firstName")
private WebElement firstNameInput;
@@ -85,6 +85,22 @@ public class RegisterPage extends Page {
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
}
+ public String getFirstName() {
+ return firstNameInput.getAttribute("value");
+ }
+
+ public String getLastName() {
+ return lastNameInput.getAttribute("value");
+ }
+
+ public String getEmail() {
+ return emailInput.getAttribute("value");
+ }
+
+ public String getUsername() {
+ return usernameInput.getAttribute("value");
+ }
+
public boolean isCurrent() {
return driver.getTitle().equals("Register with test");
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
index 5e8c24c..cfcfbb4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java
@@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
*/
-public class VerifyEmailPage extends Page {
+public class VerifyEmailPage extends AbstractPage {
@WebResource
protected OAuthClient oauth;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
index d9f789d..88301c5 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java
@@ -25,7 +25,7 @@ import java.lang.reflect.Field;
import org.junit.rules.ExternalResource;
import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.Page;
+import org.keycloak.testsuite.pages.AbstractPage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
@@ -78,7 +78,7 @@ public class WebRule extends ExternalResource {
Class<?> type = f.getType();
if (type.equals(WebDriver.class)) {
set(f, o, driver);
- } else if (Page.class.isAssignableFrom(type)) {
+ } else if (AbstractPage.class.isAssignableFrom(type)) {
set(f, o, getPage(f.getType()));
} else if (type.equals(OAuthClient.class)) {
set(f, o, oauth);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
index f4cc01c..056af5c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
@@ -35,6 +35,7 @@ import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
@@ -69,6 +70,9 @@ public class SocialLoginTest {
protected LoginPage loginPage;
@WebResource
+ protected RegisterPage registerPage;
+
+ @WebResource
protected OAuthClient oauth;
@BeforeClass
@@ -97,4 +101,41 @@ public class SocialLoginTest {
Assert.assertTrue(token.getRealmAccess().isUserInRole("user"));
}
+ @Test
+ public void registerRequired() {
+ keycloakRule.configure(new KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setAutomaticRegistrationAfterSocialLogin(false);
+ }
+ });
+
+ try {
+ loginPage.open();
+
+ loginPage.clickSocial("dummy");
+
+ driver.findElement(By.id("username")).sendKeys("dummy-user-reg");
+ driver.findElement(By.id("submit")).click();
+
+ registerPage.isCurrent();
+
+ Assert.assertEquals("", registerPage.getFirstName());
+ Assert.assertEquals("", registerPage.getLastName());
+ Assert.assertEquals("dummy-user-reg@dummy-social", registerPage.getEmail());
+ Assert.assertEquals("dummy-user-reg", registerPage.getUsername());
+
+ registerPage.register("Dummy", "User", "dummy-user-reg@dummy-social", "dummy-user-reg", "password", "password");
+
+ Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ } finally {
+ keycloakRule.configure(new KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setAutomaticRegistrationAfterSocialLogin(true);
+ }
+ });
+ }
+ }
+
}
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index 941c5db..161f11e 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -8,6 +8,7 @@
"sslNotRequired": true,
"cookieLoginAllowed": true,
"registrationAllowed": true,
+ "accountManagement": true,
"resetPasswordAllowed": 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",