Merge remote-tracking branch 'upstream/master'
|
@ -42,6 +42,13 @@ input[type="password"].error:focus,
|
||||||
input[type="email"].error:focus {
|
input[type="email"].error:focus {
|
||||||
box-shadow: 0 0 5px #ba1212;
|
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"],
|
input[type="button"],
|
||||||
button,
|
button,
|
||||||
a.button {
|
a.button {
|
||||||
|
@ -776,3 +783,11 @@ input[type="email"].tiny {
|
||||||
.breadcrumb > li + li:before {
|
.breadcrumb > li + li:before {
|
||||||
content: "» ";
|
content: "» ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-deletable:hover .btn-delete {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -196,6 +196,20 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application,
|
||||||
}
|
}
|
||||||
}, true);
|
}, 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() {
|
$scope.save = function() {
|
||||||
if ($scope.applicationForm.$valid) {
|
if ($scope.applicationForm.$valid) {
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<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">
|
<div class="controls">
|
||||||
<input class="input-small" type="text" name="baseUrl" id="baseUrl"
|
<input class="input-small" type="text" name="baseUrl" id="baseUrl"
|
||||||
|
@ -65,6 +65,36 @@
|
||||||
data-ng-model="application.adminUrl">
|
data-ng-model="application.adminUrl">
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</fieldset>
|
||||||
<div class="form-actions" data-ng-show="create">
|
<div class="form-actions" data-ng-show="create">
|
||||||
<button type="submit" data-ng-click="save()" class="primary">Save
|
<button type="submit" data-ng-click="save()" class="primary">Save
|
||||||
|
|
|
@ -75,6 +75,20 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-group clearfix block">
|
||||||
<label for="requireSsl" class="control-label">Require SSL</label>
|
<label for="requireSsl" class="control-label">Require SSL</label>
|
||||||
<div class="onoffswitch">
|
<div class="onoffswitch">
|
||||||
|
|
|
@ -22,7 +22,14 @@ import java.net.URI;
|
||||||
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
|
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||||
|
return redirect(uriInfo, redirectUri, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response redirect(UriInfo uriInfo, String redirectUri, String path) {
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
|
if (path != null) {
|
||||||
|
state += "#" + path;
|
||||||
|
}
|
||||||
|
|
||||||
URI url = UriBuilder.fromUri(authUrl)
|
URI url = UriBuilder.fromUri(authUrl)
|
||||||
.queryParam("client_id", clientId)
|
.queryParam("client_id", clientId)
|
||||||
|
@ -58,7 +65,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
return uriInfo.getQueryParameters().getFirst("code");
|
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);
|
Cookie stateCookie = headers.getCookies().get(stateCookieName);
|
||||||
if (stateCookie == null) throw new BadRequestException("state cookie not set");
|
if (stateCookie == null) throw new BadRequestException("state cookie not set");
|
||||||
String state = uriInfo.getQueryParameters().getFirst("state");
|
String state = uriInfo.getQueryParameters().getFirst("state");
|
||||||
|
@ -66,5 +73,10 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
if (!state.equals(stateCookie.getValue())) {
|
if (!state.equals(stateCookie.getValue())) {
|
||||||
throw new BadRequestException("state parameter invalid");
|
throw new BadRequestException("state parameter invalid");
|
||||||
}
|
}
|
||||||
|
if (state.indexOf('#') != -1) {
|
||||||
|
return state.substring(state.indexOf('#') + 1);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class ApplicationRepresentation {
|
||||||
protected List<UserRoleMappingRepresentation> roleMappings;
|
protected List<UserRoleMappingRepresentation> roleMappings;
|
||||||
protected List<ScopeMappingRepresentation> scopeMappings;
|
protected List<ScopeMappingRepresentation> scopeMappings;
|
||||||
protected List<String> redirectUris;
|
protected List<String> redirectUris;
|
||||||
|
protected List<String> webOrigins;
|
||||||
|
|
||||||
public String getSelf() {
|
public String getSelf() {
|
||||||
return self;
|
return self;
|
||||||
|
@ -155,4 +156,12 @@ public class ApplicationRepresentation {
|
||||||
public void setRedirectUris(List<String> redirectUris) {
|
public void setRedirectUris(List<String> redirectUris) {
|
||||||
this.redirectUris = redirectUris;
|
this.redirectUris = redirectUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getWebOrigins() {
|
||||||
|
return webOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWebOrigins(List<String> webOrigins) {
|
||||||
|
this.webOrigins = webOrigins;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ public class RealmRepresentation {
|
||||||
protected Integer accessCodeLifespan;
|
protected Integer accessCodeLifespan;
|
||||||
protected Integer accessCodeLifespanUserAction;
|
protected Integer accessCodeLifespanUserAction;
|
||||||
protected Boolean enabled;
|
protected Boolean enabled;
|
||||||
|
protected Boolean accountManagement;
|
||||||
protected Boolean sslNotRequired;
|
protected Boolean sslNotRequired;
|
||||||
protected Boolean cookieLoginAllowed;
|
protected Boolean cookieLoginAllowed;
|
||||||
protected Boolean registrationAllowed;
|
protected Boolean registrationAllowed;
|
||||||
|
@ -101,6 +102,14 @@ public class RealmRepresentation {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isAccountManagement() {
|
||||||
|
return accountManagement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccountManagement(Boolean accountManagement) {
|
||||||
|
this.accountManagement = accountManagement;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean isSslNotRequired() {
|
public Boolean isSslNotRequired() {
|
||||||
return sslNotRequired;
|
return sslNotRequired;
|
||||||
}
|
}
|
||||||
|
|
24
examples/js-google/index.html
Normal file
|
@ -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>
|
139
examples/js-google/keycloak.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
})();
|
222
examples/js-google/keycloak.js.orig
Normal file
|
@ -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
|
||||||
|
})();
|
13
examples/js-google/kinvey.html
Normal file
|
@ -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>
|
60
examples/js-google/testrealm.json
Executable file
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -10,13 +10,14 @@
|
||||||
clientId : 'test-app',
|
clientId : 'test-app',
|
||||||
clientSecret : 'password',
|
clientSecret : 'password',
|
||||||
baseUrl : 'http://localhost:8081/auth-server',
|
baseUrl : 'http://localhost:8081/auth-server',
|
||||||
realm : 'test'
|
realm : 'test',
|
||||||
|
redirectUri : 'http://localhost/js'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (keycloak.authenticated) {
|
if (keycloak.authenticated) {
|
||||||
document.write('User: ' + keycloak.user);
|
document.write('User: ' + keycloak.user);
|
||||||
} else {
|
} 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>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,70 @@
|
||||||
window.keycloak = (function () {
|
window.keycloak = (function () {
|
||||||
var kc = {};
|
var kc = {};
|
||||||
var config = null;
|
var config = {
|
||||||
|
baseUrl: null,
|
||||||
|
clientId: null,
|
||||||
|
clientSecret: null,
|
||||||
|
realm: null,
|
||||||
|
redirectUri: null
|
||||||
|
};
|
||||||
|
|
||||||
kc.init = function (c) {
|
kc.init = function (c) {
|
||||||
config = c;
|
for (var prop in config) {
|
||||||
|
if (c[prop]) {
|
||||||
var token = getTokenFromCode();
|
config[prop] = c[prop];
|
||||||
if (token) {
|
|
||||||
var t = parseToken(token);
|
|
||||||
kc.user = t.prn;
|
|
||||||
kc.authenticated = true;
|
|
||||||
} else {
|
|
||||||
kc.authenticated = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config[prop]) {
|
||||||
|
throw new Error(prop + 'not defined');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
kc.login = function () {
|
kc.login = function () {
|
||||||
var clientId = encodeURIComponent(config.clientId);
|
window.location.href = getLoginUrl();
|
||||||
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;
|
return kc;
|
||||||
|
|
||||||
function parseToken(token) {
|
function getLoginUrl(fragment) {
|
||||||
var t = base64Decode(token.split('.')[1]);
|
var state = createUUID();
|
||||||
return JSON.parse(t);
|
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 getTokenFromCode() {
|
function parseToken(token) {
|
||||||
|
return JSON.parse(atob(token.split('.')[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function processCallback() {
|
||||||
var code = getQueryParam('code');
|
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) {
|
if (code) {
|
||||||
window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
|
console.info('Received code');
|
||||||
|
|
||||||
var clientId = encodeURIComponent(config.clientId);
|
var clientId = encodeURIComponent(config.clientId);
|
||||||
var clientSecret = encodeURIComponent(config.clientSecret);
|
var clientSecret = encodeURIComponent(config.clientSecret);
|
||||||
var realm = encodeURIComponent(config.realm);
|
var realm = encodeURIComponent(config.realm);
|
||||||
|
|
||||||
var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
|
var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
|
||||||
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
|
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
|
||||||
|
|
||||||
var http = new XMLHttpRequest();
|
var http = new XMLHttpRequest();
|
||||||
|
@ -50,10 +73,30 @@ window.keycloak = (function() {
|
||||||
|
|
||||||
http.send(params);
|
http.send(params);
|
||||||
if (http.status == 200) {
|
if (http.status == 200) {
|
||||||
return JSON.parse(http.responseText)['access_token'];
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
|
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) {
|
function getQueryParam(name) {
|
||||||
|
@ -78,43 +121,4 @@ window.keycloak = (function() {
|
||||||
var uuid = s.join('');
|
var uuid = s.join('');
|
||||||
return uuid;
|
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;
|
|
||||||
}
|
|
||||||
})();
|
})();
|
|
@ -8,6 +8,7 @@
|
||||||
"sslNotRequired": true,
|
"sslNotRequired": true,
|
||||||
"cookieLoginAllowed": true,
|
"cookieLoginAllowed": true,
|
||||||
"registrationAllowed": true,
|
"registrationAllowed": true,
|
||||||
|
"accountManagement": true,
|
||||||
"resetPasswordAllowed": true,
|
"resetPasswordAllowed": true,
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
"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",
|
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"adminUrl": "http://localhost:8081/app/logout",
|
"adminUrl": "http://localhost:8081/app/logout",
|
||||||
"useRealmMappings": true,
|
"useRealmMappings": true,
|
||||||
|
"webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
|
||||||
"credentials": [
|
"credentials": [
|
||||||
{
|
{
|
||||||
"type": "password",
|
"type": "password",
|
||||||
|
|
|
@ -26,18 +26,18 @@ import org.keycloak.services.resources.flows.FormFlows;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class ErrorBean {
|
public class MessageBean {
|
||||||
|
|
||||||
private String summary;
|
private String summary;
|
||||||
|
|
||||||
private FormFlows.ErrorType type;
|
private FormFlows.MessageType type;
|
||||||
|
|
||||||
// Message is considered ERROR by default
|
// Message is considered ERROR by default
|
||||||
public ErrorBean(String summary) {
|
public MessageBean(String summary) {
|
||||||
this(summary, FormFlows.ErrorType.ERROR);
|
this(summary, FormFlows.MessageType.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ErrorBean(String summary, FormFlows.ErrorType type) {
|
public MessageBean(String summary, FormFlows.MessageType type) {
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
@ -47,15 +47,15 @@ public class ErrorBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSuccess(){
|
public boolean isSuccess(){
|
||||||
return FormFlows.ErrorType.SUCCESS.equals(this.type);
|
return FormFlows.MessageType.SUCCESS.equals(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWarning(){
|
public boolean isWarning(){
|
||||||
return FormFlows.ErrorType.WARNING.equals(this.type);
|
return FormFlows.MessageType.WARNING.equals(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isError(){
|
public boolean isError(){
|
||||||
return FormFlows.ErrorType.ERROR.equals(this.type);
|
return FormFlows.MessageType.ERROR.equals(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* JBoss, Home of Professional Open Source.
|
|
||||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
|
||||||
* as indicated by the @author tags. See the copyright.txt file in the
|
|
||||||
* distribution for a full listing of individual contributors.
|
|
||||||
*
|
|
||||||
* This is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as
|
|
||||||
* published by the Free Software Foundation; either version 2.1 of
|
|
||||||
* the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This software is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this software; if not, write to the Free
|
|
||||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
||||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
|
||||||
*/
|
|
||||||
package org.keycloak.forms;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.annotation.WebServlet;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.logging.Logger;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
|
||||||
import com.google.zxing.common.BitMatrix;
|
|
||||||
import com.google.zxing.qrcode.QRCodeWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
@WebServlet(urlPatterns = "/forms/qrcode")
|
|
||||||
public class QRServlet extends HttpServlet {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(QRServlet.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
String[] size = req.getParameter("size").split("x");
|
|
||||||
int width = Integer.parseInt(size[0]);
|
|
||||||
int height = Integer.parseInt(size[1]);
|
|
||||||
|
|
||||||
String contents = req.getParameter("contents");
|
|
||||||
|
|
||||||
try {
|
|
||||||
QRCodeWriter writer = new QRCodeWriter();
|
|
||||||
|
|
||||||
BitMatrix bitMatrix = writer.encode(contents, BarcodeFormat.QR_CODE, width, height);
|
|
||||||
|
|
||||||
MatrixToImageWriter.writeToStream(bitMatrix, "png", resp.getOutputStream());
|
|
||||||
resp.setContentType("image/png");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Failed to generate qr code", e);
|
|
||||||
resp.sendError(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -78,7 +78,7 @@ public class TotpBean {
|
||||||
|
|
||||||
public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
|
public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
|
||||||
String contents = URLEncoder.encode("otpauth://totp/keycloak?secret=" + totpSecretEncoded, "utf-8");
|
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() {
|
public UserBean getUser() {
|
||||||
|
|
|
@ -124,6 +124,10 @@ public class UrlBean {
|
||||||
return Urls.accountTotpRemove(baseURI, realm.getId()).toString();
|
return Urls.accountTotpRemove(baseURI, realm.getId()).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLogoutUrl() {
|
||||||
|
return Urls.accountLogout(baseURI, realm.getId()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
public String getLoginPasswordResetUrl() {
|
public String getLoginPasswordResetUrl() {
|
||||||
return Urls.loginPasswordReset(baseURI, realm.getId()).toString();
|
return Urls.loginPasswordReset(baseURI, realm.getId()).toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import freemarker.template.Configuration;
|
||||||
import freemarker.template.Template;
|
import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateException;
|
import freemarker.template.TemplateException;
|
||||||
import org.jboss.resteasy.logging.Logger;
|
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.LoginBean;
|
||||||
import org.keycloak.forms.OAuthGrantBean;
|
import org.keycloak.forms.OAuthGrantBean;
|
||||||
import org.keycloak.forms.RealmBean;
|
import org.keycloak.forms.RealmBean;
|
||||||
|
@ -69,8 +69,7 @@ public class FormServiceImpl implements FormService {
|
||||||
commandMap.put(Pages.TOTP, new CommandTotp());
|
commandMap.put(Pages.TOTP, new CommandTotp());
|
||||||
commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
|
commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
|
||||||
commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp());
|
commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp());
|
||||||
commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandLoginTotp());
|
commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandVerifyEmail());
|
||||||
commandMap.put(Pages.ERROR, new CommandError());
|
|
||||||
commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant());
|
commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +81,8 @@ public class FormServiceImpl implements FormService {
|
||||||
|
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||||
|
|
||||||
if (dataBean.getError() != null){
|
if (dataBean.getMessage() != null){
|
||||||
attributes.put("message", new ErrorBean(dataBean.getError(), dataBean.getErrorType()));
|
attributes.put("message", new MessageBean(dataBean.getMessage(), dataBean.getMessageType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
RealmBean realm = new RealmBean(dataBean.getRealm());
|
RealmBean realm = new RealmBean(dataBean.getRealm());
|
||||||
|
@ -161,9 +160,6 @@ public class FormServiceImpl implements FormService {
|
||||||
|
|
||||||
private class CommandLoginTotp implements Command {
|
private class CommandLoginTotp implements Command {
|
||||||
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
|
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());
|
RealmBean realm = new RealmBean(dataBean.getRealm());
|
||||||
|
|
||||||
|
@ -206,10 +202,6 @@ public class FormServiceImpl implements FormService {
|
||||||
|
|
||||||
private class CommandLogin implements Command {
|
private class CommandLogin implements Command {
|
||||||
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
|
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());
|
RealmBean realm = new RealmBean(dataBean.getRealm());
|
||||||
|
|
||||||
attributes.put("realm", realm);
|
attributes.put("realm", realm);
|
||||||
|
@ -230,9 +222,6 @@ public class FormServiceImpl implements FormService {
|
||||||
|
|
||||||
private class CommandRegister implements Command {
|
private class CommandRegister implements Command {
|
||||||
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
|
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());
|
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 {
|
private class CommandOAuthGrant implements Command {
|
||||||
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
|
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 {
|
private interface Command {
|
||||||
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean);
|
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email">${rb.getString('email')}</label><span class="required">*</span>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="lastName">${rb.getString('lastName')}</label><span class="required">*</span>
|
<label for="lastName">${rb.getString('lastName')}</label><span class="required">*</span>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-actions">
|
<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" class="primary">Save</button>
|
||||||
<button type="submit">Cancel</button>
|
<button type="submit">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -185,43 +185,6 @@ table a:hover {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-left: 1em;
|
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 {
|
td.token-cell button {
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,9 +307,7 @@ button[class^="icon-"] {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
button[class^="icon-"]:hover {
|
|
||||||
background-image: url(img/sprites.png);
|
|
||||||
}
|
|
||||||
legend {
|
legend {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
border-width: 1px 0 0 0;
|
border-width: 1px 0 0 0;
|
||||||
|
@ -328,14 +326,7 @@ legend .text {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.25em;
|
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 {
|
.form-group {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
@ -377,7 +368,7 @@ legend + table {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
.code {
|
.code {
|
||||||
font-family: Courier, monospace;
|
font-family: Courier, monospace;
|
||||||
}
|
}
|
||||||
.onoffswitch {
|
.onoffswitch {
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
@ -460,20 +451,6 @@ input[type="email"].tiny {
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
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 {
|
.select-rcue:hover {
|
||||||
border-color: #62afdb;
|
border-color: #62afdb;
|
||||||
}
|
}
|
||||||
|
@ -510,93 +487,7 @@ input[type="email"].tiny {
|
||||||
.select-rcue option:hover {
|
.select-rcue option:hover {
|
||||||
background-color: #d5ecf9;
|
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 {
|
.input-group input + .select-rcue {
|
||||||
border-radius: 0 2px 2px 0;
|
border-radius: 0 2px 2px 0;
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
|
@ -605,93 +496,7 @@ input[type="email"].tiny {
|
||||||
.input-select .input-group input {
|
.input-select .input-group input {
|
||||||
float: left;
|
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 {
|
.form-actions {
|
||||||
float: right;
|
float: right;
|
||||||
margin-top: 3em;
|
margin-top: 3em;
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="80px" height="76px" viewBox="0 0 80 76" enable-background="new 0 0 80 76" xml:space="preserve">
|
|
||||||
<path id="f" fill="#FFFFFF" d="M43.717,62V40.104h7.35l1.1-8.533h-8.449v-5.448c0-2.471,0.686-4.154,4.229-4.154l4.518-0.002v-7.632
|
|
||||||
C51.682,14.231,49,14,45.88,14c-6.514,0-10.975,3.977-10.975,11.279v6.292h-7.368v8.533h7.368V62H43.717z"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 722 B |
|
@ -1,164 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="1680px" height="1080px" viewBox="0 0 1680 1080" enable-background="new 0 0 1680 1080" xml:space="preserve">
|
|
||||||
<rect fill="#383D42" width="1680" height="1080"/>
|
|
||||||
<g>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="1057" y="597" width="704" height="572">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="1057" y="597" width="704" height="572" id="SVGID_1_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter)">
|
|
||||||
|
|
||||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="1302.4395" y1="-269.9951" x2="1622.4397" y2="22.0046" gradientTransform="matrix(-1 0 0 -1 3112 976.6914)">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<rect x="1035" y="573" fill="url(#SVGID_2_)" width="752" height="620"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path mask="url(#SVGID_1_)" fill="#D8D8D8" d="M1761,597c-140,214-410,510-704,572h704V597z"/>
|
|
||||||
<defs>
|
|
||||||
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_1_" filterUnits="userSpaceOnUse" x="1056.742" y="596.315" width="705.304" height="573.907">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="1056.742" y="596.315" width="705.304" height="573.907" id="SVGID_3_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_1_)">
|
|
||||||
|
|
||||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="1302.4395" y1="-269.9951" x2="1622.4397" y2="22.0046" gradientTransform="matrix(-1 0 0 -1 3112 976.6914)">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<rect x="1035" y="573" fill="url(#SVGID_4_)" width="752" height="620"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path opacity="0.1" mask="url(#SVGID_3_)" fill="none" stroke="#D8D8D8" stroke-width="2.5" stroke-miterlimit="10" d="M1761,597
|
|
||||||
c-140,214-410,510-704,572"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_2_" filterUnits="userSpaceOnUse" x="168.747" y="365.576" width="1798.615" height="822.631">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="168.747" y="365.576" width="1798.615" height="822.631" id="SVGID_5_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_2_)">
|
|
||||||
|
|
||||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="1476.5908" y1="477.5732" x2="2688.6016" y2="21.5689" gradientTransform="matrix(-0.9876 -0.1569 0.1569 -0.9876 3048.3154 1294.3018)">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon fill="url(#SVGID_6_)" points="329.208,58.782 1976.554,320.459 1810.26,1367.334 162.914,1105.656 "/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path mask="url(#SVGID_5_)" fill="#E0E0E0" d="M1967.361,365.576c-393.429,322.269-1126.777,782.925-1798.615,728.856
|
|
||||||
c379.652,70.433,1428.387,135.769,1651.069,59.762L1967.361,365.576z"/>
|
|
||||||
<path fill="#D8D8D8" d="M-114.695,1221h1160.347c80.272-27.337,158.024-70.67,230.989-123.266
|
|
||||||
C792.789,983.34-203.837,592.187-368.695,373L-114.695,1221z"/>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_3_" filterUnits="userSpaceOnUse" x="-395" y="514.337" width="1423.831" height="829.326">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="-395" y="514.337" width="1423.831" height="829.326" id="SVGID_7_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_3_)">
|
|
||||||
|
|
||||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="2111.374" y1="319.9775" x2="3115.3701" y2="81.9786" gradientTransform="matrix(-1.075 0.1117 -0.1167 -1.1224 3229.8789 866.1523)">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="0.0972" style="stop-color:#060606"/>
|
|
||||||
<stop offset="0.2213" style="stop-color:#151515"/>
|
|
||||||
<stop offset="0.36" style="stop-color:#303030"/>
|
|
||||||
<stop offset="0.5093" style="stop-color:#545454"/>
|
|
||||||
<stop offset="0.6671" style="stop-color:#838383"/>
|
|
||||||
<stop offset="0.8323" style="stop-color:#BDBDBD"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon fill="url(#SVGID_8_)" points="-543.126,587.599 1129.521,413.746 1223.549,1318.382 -449.1,1492.233 "/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path mask="url(#SVGID_7_)" fill="#C6C6C5" d="M-395,1343.663c225.178-156.636,836.847-511.283,1423.831-382.832l-17.056-446.355
|
|
||||||
C360.896,503.847-136.975,1108.097-395,1343.663z"/>
|
|
||||||
<path opacity="0.2" fill="none" stroke="#D8D8D8" stroke-width="5" stroke-miterlimit="10" d="M-368.695,373
|
|
||||||
c164.858,219.187,1161.484,610.34,1645.336,724.734c154.24,39.423,293.768,67.189,413.359,86.745"/>
|
|
||||||
<path opacity="0.4" fill="none" stroke="#777D82" stroke-width="3" stroke-miterlimit="10" d="M-218.695,311
|
|
||||||
c338,316,1048,836,1440,990"/>
|
|
||||||
<path opacity="0.1" fill="none" stroke="#C6C6C5" stroke-width="2" stroke-miterlimit="10" d="M1257.069,1089.063
|
|
||||||
c124.494-56.997,382.481-291.736,437.979-414.73"/>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_4_" filterUnits="userSpaceOnUse" x="535.169" y="-135" width="1423.831" height="829.326">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="535.169" y="-135" width="1423.831" height="829.326" id="SVGID_9_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_4_)">
|
|
||||||
|
|
||||||
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="664.5693" y1="382.668" x2="1668.5647" y2="144.6692" gradientTransform="matrix(1.075 -0.1117 0.1167 1.1224 -117.8789 110.5386)">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<polygon fill="url(#SVGID_10_)" points="2107.126,621.064 434.478,794.917 340.452,-109.719 2013.1,-283.57 "/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path mask="url(#SVGID_9_)" fill="#C6C6C5" d="M1959-135C1733.822,21.636,1122.153,376.284,535.169,247.832l17.055,446.356
|
|
||||||
C1203.104,704.816,1700.975,100.567,1959-135z"/>
|
|
||||||
<path fill="#D8D8D8" d="M1743-27H582.653C502.38,0.337,424.629,43.67,351.665,96.265C835.516,210.66,1832.142,601.813,1997,821
|
|
||||||
L1743-27z"/>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_5_" filterUnits="userSpaceOnUse" x="-49" y="-47" width="704" height="572">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="-49" y="-47" width="704" height="572" id="SVGID_11_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_5_)">
|
|
||||||
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="-97.5605" y1="-124.686" x2="222.4397" y2="167.3137">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<rect x="-75" y="-71" fill="url(#SVGID_12_)" width="752" height="620"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path mask="url(#SVGID_11_)" fill="#D8D8D8" d="M-49,525C91,311,361,15,655-47H-49V525z"/>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_6_" filterUnits="userSpaceOnUse" x="-209" y="-147.691" width="1662" height="1002.691">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="-209" y="-147.691" width="1662" height="1002.691" id="SVGID_13_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_6_)">
|
|
||||||
<linearGradient id="SVGID_14_" gradientUnits="userSpaceOnUse" x1="54.2939" y1="584.9688" x2="1266.2927" y2="128.9689">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<rect x="-211" y="-159" fill="url(#SVGID_14_)" width="1668" height="1060"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path mask="url(#SVGID_13_)" fill="#E0E0E0" d="M-209,855C129,475,781-95,1453-147C1067-157,21-57-187,53L-209,855z"/>
|
|
||||||
<path opacity="0.2" fill="none" stroke="#D8D8D8" stroke-width="5" stroke-miterlimit="10" d="M1997,821
|
|
||||||
C1832.142,601.813,835.516,210.66,351.665,96.265C197.423,56.843,57.896,29.076-61.695,9.521"/>
|
|
||||||
<path opacity="0.4" fill="none" stroke="#777D82" stroke-width="3" stroke-miterlimit="10" d="M1847,883C1509,567,799,47,407-107"/>
|
|
||||||
<defs>
|
|
||||||
<filter id="Adobe_OpacityMaskFilter_7_" filterUnits="userSpaceOnUse" x="-50.046" y="-48.223" width="705.304" height="573.907">
|
|
||||||
<feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<mask maskUnits="userSpaceOnUse" x="-50.046" y="-48.223" width="705.304" height="573.907" id="SVGID_15_">
|
|
||||||
<g filter="url(#Adobe_OpacityMaskFilter_7_)">
|
|
||||||
<linearGradient id="SVGID_16_" gradientUnits="userSpaceOnUse" x1="-97.5605" y1="-124.686" x2="222.4397" y2="167.3137">
|
|
||||||
<stop offset="0" style="stop-color:#000000"/>
|
|
||||||
<stop offset="1" style="stop-color:#FFFFFF"/>
|
|
||||||
</linearGradient>
|
|
||||||
<rect x="-75" y="-71" fill="url(#SVGID_16_)" width="752" height="620"/>
|
|
||||||
</g>
|
|
||||||
</mask>
|
|
||||||
<path opacity="0.1" mask="url(#SVGID_15_)" fill="none" stroke="#D8D8D8" stroke-width="2.5" stroke-miterlimit="10" d="M-49,525
|
|
||||||
C91,311,361,15,655-47"/>
|
|
||||||
<circle fill="#D8D8D8" cx="1247.084" cy="399.5" r="2.5"/>
|
|
||||||
<circle fill="#D8D8D8" cx="436.333" cy="117.667" r="2"/>
|
|
||||||
<circle fill="#D8D8D8" cx="254.667" cy="173.667" r="2"/>
|
|
||||||
<circle fill="#D8D8D8" cx="375" cy="101.667" r="4"/>
|
|
||||||
<circle fill="#D8D8D8" cx="351.665" cy="96.265" r="5"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 9.6 KiB |
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="1000px" height="11px" viewBox="0 0 1000 11" enable-background="new 0 0 1000 11" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<path fill="#F7E6E6" d="M1000,0c0,1.104-0.896,2-2,2H38l-8,9l-8-9H2C0.896,2,0,1.104,0,0"/>
|
|
||||||
<path fill="#B82025" d="M999,0c0,0.551-0.448,1-1,1H38h-0.449l-0.298,0.335L30,9.495l-7.253-8.159L22.449,1H22H2
|
|
||||||
C1.449,1,1,0.551,1,0 M0,0c0,1.104,0.896,2,2,2h20l8,9l8-9h960c1.104,0,2-0.896,2-2"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 793 B |
|
@ -1,19 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" id="svg7384" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="17px" height="17px" viewBox="0 0 17 17" enable-background="new 0 0 17 17" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<path fill="#CC0000" d="M8.196,3.822C8.74,3.806,9.244,4.31,9.228,4.854v3.938c0.007,0.528-0.473,1.014-1,1.014
|
|
||||||
c-0.528,0-1.008-0.486-1-1.014V4.854c-0.008-0.467,0.354-0.913,0.812-1C8.091,3.839,8.144,3.83,8.196,3.822L8.196,3.822z"/>
|
|
||||||
<rect x="7.228" y="10.792" fill="#CC0000" width="2" height="2"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path fill="#CC0000" d="M11.432,16.5H5.126L0,11.373V5.097L5.127,0h6.247l5.125,5.157V11.4L11.432,16.5z M5.747,15h5.06
|
|
||||||
l4.192-4.218V5.775L10.75,1.5H5.746L1.5,5.721v5.031L5.747,15z"/>
|
|
||||||
<path fill="#CC0000" d="M5.229,16.25h6.098l4.922-4.953V5.26L11.27,0.25H5.23L0.25,5.201v6.069L5.229,16.25z M10.91,15.25H5.644
|
|
||||||
L1.25,10.855V5.617L5.643,1.25h5.212l4.395,4.422v5.212L10.91,15.25z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 164 B |
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="1px" height="400px" viewBox="0 0 1 400" enable-background="new 0 0 1 400" xml:space="preserve">
|
|
||||||
<rect opacity="0.15" fill="#FFFFFF" width="1.447" height="400"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 551 B |
|
@ -1,19 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
||||||
width="324px" height="400px" viewBox="0 0 324 400" enable-background="new 0 0 324 400" xml:space="preserve">
|
|
||||||
<rect x="6.001" opacity="0.07" fill="#FFFFFF" width="0.997" height="190"/>
|
|
||||||
<rect x="6" y="209" opacity="0.07" fill="#FFFFFF" width="1" height="191"/>
|
|
||||||
<rect x="323" opacity="0.15" fill="#FFFFFF" width="1.447" height="400"/>
|
|
||||||
<g opacity="0.15">
|
|
||||||
<path fill="#FFFFFF" d="M6.501,200.066c0,1.047-0.264,1.864-0.791,2.452S4.454,203.4,3.524,203.4c-0.574,0-1.084-0.135-1.529-0.404
|
|
||||||
s-0.789-0.656-1.031-1.16s-0.363-1.094-0.363-1.77c0-1.047,0.262-1.862,0.785-2.446s1.25-0.876,2.18-0.876
|
|
||||||
c0.898,0,1.612,0.299,2.142,0.896S6.501,199.047,6.501,200.066z M1.608,200.066c0,0.82,0.164,1.445,0.492,1.875
|
|
||||||
s0.811,0.645,1.447,0.645s1.12-0.214,1.45-0.642s0.495-1.054,0.495-1.878c0-0.816-0.165-1.437-0.495-1.86s-0.817-0.636-1.462-0.636
|
|
||||||
c-0.637,0-1.117,0.209-1.441,0.627S1.608,199.238,1.608,200.066z"/>
|
|
||||||
<path fill="#FFFFFF" d="M11.136,196.744c0.285,0,0.541,0.023,0.768,0.07l-0.135,0.902c-0.266-0.059-0.5-0.088-0.703-0.088
|
|
||||||
c-0.52,0-0.964,0.211-1.333,0.633s-0.554,0.947-0.554,1.576v3.445H8.206v-6.422h0.803l0.111,1.189h0.047
|
|
||||||
c0.238-0.418,0.525-0.74,0.861-0.967S10.733,196.744,11.136,196.744z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
|
@ -3,9 +3,9 @@ body {
|
||||||
min-height: 60em;
|
min-height: 60em;
|
||||||
min-width: 120em;
|
min-width: 120em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rcue-login-register {
|
.rcue-login-register {
|
||||||
background-color: #1D2226;
|
background-color: #1D2226;
|
||||||
background-image: url("img/login-screen-background.jpg");
|
|
||||||
background-position: top left;
|
background-position: top left;
|
||||||
background-size: auto;
|
background-size: auto;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
@ -17,6 +17,7 @@ body {
|
||||||
/* Info area */
|
/* Info area */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rcue-login-register h1 a {
|
.rcue-login-register h1 a {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5em;
|
top: 5em;
|
||||||
|
@ -61,12 +62,12 @@ body {
|
||||||
padding-right: 4.5em;
|
padding-right: 4.5em;
|
||||||
}
|
}
|
||||||
.rcue-login-register .form-area {
|
.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-repeat: no-repeat;
|
||||||
background-position: 40.2em center;
|
background-position: 40.2em center;
|
||||||
}
|
}
|
||||||
.rcue-login-register .form-area.social {
|
.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;
|
background-position: 39.6em center;
|
||||||
}
|
}
|
||||||
.rcue-login-register .section > p {
|
.rcue-login-register .section > p {
|
||||||
|
@ -267,10 +268,6 @@ a.zocial:before {
|
||||||
.rcue-login-register.register form > div.aside-btn p {
|
.rcue-login-register.register form > div.aside-btn p {
|
||||||
line-height: 1.3em;
|
line-height: 1.3em;
|
||||||
}
|
}
|
||||||
/* Customer login */
|
|
||||||
.rcue-login-register.customer {
|
|
||||||
background-image: url("img/customer-login-screen-bg2.jpg");
|
|
||||||
}
|
|
||||||
.rcue-login-register p.powered {
|
.rcue-login-register p.powered {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
margin-top: 1.27272727272727em;
|
margin-top: 1.27272727272727em;
|
||||||
|
@ -291,9 +288,6 @@ a.zocial:before {
|
||||||
.rcue-login-register.reset .background-area .section.app-form {
|
.rcue-login-register.reset .background-area .section.app-form {
|
||||||
width: 43.2em;
|
width: 43.2em;
|
||||||
}
|
}
|
||||||
.rcue-login-register.reset .feedback {
|
|
||||||
left: 35.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rcue-login-register.oauth .form-actions {
|
.rcue-login-register.oauth .form-actions {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -405,11 +399,6 @@ a.zocial:before {
|
||||||
.rcue-login-register.email label {
|
.rcue-login-register.email label {
|
||||||
width: 6.78571428571429em;
|
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 {
|
.rcue-login-register.email .feedback.bottom-left {
|
||||||
left: 38.3em;
|
left: 38.3em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-image: url(img/sprites.png); /* Modified by Gabriel */
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
text-indent: -99999em;
|
text-indent: -99999em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
|
|
@ -30,14 +30,6 @@ table thead tr th {
|
||||||
.rcue-table-actions {
|
.rcue-table-actions {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
.rcue-table-actions button {
|
|
||||||
padding: 3px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
box-shadow: 1px 2px 2px #e3e3e3;
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
.rcue-table-number {
|
.rcue-table-number {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 100px;
|
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));
|
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #eeeeee));
|
||||||
border-bottom: 1px #cedede solid;
|
border-bottom: 1px #cedede solid;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
table tbody tr td:first-child {
|
|
||||||
color: #0099d3;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
table thead tr th:last-child {
|
table thead tr th:last-child {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
@ -70,12 +58,7 @@ table tbody tr:first-child td {
|
||||||
table tbody tr td:last-child {
|
table tbody tr td:last-child {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
table tbody tr:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #ebebeb;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/* Styles from Gabriel */
|
/* Styles from Gabriel */
|
||||||
.rcue-table-actions button + button,
|
.rcue-table-actions button + button,
|
||||||
.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-bottom: 7px;
|
||||||
margin-top: 8px;
|
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 {
|
table tfoot tr {
|
||||||
border-top: 1px solid #cecece;
|
border-top: 1px solid #cecece;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<#elseif section = "form">
|
<#elseif section = "form">
|
||||||
|
|
||||||
<p class="instruction">Something happened and we could not process your request.</p>
|
<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" >
|
<#elseif section = "info" >
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 318 B |
|
@ -8,11 +8,6 @@
|
||||||
|
|
||||||
Google Authenticator Setup
|
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">
|
<#elseif section = "form">
|
||||||
|
|
||||||
<div id="form">
|
<div id="form">
|
||||||
|
|
|
@ -11,15 +11,6 @@
|
||||||
<#elseif section = "form">
|
<#elseif section = "form">
|
||||||
|
|
||||||
<div id="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>
|
<p class="instruction">${rb.getString('emailInstruction')}</p>
|
||||||
<form action="${url.loginPasswordResetUrl}" method="post">
|
<form action="${url.loginPasswordResetUrl}" method="post">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -8,11 +8,6 @@
|
||||||
|
|
||||||
Email verification
|
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">
|
<#elseif section = "form">
|
||||||
|
|
||||||
<div class="app-form">
|
<div class="app-form">
|
||||||
|
|
|
@ -15,15 +15,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password-new">${rb.getString('passwordNew')}</label>
|
<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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password-confirm" class="two-lines">${rb.getString('passwordConfirm')}</label>
|
<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>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-actions">
|
<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" class="primary">Save</button>
|
||||||
<button type="submit">Cancel</button>
|
<button type="submit">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,11 @@
|
||||||
|
|
||||||
<body class="rcue-login-register ${bodyClass}">
|
<body class="rcue-login-register ${bodyClass}">
|
||||||
<div class="feedback-aligner">
|
<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>
|
</div>
|
||||||
<#if (template.themeConfig.logo)?has_content>
|
<#if (template.themeConfig.logo)?has_content>
|
||||||
<h1>
|
<h1>
|
||||||
|
@ -33,17 +37,25 @@
|
||||||
<div class="background-area">
|
<div class="background-area">
|
||||||
<div class="form-area clearfix">
|
<div class="form-area clearfix">
|
||||||
<div class="section app-form">
|
<div class="section app-form">
|
||||||
<h3>Application login area</h3>
|
<#if !isErrorPage && message?has_content>
|
||||||
<#nested "form">
|
<#if message.error>
|
||||||
</div>
|
|
||||||
|
|
||||||
<#if !isErrorPage && error?has_content>
|
|
||||||
<div class="feedback error bottom-left show">
|
<div class="feedback error bottom-left show">
|
||||||
<p>
|
<p>
|
||||||
<strong id="loginError">${rb.getString(error.summary)}</strong>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<h3>Application login area</h3>
|
||||||
|
<#nested "form">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section info-area">
|
<div class="section info-area">
|
||||||
<h3>Info area</h3>
|
<h3>Info area</h3>
|
||||||
|
|
|
@ -31,10 +31,10 @@
|
||||||
<div class="form-area ${(realm.social)?string('social','')} clearfix">
|
<div class="form-area ${(realm.social)?string('social','')} clearfix">
|
||||||
<div class="section app-form">
|
<div class="section app-form">
|
||||||
<h3>Application login area</h3>
|
<h3>Application login area</h3>
|
||||||
<#if error?has_content>
|
<#if message?has_content && message.error>
|
||||||
<div class="feedback error bottom-left show">
|
<div class="feedback error bottom-left show">
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -4,16 +4,15 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Edit Account - <#nested "title"></title>
|
<title>Edit Account - <#nested "title"></title>
|
||||||
|
<!-- TODO replace with actual logo once
|
||||||
<link rel="icon" href="img/favicon.ico">
|
<link rel="icon" href="img/favicon.ico">
|
||||||
|
|
||||||
<!-- Frameworks -->
|
<!-- Frameworks -->
|
||||||
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/reset.css">
|
<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}/lib/bootstrap/css/bootstrap.css" rel="stylesheet" />
|
||||||
<link href="${template.formsPath}/theme/${template.theme}/css/zocial/zocial.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/sprites.css">
|
||||||
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/select2.css">
|
|
||||||
<!-- TODO remove external links -->
|
<!-- 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'>
|
<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/base.css">
|
||||||
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/forms.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/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/icons.css">
|
||||||
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/tables.css">
|
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/tables.css">
|
||||||
|
|
||||||
<!-- Page styles -->
|
<!-- Page styles -->
|
||||||
<link rel="stylesheet" href="${template.formsPath}/theme/${template.theme}/css/admin-console.css">
|
<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>
|
</head>
|
||||||
<body class="admin-console user ${bodyClass}">
|
<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>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="icon-user">Icon: user</span>
|
||||||
${user.firstName!''} ${user.lastName!''}</a>
|
${user.firstName!''} ${user.lastName!''}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="${url.logoutUrl}">Logout</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,7 +65,7 @@
|
||||||
<li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li>
|
<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=='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=='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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ missingLastName=Please specify last name
|
||||||
missingEmail=Please specify email
|
missingEmail=Please specify email
|
||||||
missingUsername=Please specify username
|
missingUsername=Please specify username
|
||||||
missingPassword=Please specify password
|
missingPassword=Please specify password
|
||||||
|
notMatchPassword=Passwords don't match
|
||||||
missingTotp=Please specify authenticator code
|
missingTotp=Please specify authenticator code
|
||||||
|
|
||||||
invalidPasswordExisting=Invalid existing password
|
invalidPasswordExisting=Invalid existing password
|
||||||
|
@ -43,6 +44,12 @@ successTotpRemoved=Google authenticator removed.
|
||||||
usernameExists=Username already exists
|
usernameExists=Username already exists
|
||||||
|
|
||||||
error=A system error has occured, contact admin
|
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!
|
successHeader=Success!
|
||||||
errorHeader=Error!
|
errorHeader=Error!
|
||||||
|
@ -55,3 +62,6 @@ emailSent=You should receive an email shortly with further instructions.
|
||||||
emailError=Invalid username or email.
|
emailError=Invalid username or email.
|
||||||
emailErrorInfo=Please, fill in the fields again.
|
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.
|
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
|
|
@ -11,4 +11,6 @@ public interface Constants {
|
||||||
String APPLICATION_ROLE = "KEYCLOAK_APPLICATION";
|
String APPLICATION_ROLE = "KEYCLOAK_APPLICATION";
|
||||||
String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
|
String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
|
||||||
String WILDCARD_ROLE = "*";
|
String WILDCARD_ROLE = "*";
|
||||||
|
|
||||||
|
String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management";
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,14 @@ public interface UserModel {
|
||||||
|
|
||||||
void removeRequiredAction(RequiredAction action);
|
void removeRequiredAction(RequiredAction action);
|
||||||
|
|
||||||
|
Set<String> getWebOrigins();
|
||||||
|
|
||||||
|
void setWebOrigins(Set<String> webOrigins);
|
||||||
|
|
||||||
|
void addWebOrigin(String webOrigin);
|
||||||
|
|
||||||
|
void removeWebOrigin(String webOrigin);
|
||||||
|
|
||||||
Set<String> getRedirectUris();
|
Set<String> getRedirectUris();
|
||||||
|
|
||||||
void setRedirectUris(Set<String> redirectUris);
|
void setRedirectUris(Set<String> redirectUris);
|
||||||
|
|
|
@ -22,6 +22,7 @@ public class UserAdapter implements UserModel {
|
||||||
private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
|
private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
|
||||||
|
|
||||||
private static final String REDIRECT_URIS = "redirectUris";
|
private static final String REDIRECT_URIS = "redirectUris";
|
||||||
|
private static final String WEB_ORIGINS = "webOrigins";
|
||||||
|
|
||||||
protected User user;
|
protected User user;
|
||||||
protected IdentityManager idm;
|
protected IdentityManager idm;
|
||||||
|
@ -161,6 +162,26 @@ public class UserAdapter implements UserModel {
|
||||||
removeFromAttributeSet(REDIRECT_URIS, redirectUri);
|
removeFromAttributeSet(REDIRECT_URIS, redirectUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public boolean isTotp() {
|
public boolean isTotp() {
|
||||||
Attribute<Boolean> a = user.getAttribute(KEYCLOAK_TOTP_ATTR);
|
Attribute<Boolean> a = user.getAttribute(KEYCLOAK_TOTP_ATTR);
|
||||||
|
|
5
pom.xml
|
@ -192,6 +192,11 @@
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<version>${jboss.logging.version}</version>
|
<version>${jboss.logging.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
<version>1.2.17</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
|
@ -165,6 +165,10 @@
|
||||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>javase</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
|
@ -44,9 +44,9 @@ public interface FormService {
|
||||||
|
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
private UserModel userModel;
|
private UserModel userModel;
|
||||||
private String error;
|
private String message;
|
||||||
|
|
||||||
private FormFlows.ErrorType errorType;
|
private FormFlows.MessageType messageType;
|
||||||
|
|
||||||
private MultivaluedMap<String, String> formData;
|
private MultivaluedMap<String, String> formData;
|
||||||
private URI baseURI;
|
private URI baseURI;
|
||||||
|
@ -81,11 +81,11 @@ public interface FormService {
|
||||||
|
|
||||||
private String contextPath;
|
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.realm = realm;
|
||||||
this.userModel = userModel;
|
this.userModel = userModel;
|
||||||
this.formData = formData;
|
this.formData = formData;
|
||||||
this.error = error;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI getBaseURI() {
|
public URI getBaseURI() {
|
||||||
|
@ -96,12 +96,12 @@ public interface FormService {
|
||||||
this.baseURI = baseURI;
|
this.baseURI = baseURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getError() {
|
public String getMessage() {
|
||||||
return error;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setError(String error) {
|
public void setMessage(String message) {
|
||||||
this.error = error;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultivaluedMap<String, String> getFormData() {
|
public MultivaluedMap<String, String> getFormData() {
|
||||||
|
@ -128,12 +128,12 @@ public interface FormService {
|
||||||
this.userModel = userModel;
|
this.userModel = userModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormFlows.ErrorType getErrorType() {
|
public FormFlows.MessageType getMessageType() {
|
||||||
return errorType;
|
return messageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setErrorType(FormFlows.ErrorType errorType) {
|
public void setMessageType(FormFlows.MessageType messageType) {
|
||||||
this.errorType = errorType;
|
this.messageType = messageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OAuth Part */
|
/* OAuth Part */
|
||||||
|
|
|
@ -42,6 +42,11 @@ public class ApplicationManager {
|
||||||
resourceUser.addRedirectUri(redirectUri);
|
resourceUser.addRedirectUri(redirectUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (resourceRep.getWebOrigins() != null) {
|
||||||
|
for (String webOrigin : resourceRep.getWebOrigins()) {
|
||||||
|
resourceUser.addWebOrigin(webOrigin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
realm.grantRole(resourceUser, loginRole);
|
realm.grantRole(resourceUser, loginRole);
|
||||||
|
|
||||||
|
@ -97,6 +102,11 @@ public class ApplicationManager {
|
||||||
if (redirectUris != null) {
|
if (redirectUris != null) {
|
||||||
resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
|
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) {
|
public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) {
|
||||||
|
@ -113,6 +123,11 @@ public class ApplicationManager {
|
||||||
rep.setRedirectUris(new LinkedList<String>(redirectUris));
|
rep.setRedirectUris(new LinkedList<String>(redirectUris));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> webOrigins = applicationModel.getApplicationUser().getWebOrigins();
|
||||||
|
if (webOrigins != null) {
|
||||||
|
rep.setWebOrigins(new LinkedList<String>(webOrigins));
|
||||||
|
}
|
||||||
|
|
||||||
return rep;
|
return rep;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.services.resources.AccountService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.resources.SaasService;
|
import org.keycloak.services.resources.SaasService;
|
||||||
|
|
||||||
|
@ -61,6 +62,11 @@ public class AuthenticationManager {
|
||||||
return createLoginCookie(realm, user, cookieName, cookiePath);
|
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) {
|
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
|
||||||
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
|
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
|
||||||
|
@ -99,6 +105,11 @@ public class AuthenticationManager {
|
||||||
expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
|
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) {
|
public void expireCookie(String cookieName, String path) {
|
||||||
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
|
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
|
@ -120,6 +131,11 @@ public class AuthenticationManager {
|
||||||
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
|
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) {
|
public UserModel authenticateSaasIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||||
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
|
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
|
||||||
if (user != null) return user;
|
if (user != null) return user;
|
||||||
|
|
|
@ -90,6 +90,36 @@ public class RealmManager {
|
||||||
if (rep.getDefaultRoles() != null) {
|
if (rep.getDefaultRoles() != null) {
|
||||||
realm.updateDefaultRoles(rep.getDefaultRoles());
|
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) {
|
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) {
|
public void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
|
||||||
|
@ -370,6 +404,9 @@ public class RealmManager {
|
||||||
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
|
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
|
||||||
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
||||||
|
|
||||||
|
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
|
||||||
|
rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
|
||||||
|
|
||||||
List<RoleModel> defaultRoles = realm.getDefaultRoles();
|
List<RoleModel> defaultRoles = realm.getDefaultRoles();
|
||||||
if (defaultRoles.size() > 0) {
|
if (defaultRoles.size() > 0) {
|
||||||
String[] d = new String[defaultRoles.size()];
|
String[] d = new String[defaultRoles.size()];
|
||||||
|
|
|
@ -37,16 +37,20 @@ public class ResourceAdminManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean logoutResource(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
|
protected boolean logoutResource(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
|
||||||
|
String managementUrl = resource.getManagementUrl();
|
||||||
|
if (managementUrl != null) {
|
||||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
|
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user);
|
||||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||||
Form form = new Form();
|
Form form = new Form();
|
||||||
form.param("token", token);
|
form.param("token", token);
|
||||||
String managementUrl = resource.getManagementUrl();
|
|
||||||
logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
|
logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
|
||||||
Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
|
Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
|
||||||
boolean success = response.getStatus() == 204;
|
boolean success = response.getStatus() == 204;
|
||||||
response.close();
|
response.close();
|
||||||
return success;
|
return success;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ public class Messages {
|
||||||
|
|
||||||
public static final String MISSING_PASSWORD = "missingPassword";
|
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_USERNAME = "missingUsername";
|
||||||
|
|
||||||
public static final String MISSING_TOTP = "missingTotp";
|
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 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";
|
public static final String ERROR = "error";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,37 +21,35 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.core.*;
|
||||||
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.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import javax.ws.rs.ext.Providers;
|
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.JWSInput;
|
||||||
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
|
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
|
||||||
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
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.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.email.EmailSender;
|
import org.keycloak.services.email.EmailSender;
|
||||||
import org.keycloak.services.managers.AccessCodeEntry;
|
import org.keycloak.services.managers.AccessCodeEntry;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
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.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.FormFlows;
|
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.keycloak.services.validation.Validation;
|
||||||
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
||||||
|
|
||||||
|
@ -60,6 +58,10 @@ import org.picketlink.idm.credential.util.TimeBasedOTP;
|
||||||
*/
|
*/
|
||||||
public class AccountService {
|
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;
|
private RealmModel realm;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
|
@ -72,50 +74,85 @@ public class AccountService {
|
||||||
private UriInfo uriInfo;
|
private UriInfo uriInfo;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
protected Providers providers;
|
private Providers providers;
|
||||||
|
|
||||||
protected AuthenticationManager authManager = new AuthenticationManager();
|
private AuthenticationManager authManager = new AuthenticationManager();
|
||||||
|
|
||||||
|
private ApplicationModel application;
|
||||||
|
|
||||||
private TokenManager tokenManager;
|
private TokenManager tokenManager;
|
||||||
|
|
||||||
public AccountService(RealmModel realm, TokenManager tokenManager) {
|
public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager) {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
this.application = application;
|
||||||
this.tokenManager = tokenManager;
|
this.tokenManager = tokenManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response forwardToPage(String path, String template) {
|
||||||
|
UserModel user = getUser(false);
|
||||||
|
if (user != null) {
|
||||||
|
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template);
|
||||||
|
} else {
|
||||||
|
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")
|
@Path("access")
|
||||||
@GET
|
@GET
|
||||||
public Response accessPage() {
|
public Response accessPage() {
|
||||||
UserModel user = getUserFromAuthManager();
|
return forwardToPage("access", Pages.ACCESS);
|
||||||
if (user != null) {
|
|
||||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccess();
|
|
||||||
} else {
|
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("")
|
@Path("")
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
|
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
UserModel user = getUserFromAuthManager();
|
|
||||||
if (user == null) {
|
UserModel user = getUser(true);
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
|
||||||
|
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.setFirstName(formData.getFirst("firstName"));
|
||||||
user.setLastName(formData.getFirst("lastName"));
|
user.setLastName(formData.getFirst("lastName"));
|
||||||
user.setEmail(formData.getFirst("email"));
|
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")
|
@Path("totp-remove")
|
||||||
@GET
|
@GET
|
||||||
public Response processTotpRemove() {
|
public Response processTotpRemove() {
|
||||||
UserModel user = getUserFromAuthManager();
|
UserModel user = getUser(true);
|
||||||
user.setTotp(false);
|
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();
|
.setUser(user).forwardToTotp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,36 +160,26 @@ public class AccountService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
|
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
UserModel user = getUserFromAuthManager();
|
UserModel user = getUser(true);
|
||||||
if (user == null) {
|
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
FormFlows forms = Flows.forms(realm, request, uriInfo);
|
|
||||||
|
|
||||||
String totp = formData.getFirst("totp");
|
String totp = formData.getFirst("totp");
|
||||||
String totpSecret = formData.getFirst("totpSecret");
|
String totpSecret = formData.getFirst("totpSecret");
|
||||||
|
|
||||||
String error = null;
|
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
||||||
|
|
||||||
if (Validation.isEmpty(totp)) {
|
if (Validation.isEmpty(totp)) {
|
||||||
error = Messages.MISSING_TOTP;
|
return forms.setError(Messages.MISSING_TOTP).forwardToTotp();
|
||||||
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
|
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
|
||||||
error = Messages.INVALID_TOTP;
|
return forms.setError(Messages.INVALID_TOTP).forwardToTotp();
|
||||||
}
|
|
||||||
|
|
||||||
if (error != null) {
|
|
||||||
return forms.setError(error).setUser(user).forwardToTotp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(CredentialRepresentation.TOTP);
|
credentials.setType(CredentialRepresentation.TOTP);
|
||||||
credentials.setValue(formData.getFirst("totpSecret"));
|
credentials.setValue(totpSecret);
|
||||||
realm.updateCredential(user, credentials);
|
realm.updateCredential(user, credentials);
|
||||||
|
|
||||||
user.setTotp(true);
|
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();
|
.setUser(user).forwardToTotp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,10 +187,7 @@ public class AccountService {
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
|
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
|
||||||
UserModel user = getUserFromAuthManager();
|
UserModel user = getUser(true);
|
||||||
if (user == null) {
|
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
||||||
|
|
||||||
|
@ -172,71 +196,133 @@ public class AccountService {
|
||||||
String passwordConfirm = formData.getFirst("password-confirm");
|
String passwordConfirm = formData.getFirst("password-confirm");
|
||||||
|
|
||||||
if (Validation.isEmpty(passwordNew)) {
|
if (Validation.isEmpty(passwordNew)) {
|
||||||
forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
|
return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
|
||||||
} else if (!passwordNew.equals(passwordConfirm)) {
|
} else if (!passwordNew.equals(passwordConfirm)) {
|
||||||
forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
|
return forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Validation.isEmpty(password)) {
|
if (Validation.isEmpty(password)) {
|
||||||
forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
|
return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword();
|
||||||
} else if (!realm.validatePassword(user, password)) {
|
} 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();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
credentials.setType(CredentialRepresentation.PASSWORD);
|
||||||
credentials.setValue(passwordNew);
|
credentials.setValue(passwordNew);
|
||||||
|
|
||||||
realm.updateCredential(user, credentials);
|
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
|
@GET
|
||||||
public Response accountPage() {
|
public Response loginRedirect(@QueryParam("code") String code,
|
||||||
UserModel user = getUserFromAuthManager();
|
@QueryParam("state") String state,
|
||||||
if (user != null) {
|
@QueryParam("error") String error,
|
||||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount();
|
@Context HttpHeaders headers) {
|
||||||
} else {
|
try {
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
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
|
@GET
|
||||||
public Response socialPage() {
|
public Response logout() {
|
||||||
UserModel user = getUserFromAuthManager();
|
// TODO Should use single-sign out via TokenService
|
||||||
if (user != null) {
|
URI baseUri = Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId());
|
||||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToSocial();
|
authManager.expireIdentityCookie(realm, uriInfo);
|
||||||
} else {
|
authManager.expireAccountIdentityCookie(baseUri);
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
return Response.status(302).location(baseUri).build();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("totp")
|
private Response login(String path) {
|
||||||
@GET
|
JaxrsOAuthClient oauth = new JaxrsOAuthClient();
|
||||||
public Response totpPage() {
|
String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getId()).toString();
|
||||||
UserModel user = getUserFromAuthManager();
|
oauth.setAuthUrl(authUrl);
|
||||||
if (user != null) {
|
|
||||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp();
|
oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
|
||||||
} else {
|
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId());
|
||||||
}
|
|
||||||
|
oauth.setStateCookiePath(accountUri.getPath());
|
||||||
|
return oauth.redirect(uriInfo, accountUri.toString(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("password")
|
private UserModel getUser(boolean required) {
|
||||||
@GET
|
UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers);
|
||||||
public Response passwordPage() {
|
if (user == null && required) {
|
||||||
UserModel user = getUserFromAuthManager();
|
throw new ForbiddenException();
|
||||||
if (user == null) {
|
|
||||||
return Response.status(Status.FORBIDDEN).build();
|
|
||||||
}
|
}
|
||||||
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword();
|
return user;
|
||||||
}
|
|
||||||
|
|
||||||
private UserModel getUserFromAuthManager() {
|
|
||||||
return authManager.authenticateIdentityCookie(realm, uriInfo, headers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ public class KeycloakApplication extends Application {
|
||||||
singletons.add(new SaasService(tokenManager));
|
singletons.add(new SaasService(tokenManager));
|
||||||
singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
|
singletons.add(new SocialResource(tokenManager, new SocialRequestManager()));
|
||||||
classes.add(SkeletonKeyContextResolver.class);
|
classes.add(SkeletonKeyContextResolver.class);
|
||||||
|
classes.add(QRCodeResource.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakSessionFactory createSessionFactory() {
|
protected KeycloakSessionFactory createSessionFactory() {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.services.resources;
|
package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.resteasy.logging.Logger;
|
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.RealmManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -66,7 +68,14 @@ public class RealmsResource {
|
||||||
logger.debug("realm not found");
|
logger.debug("realm not found");
|
||||||
throw new NotFoundException();
|
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);
|
resourceContext.initResource(accountService);
|
||||||
return accountService;
|
return accountService;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,12 @@ public class RequiredActionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
UserModel user = getUser(accessCode);
|
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.setFirstName(formData.getFirst("firstName"));
|
||||||
user.setLastName(formData.getFirst("lastName"));
|
user.setLastName(formData.getFirst("lastName"));
|
||||||
user.setEmail(formData.getFirst("email"));
|
user.setEmail(formData.getFirst("email"));
|
||||||
|
@ -121,7 +127,7 @@ public class RequiredActionsService {
|
||||||
|
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(CredentialRepresentation.TOTP);
|
credentials.setType(CredentialRepresentation.TOTP);
|
||||||
credentials.setValue(formData.getFirst("totpSecret"));
|
credentials.setValue(totpSecret);
|
||||||
realm.updateCredential(user, credentials);
|
realm.updateCredential(user, credentials);
|
||||||
|
|
||||||
user.setTotp(true);
|
user.setTotp(true);
|
||||||
|
@ -146,15 +152,14 @@ public class RequiredActionsService {
|
||||||
|
|
||||||
UserModel user = getUser(accessCode);
|
UserModel user = getUser(accessCode);
|
||||||
|
|
||||||
String password = formData.getFirst("password");
|
|
||||||
String passwordNew = formData.getFirst("password-new");
|
String passwordNew = formData.getFirst("password-new");
|
||||||
String passwordConfirm = formData.getFirst("password-confirm");
|
String passwordConfirm = formData.getFirst("password-confirm");
|
||||||
|
|
||||||
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
|
||||||
if (Validation.isEmpty(passwordNew)) {
|
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)) {
|
} 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();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
|
@ -257,7 +262,7 @@ public class RequiredActionsService {
|
||||||
|
|
||||||
new EmailSender().sendPasswordReset(user, realm, accessCode, uriInfo);
|
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();
|
.forwardToPasswordReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -415,7 +415,8 @@ public class TokenService {
|
||||||
}
|
}
|
||||||
logger.info("accessRequest SUCCESS");
|
logger.info("accessRequest SUCCESS");
|
||||||
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
|
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) {
|
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.services.managers.AccessCodeEntry;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.picketlink.idm.model.sample.Realm;
|
import org.picketlink.idm.model.sample.Realm;
|
||||||
|
|
||||||
import javax.imageio.spi.ServiceRegistry;
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
@ -58,8 +59,8 @@ public class FormFlows {
|
||||||
// TODO refactor/rename "error" to "message" everywhere where it makes sense
|
// TODO refactor/rename "error" to "message" everywhere where it makes sense
|
||||||
private String error;
|
private String error;
|
||||||
|
|
||||||
public static enum ErrorType {SUCCESS, WARNING, ERROR};
|
public static enum MessageType {SUCCESS, WARNING, ERROR};
|
||||||
private ErrorType errorType;
|
private MessageType messageType = MessageType.ERROR;
|
||||||
|
|
||||||
private MultivaluedMap<String, String> formData;
|
private MultivaluedMap<String, String> formData;
|
||||||
|
|
||||||
|
@ -79,16 +80,17 @@ public class FormFlows {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response forwardToAction(RequiredAction action) {
|
public Response forwardToAction(RequiredAction action) {
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case CONFIGURE_TOTP:
|
case CONFIGURE_TOTP:
|
||||||
return forwardToForm(Pages.LOGIN_CONFIG_TOTP);
|
return forwardToActionForm(Pages.LOGIN_CONFIG_TOTP, Messages.ACTION_WARN_TOTP);
|
||||||
case UPDATE_PROFILE:
|
case UPDATE_PROFILE:
|
||||||
return forwardToForm(Pages.LOGIN_UPDATE_PROFILE);
|
return forwardToActionForm(Pages.LOGIN_UPDATE_PROFILE, Messages.ACTION_WARN_PROFILE);
|
||||||
case UPDATE_PASSWORD:
|
case UPDATE_PASSWORD:
|
||||||
return forwardToForm(Pages.LOGIN_UPDATE_PASSWORD);
|
return forwardToActionForm(Pages.LOGIN_UPDATE_PASSWORD, Messages.ACTION_WARN_PASSWD);
|
||||||
case VERIFY_EMAIL:
|
case VERIFY_EMAIL:
|
||||||
new EmailSender().sendEmailVerification(userModel, realm, accessCode, uriInfo);
|
new EmailSender().sendEmailVerification(userModel, realm, accessCode, uriInfo);
|
||||||
return forwardToForm(Pages.LOGIN_VERIFY_EMAIL);
|
return forwardToActionForm(Pages.LOGIN_VERIFY_EMAIL, Messages.ACTION_WARN_EMAIL);
|
||||||
default:
|
default:
|
||||||
return Response.serverError().build();
|
return Response.serverError().build();
|
||||||
}
|
}
|
||||||
|
@ -103,7 +105,6 @@ public class FormFlows {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response forwardToForm(String template, FormService.FormServiceDataBean formDataBean) {
|
private Response forwardToForm(String template, FormService.FormServiceDataBean formDataBean) {
|
||||||
formDataBean.setErrorType(errorType == null ? ErrorType.ERROR : errorType);
|
|
||||||
|
|
||||||
// Getting URI needed by form processing service
|
// Getting URI needed by form processing service
|
||||||
ResteasyUriInfo uriInfo = request.getUri();
|
ResteasyUriInfo uriInfo = request.getUri();
|
||||||
|
@ -140,11 +141,24 @@ public class FormFlows {
|
||||||
return Response.status(200).entity("form provider not found").build();
|
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);
|
FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error);
|
||||||
return forwardToForm(template, formDataBean);
|
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() {
|
public Response forwardToLogin() {
|
||||||
|
@ -202,8 +216,8 @@ public class FormFlows {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormFlows setErrorType(ErrorType errorType) {
|
public FormFlows setErrorType(MessageType errorType) {
|
||||||
this.errorType = errorType;
|
this.messageType = errorType;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,12 +35,16 @@ public class Urls {
|
||||||
return accountBase(baseUri).path(AccountService.class, "accessPage").build(realmId);
|
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");
|
return realmBase(baseUri).path(RealmsResource.class, "getAccountService");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static URI accountPage(URI baseUri, String realmId) {
|
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) {
|
public static URI accountPasswordPage(URI baseUri, String realmId) {
|
||||||
|
@ -59,6 +63,10 @@ public class Urls {
|
||||||
return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId);
|
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) {
|
public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
|
||||||
return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId);
|
return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,22 @@ public class Validation {
|
||||||
return null;
|
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) {
|
public static boolean isEmpty(String s) {
|
||||||
return s == null || s.length() == 0;
|
return s == null || s.length() == 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
package org.keycloak.test;
|
package org.keycloak.test;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.*;
|
||||||
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.representations.idm.ApplicationRepresentation;
|
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||||
import org.keycloak.services.managers.ApplicationManager;
|
import org.keycloak.services.managers.ApplicationManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
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>
|
* @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-1");
|
||||||
application.getApplicationUser().addRedirectUri("redirect-2");
|
application.getApplicationUser().addRedirectUri("redirect-2");
|
||||||
|
|
||||||
|
application.getApplicationUser().addWebOrigin("origin-1");
|
||||||
|
application.getApplicationUser().addWebOrigin("origin-2");
|
||||||
|
|
||||||
application.updateApplication();
|
application.updateApplication();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +85,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
|
||||||
UserModel euser = expected.getApplicationUser();
|
UserModel euser = expected.getApplicationUser();
|
||||||
|
|
||||||
Assert.assertTrue(euser.getRedirectUris().containsAll(auser.getRedirectUris()));
|
Assert.assertTrue(euser.getRedirectUris().containsAll(auser.getRedirectUris()));
|
||||||
|
Assert.assertTrue(euser.getWebOrigins().containsAll(auser.getWebOrigins()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
|
public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
|
||||||
|
|
|
@ -53,11 +53,34 @@ public class UserModelTest extends AbstractKeycloakServerTest {
|
||||||
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||||
user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
||||||
|
|
||||||
|
user.addWebOrigin("origin-1");
|
||||||
|
user.addWebOrigin("origin-2");
|
||||||
|
|
||||||
UserModel persisted = manager.getRealm(realm.getId()).getUser("user");
|
UserModel persisted = manager.getRealm(realm.getId()).getUser("user");
|
||||||
|
|
||||||
assertEquals(user, persisted);
|
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
|
@Test
|
||||||
public void testUserRequiredActions() throws Exception {
|
public void testUserRequiredActions() throws Exception {
|
||||||
RealmModel realm = manager.createRealm("original");
|
RealmModel realm = manager.createRealm("original");
|
||||||
|
@ -102,7 +125,7 @@ public class UserModelTest extends AbstractKeycloakServerTest {
|
||||||
Assert.assertEquals(expected.getLastName(), actual.getLastName());
|
Assert.assertEquals(expected.getLastName(), actual.getLastName());
|
||||||
Assert.assertArrayEquals(expected.getRedirectUris().toArray(), actual.getRedirectUris().toArray());
|
Assert.assertArrayEquals(expected.getRedirectUris().toArray(), actual.getRedirectUris().toArray());
|
||||||
Assert.assertArrayEquals(expected.getRequiredActions().toArray(), actual.getRequiredActions().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) {
|
public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
|
||||||
|
|
|
@ -101,6 +101,10 @@
|
||||||
<groupId>org.picketlink</groupId>
|
<groupId>org.picketlink</groupId>
|
||||||
<artifactId>picketlink-config</artifactId>
|
<artifactId>picketlink-config</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
|
|
@ -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
|
|
@ -44,8 +44,8 @@ import org.openqa.selenium.WebDriver;
|
||||||
*/
|
*/
|
||||||
public class RequiredActionUpdateProfileTest {
|
public class RequiredActionUpdateProfileTest {
|
||||||
|
|
||||||
@ClassRule
|
@Rule
|
||||||
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
|
public KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
|
||||||
|
@ -83,4 +83,50 @@ public class RequiredActionUpdateProfileTest {
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.forms;
|
package org.keycloak.testsuite.forms;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.*;
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.ClassRule;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.models.RealmModel;
|
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.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,13 +95,22 @@ public class AccountTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void changePassword() {
|
public void changePassword() {
|
||||||
loginPage.open();
|
changePasswordPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
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");
|
changePasswordPage.changePassword("password", "new-password", "new-password");
|
||||||
|
|
||||||
oauth.openLogout();
|
Assert.assertTrue(profilePage.isSuccess());
|
||||||
|
|
||||||
|
changePasswordPage.logout();
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
@ -118,17 +125,38 @@ public class AccountTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void changeProfile() {
|
public void changeProfile() {
|
||||||
loginPage.open();
|
profilePage.open();
|
||||||
loginPage.login("test-user@localhost", "password");
|
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.getFirstName());
|
||||||
Assert.assertEquals("", profilePage.getLastName());
|
Assert.assertEquals("", profilePage.getLastName());
|
||||||
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
|
||||||
|
|
||||||
profilePage.updateProfile("New first", "New last", "new@email.com");
|
profilePage.updateProfile("New first", "New last", "new@email.com");
|
||||||
|
|
||||||
|
Assert.assertTrue(profilePage.isSuccess());
|
||||||
Assert.assertEquals("New first", profilePage.getFirstName());
|
Assert.assertEquals("New first", profilePage.getFirstName());
|
||||||
Assert.assertEquals("New last", profilePage.getLastName());
|
Assert.assertEquals("New last", profilePage.getLastName());
|
||||||
Assert.assertEquals("new@email.com", profilePage.getEmail());
|
Assert.assertEquals("new@email.com", profilePage.getEmail());
|
||||||
|
@ -136,17 +164,22 @@ public class AccountTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setupTotp() {
|
public void setupTotp() {
|
||||||
loginPage.open();
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
|
||||||
|
|
||||||
totpPage.open();
|
totpPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
Assert.assertTrue(totpPage.isCurrent());
|
Assert.assertTrue(totpPage.isCurrent());
|
||||||
|
|
||||||
Assert.assertFalse(driver.getPageSource().contains("Remove Google"));
|
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()));
|
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||||
|
|
||||||
|
Assert.assertTrue(profilePage.isSuccess());
|
||||||
|
|
||||||
Assert.assertTrue(driver.getPageSource().contains("Remove Google"));
|
Assert.assertTrue(driver.getPageSource().contains("Remove Google"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ public class ResetPasswordTest {
|
||||||
resetPasswordPage.assertCurrent();
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
|
Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
|
||||||
Assert.assertEquals("Error!", resetPasswordPage.getMessage());
|
Assert.assertEquals("Invalid username or email.", resetPasswordPage.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -138,7 +138,7 @@ public class ResetPasswordTest {
|
||||||
resetPasswordPage.assertCurrent();
|
resetPasswordPage.assertCurrent();
|
||||||
|
|
||||||
Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
|
Assert.assertNotEquals("Success!", resetPasswordPage.getMessage());
|
||||||
Assert.assertEquals("Error!", resetPasswordPage.getMessage());
|
Assert.assertEquals("Invalid username or email.", resetPasswordPage.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source.
|
||||||
|
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||||
|
* as indicated by the @author tags. See the copyright.txt file in the
|
||||||
|
* distribution for a full listing of individual contributors.
|
||||||
|
*
|
||||||
|
* This is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this software; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||||
|
*/
|
||||||
|
package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAccountPage extends AbstractPage {
|
||||||
|
|
||||||
|
@FindBy(linkText = "Logout")
|
||||||
|
private WebElement logoutLink;
|
||||||
|
|
||||||
|
public void logout() {
|
||||||
|
logoutLink.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ import org.openqa.selenium.WebDriver;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public abstract class Page {
|
public abstract class AbstractPage {
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected WebDriver driver;
|
protected WebDriver driver;
|
|
@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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";
|
private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/password";
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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";
|
private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/totp";
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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";
|
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\"]")
|
@FindBy(css = "button[type=\"submit\"]")
|
||||||
private WebElement submitButton;
|
private WebElement submitButton;
|
||||||
|
|
||||||
|
@FindBy(css = ".feedback > p > strong")
|
||||||
|
private WebElement feedbackMessage;
|
||||||
|
|
||||||
public void updateProfile(String firstName, String lastName, String email) {
|
public void updateProfile(String firstName, String lastName, String email) {
|
||||||
firstNameInput.clear();
|
firstNameInput.clear();
|
||||||
firstNameInput.sendKeys(firstName);
|
firstNameInput.sendKeys(firstName);
|
||||||
|
@ -75,4 +78,11 @@ public class AccountUpdateProfilePage extends Page {
|
||||||
driver.navigate().to(PATH);
|
driver.navigate().to(PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess(){
|
||||||
|
return feedbackMessage != null && "Success!".equals(feedbackMessage.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isError(){
|
||||||
|
return feedbackMessage != null && "Error!".equals(feedbackMessage.getText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ package org.keycloak.testsuite.pages;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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";
|
private String baseUrl = "http://localhost:8081/app";
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,13 @@ package org.keycloak.testsuite.pages;
|
||||||
|
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.openqa.selenium.By;
|
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class ErrorPage extends Page {
|
public class ErrorPage extends AbstractPage {
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected OAuthClient oauth;
|
protected OAuthClient oauth;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginConfigTotpPage extends Page {
|
public class LoginConfigTotpPage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(id = "totpSecret")
|
@FindBy(id = "totpSecret")
|
||||||
private WebElement totpSecret;
|
private WebElement totpSecret;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginPage extends Page {
|
public class LoginPage extends AbstractPage {
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected OAuthClient oauth;
|
protected OAuthClient oauth;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginPasswordResetPage extends Page {
|
public class LoginPasswordResetPage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(id = "username")
|
@FindBy(id = "username")
|
||||||
private WebElement usernameInput;
|
private WebElement usernameInput;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginPasswordUpdatePage extends Page {
|
public class LoginPasswordUpdatePage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(id = "password-new")
|
@FindBy(id = "password-new")
|
||||||
private WebElement newPasswordInput;
|
private WebElement newPasswordInput;
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginTotpPage extends Page {
|
public class LoginTotpPage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(id = "totp")
|
@FindBy(id = "totp")
|
||||||
private WebElement totpInput;
|
private WebElement totpInput;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class LoginUpdateProfilePage extends Page {
|
public class LoginUpdateProfilePage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(id = "firstName")
|
@FindBy(id = "firstName")
|
||||||
private WebElement firstNameInput;
|
private WebElement firstNameInput;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @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\"]")
|
@FindBy(css = "input[name=\"accept\"]")
|
||||||
private WebElement acceptButton;
|
private WebElement acceptButton;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class RegisterPage extends Page {
|
public class RegisterPage extends AbstractPage {
|
||||||
|
|
||||||
@FindBy(id = "firstName")
|
@FindBy(id = "firstName")
|
||||||
private WebElement firstNameInput;
|
private WebElement firstNameInput;
|
||||||
|
@ -85,6 +85,22 @@ public class RegisterPage extends Page {
|
||||||
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
|
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() {
|
public boolean isCurrent() {
|
||||||
return driver.getTitle().equals("Register with test");
|
return driver.getTitle().equals("Register with test");
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
|
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
|
||||||
*/
|
*/
|
||||||
public class VerifyEmailPage extends Page {
|
public class VerifyEmailPage extends AbstractPage {
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected OAuthClient oauth;
|
protected OAuthClient oauth;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import java.lang.reflect.Field;
|
||||||
|
|
||||||
import org.junit.rules.ExternalResource;
|
import org.junit.rules.ExternalResource;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
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.WebDriver;
|
||||||
import org.openqa.selenium.chrome.ChromeDriver;
|
import org.openqa.selenium.chrome.ChromeDriver;
|
||||||
import org.openqa.selenium.firefox.FirefoxDriver;
|
import org.openqa.selenium.firefox.FirefoxDriver;
|
||||||
|
@ -78,7 +78,7 @@ public class WebRule extends ExternalResource {
|
||||||
Class<?> type = f.getType();
|
Class<?> type = f.getType();
|
||||||
if (type.equals(WebDriver.class)) {
|
if (type.equals(WebDriver.class)) {
|
||||||
set(f, o, driver);
|
set(f, o, driver);
|
||||||
} else if (Page.class.isAssignableFrom(type)) {
|
} else if (AbstractPage.class.isAssignableFrom(type)) {
|
||||||
set(f, o, getPage(f.getType()));
|
set(f, o, getPage(f.getType()));
|
||||||
} else if (type.equals(OAuthClient.class)) {
|
} else if (type.equals(OAuthClient.class)) {
|
||||||
set(f, o, oauth);
|
set(f, o, oauth);
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.pages.RegisterPage;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
|
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
@ -68,6 +69,9 @@ public class SocialLoginTest {
|
||||||
@WebResource
|
@WebResource
|
||||||
protected LoginPage loginPage;
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected RegisterPage registerPage;
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected OAuthClient oauth;
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
@ -97,4 +101,41 @@ public class SocialLoginTest {
|
||||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("user"));
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"sslNotRequired": true,
|
"sslNotRequired": true,
|
||||||
"cookieLoginAllowed": true,
|
"cookieLoginAllowed": true,
|
||||||
"registrationAllowed": true,
|
"registrationAllowed": true,
|
||||||
|
"accountManagement": true,
|
||||||
"resetPasswordAllowed": true,
|
"resetPasswordAllowed": true,
|
||||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
"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",
|
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
|
|