Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
0d9e14d4c7
61 changed files with 4521 additions and 6804 deletions
|
@ -37,6 +37,11 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mongodb</groupId>
|
<groupId>org.mongodb</groupId>
|
||||||
<artifactId>mongo-java-driver</artifactId>
|
<artifactId>mongo-java-driver</artifactId>
|
||||||
|
@ -85,9 +90,6 @@
|
||||||
<keycloak.audit.mongo.db>${keycloak.audit.mongo.db}</keycloak.audit.mongo.db>
|
<keycloak.audit.mongo.db>${keycloak.audit.mongo.db}</keycloak.audit.mongo.db>
|
||||||
<keycloak.audit.mongo.clearOnStartup>${keycloak.audit.mongo.clearOnStartup}</keycloak.audit.mongo.clearOnStartup>
|
<keycloak.audit.mongo.clearOnStartup>${keycloak.audit.mongo.clearOnStartup}</keycloak.audit.mongo.clearOnStartup>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
<dependenciesToScan>
|
|
||||||
<dependency>org.keycloak:keycloak-model-tests</dependency>
|
|
||||||
</dependenciesToScan>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.mongodb.MongoClient;
|
||||||
import com.mongodb.MongoCredential;
|
import com.mongodb.MongoCredential;
|
||||||
import com.mongodb.ServerAddress;
|
import com.mongodb.ServerAddress;
|
||||||
import com.mongodb.WriteConcern;
|
import com.mongodb.WriteConcern;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.audit.AuditProvider;
|
import org.keycloak.audit.AuditProvider;
|
||||||
import org.keycloak.audit.AuditProviderFactory;
|
import org.keycloak.audit.AuditProviderFactory;
|
||||||
|
@ -21,6 +22,8 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class MongoAuditProviderFactory implements AuditProviderFactory {
|
public class MongoAuditProviderFactory implements AuditProviderFactory {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(MongoAuditProviderFactory.class);
|
||||||
|
|
||||||
public static final String ID = "mongo";
|
public static final String ID = "mongo";
|
||||||
private MongoClient client;
|
private MongoClient client;
|
||||||
private DB db;
|
private DB db;
|
||||||
|
@ -55,6 +58,8 @@ public class MongoAuditProviderFactory implements AuditProviderFactory {
|
||||||
if (clearOnStartup) {
|
if (clearOnStartup) {
|
||||||
db.getCollection("audit").drop();
|
db.getCollection("audit").drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.infof("Initialized mongo audit. host: %s, port: %d, db: %s, clearOnStartup: %b", host, port, dbName, clearOnStartup);
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,13 @@
|
||||||
<exclude>keycloak-ds.xml</exclude>
|
<exclude>keycloak-ds.xml</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF</directory>
|
||||||
|
<outputDirectory>keycloak/standalone/configuration</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>keycloak-server.json</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>${project.build.directory}/unpacked/themes</directory>
|
<directory>${project.build.directory}/unpacked/themes</directory>
|
||||||
<outputDirectory>keycloak/standalone/configuration/themes</outputDirectory>
|
<outputDirectory>keycloak/standalone/configuration/themes</outputDirectory>
|
||||||
|
|
|
@ -19,5 +19,12 @@
|
||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>deployments</outputDirectory>
|
<outputDirectory>deployments</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF</directory>
|
||||||
|
<outputDirectory>configuration</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>keycloak-server.json</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
|
|
@ -33,8 +33,6 @@
|
||||||
<para>
|
<para>
|
||||||
Twitter doesn't allow <literal>localhost</literal> in the redirect URI. To test on a local server
|
Twitter doesn't allow <literal>localhost</literal> in the redirect URI. To test on a local server
|
||||||
replace <literal>localhost</literal> with <literal>127.0.0.1</literal>.
|
replace <literal>localhost</literal> with <literal>127.0.0.1</literal>.
|
||||||
Twitter also restricts connection to TLS/SSL connections only, so you are required to use HTTPS to access
|
|
||||||
Keycloak to enable log in with Twitter.
|
|
||||||
</para>
|
</para>
|
||||||
</tip>
|
</tip>
|
||||||
</section>
|
</section>
|
|
@ -24,7 +24,7 @@
|
||||||
<td>${event.date?datetime}</td>
|
<td>${event.date?datetime}</td>
|
||||||
<td>${event.event}</td>
|
<td>${event.event}</td>
|
||||||
<td>${event.ipAddress}</td>
|
<td>${event.ipAddress}</td>
|
||||||
<td>${event.client}</td>
|
<td>${event.client!}</td>
|
||||||
<td><#list event.details as detail>${detail.key} = ${detail.value} <#if detail_has_next>, </#if></#list></td>
|
<td><#list event.details as detail>${detail.key} = ${detail.value} <#if detail_has_next>, </#if></#list></td>
|
||||||
</tr>
|
</tr>
|
||||||
</#list>
|
</#list>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>IP</td>
|
<td>IP</td>
|
||||||
|
@ -27,18 +27,14 @@
|
||||||
<td>${session.lastAccess?datetime}</td>
|
<td>${session.lastAccess?datetime}</td>
|
||||||
<td>${session.expires?datetime}</td>
|
<td>${session.expires?datetime}</td>
|
||||||
<td>
|
<td>
|
||||||
<ul style="list-style: none; ">
|
<#list session.applications as app>
|
||||||
<#list session.applications as app>
|
${app}<br/>
|
||||||
<li>${app}</li>
|
</#list>
|
||||||
</#list>
|
|
||||||
</ul>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ul style="list-style: none; ">
|
<#list session.clients as client>
|
||||||
<#list session.clients as client>
|
${client}<br/>
|
||||||
<li>${client}</li>
|
</#list>
|
||||||
</#list>
|
|
||||||
</ul>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</#list>
|
</#list>
|
||||||
|
|
|
@ -91,7 +91,7 @@ header .navbar {
|
||||||
background-color: #c7e5f0;
|
background-color: #c7e5f0;
|
||||||
border-color: #56bae0;
|
border-color: #56bae0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-image: url(../img/icon-sidebar-active.svg);
|
background-image: url(../img/icon-sidebar-active.png);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right center;
|
background-position: right center;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 202 B |
|
@ -15,7 +15,7 @@
|
||||||
<script src="lib/angular/angular.js"></script>
|
<script src="lib/angular/angular.js"></script>
|
||||||
<script src="lib/angular/angular-resource.js"></script>
|
<script src="lib/angular/angular-resource.js"></script>
|
||||||
<script src="lib/angular/angular-route.js"></script>
|
<script src="lib/angular/angular-route.js"></script>
|
||||||
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
<script src="lib/angular/ui-bootstrap-tpls-0.11.0.js"></script>
|
||||||
|
|
||||||
<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
|
<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
|
||||||
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
|
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
|
||||||
|
@ -39,6 +39,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="admin-console" data-ng-controller="GlobalCtrl" data-ng-cloak>
|
<body class="admin-console" data-ng-controller="GlobalCtrl" data-ng-cloak>
|
||||||
|
|
||||||
<div id="idletimeout">
|
<div id="idletimeout">
|
||||||
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
|
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
|
||||||
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
|
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
|
||||||
|
|
|
@ -2,9 +2,40 @@
|
||||||
|
|
||||||
var module = angular.module('keycloak.services', [ 'ngResource', 'ngRoute' ]);
|
var module = angular.module('keycloak.services', [ 'ngResource', 'ngRoute' ]);
|
||||||
|
|
||||||
module.service('Dialog', function($dialog) {
|
module.service('Dialog', function($modal) {
|
||||||
var dialog = {};
|
var dialog = {};
|
||||||
|
|
||||||
|
var openDialog = function(title, message, btns) {
|
||||||
|
var controller = function($scope, $modalInstance, title, message, btns) {
|
||||||
|
$scope.title = title;
|
||||||
|
$scope.message = message;
|
||||||
|
$scope.btns = btns;
|
||||||
|
|
||||||
|
$scope.ok = function () {
|
||||||
|
$modalInstance.close();
|
||||||
|
};
|
||||||
|
$scope.cancel = function () {
|
||||||
|
$modalInstance.dismiss('cancel');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return $modal.open({
|
||||||
|
templateUrl: 'templates/kc-modal.html',
|
||||||
|
controller: controller,
|
||||||
|
resolve: {
|
||||||
|
title: function() {
|
||||||
|
return title;
|
||||||
|
},
|
||||||
|
message: function() {
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
btns: function() {
|
||||||
|
return btns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).result;
|
||||||
|
}
|
||||||
|
|
||||||
var escapeHtml = function(str) {
|
var escapeHtml = function(str) {
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.appendChild(document.createTextNode(str));
|
div.appendChild(document.createTextNode(str));
|
||||||
|
@ -13,64 +44,53 @@ module.service('Dialog', function($dialog) {
|
||||||
|
|
||||||
dialog.confirmDelete = function(name, type, success) {
|
dialog.confirmDelete = function(name, type, success) {
|
||||||
var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
|
var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
|
||||||
var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' <strong>' + escapeHtml(name) + '</strong>?</span>';
|
var msg = 'Are you sure you want to permanently delete the ' + type + ' ' + name + '?';
|
||||||
var btns = [ {
|
var btns = {
|
||||||
result : 'cancel',
|
ok: {
|
||||||
label : 'Cancel',
|
label: 'Delete',
|
||||||
cssClass : 'btn btn-default'
|
cssClass: 'btn btn-danger'
|
||||||
}, {
|
},
|
||||||
result : 'ok',
|
cancel: {
|
||||||
label : 'Delete',
|
label: 'Cancel',
|
||||||
cssClass : 'btn btn-danger'
|
cssClass: 'btn btn-default'
|
||||||
} ];
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
openDialog(title, msg, btns).then(success);
|
||||||
if (result == "ok") {
|
|
||||||
success();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.confirmGenerateKeys = function(name, type, success) {
|
dialog.confirmGenerateKeys = function(name, type, success) {
|
||||||
var title = 'Generate new keys for realm';
|
var title = 'Generate new keys for realm';
|
||||||
var msg = '<span class="primary">Are you sure you want to permanently generate new keys for <strong>' + name + '</strong>?</span>';
|
var msg = 'Are you sure you want to permanently generate new keys for ' + name + '?';
|
||||||
var btns = [ {
|
var btns = {
|
||||||
result : 'cancel',
|
ok: {
|
||||||
label : 'Cancel',
|
label: 'Generate Keys',
|
||||||
cssClass : 'btn btn-default'
|
cssClass: 'btn btn-danger'
|
||||||
}, {
|
},
|
||||||
result : 'ok',
|
cancel: {
|
||||||
label : 'Generate new keys',
|
label: 'Cancel',
|
||||||
cssClass : 'btn btn-danger'
|
cssClass: 'btn btn-default'
|
||||||
} ];
|
|
||||||
|
|
||||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
|
||||||
if (result == "ok") {
|
|
||||||
success();
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
openDialog(title, msg, btns).then(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.confirm = function(title, message, success, cancel) {
|
dialog.confirm = function(title, message, success, cancel) {
|
||||||
var title = title;
|
var title = title;
|
||||||
var msg = '<span class="primary">' + message + '"</span>';
|
var msg = '<span class="primary">' + message + '"</span>';
|
||||||
var btns = [ {
|
var btns = {
|
||||||
result : 'cancel',
|
ok: {
|
||||||
label : 'Cancel',
|
label: title,
|
||||||
cssClass : 'btn btn-default'
|
cssClass: 'btn btn-danger'
|
||||||
}, {
|
},
|
||||||
result : 'ok',
|
cancel: {
|
||||||
label : title,
|
label: 'Cancel',
|
||||||
cssClass : 'btn btn-danger'
|
cssClass: 'btn btn-default'
|
||||||
} ];
|
|
||||||
|
|
||||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
|
||||||
if (result == "ok") {
|
|
||||||
success();
|
|
||||||
} else {
|
|
||||||
cancel && cancel();
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
openDialog(title, msg, btns).then(success).reject(cancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
|
|
|
@ -37,17 +37,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group clearfix block">
|
<div class="form-group clearfix block" data-ng-show="authProvider.providerName">
|
||||||
<label class="col-sm-2 control-label" for="passwordUpdateSupported">Password Update Supported</label>
|
<label class="col-sm-2 control-label" for="passwordUpdateSupported">Password Update Supported</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input ng-model="authProvider.passwordUpdateSupported" name="passwordUpdateSupported" id="passwordUpdateSupported" onoffswitch />
|
<input ng-model="authProvider.passwordUpdateSupported" name="passwordUpdateSupported" id="passwordUpdateSupported" onoffswitch />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend data-ng-show="authProvider.providerName"><span class="text">{{authProvider.providerName|capitalize}}'s provider options</span></legend>
|
|
||||||
<div data-ng-repeat="option in authProviderOptionNames" class="form-group">
|
<div data-ng-repeat="option in authProviderOptionNames" class="form-group">
|
||||||
<label class="col-sm-2 control-label">{{option|capitalize}} </label>
|
<label class="col-sm-2 control-label">{{option|capitalize}} </label>
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,14 @@
|
||||||
<tr ng-repeat="authProvider in authenticationProviders">
|
<tr ng-repeat="authProvider in authenticationProviders">
|
||||||
<td><a href="#/realms/{{realm.realm}}/auth-settings/{{$index}}">{{authProvider.providerName|capitalize}}</a></td>
|
<td><a href="#/realms/{{realm.realm}}/auth-settings/{{$index}}">{{authProvider.providerName|capitalize}}</a></td>
|
||||||
<td>{{authProvider.passwordUpdateSupported}}</td>
|
<td>{{authProvider.passwordUpdateSupported}}</td>
|
||||||
<td>{{authProvider.config}}</td>
|
<td>
|
||||||
|
<table class="table table-striped table-bordered" data-ng-show="authProvider.config">
|
||||||
|
<tr data-ng-repeat="(key, value) in authProvider.config">
|
||||||
|
<td>{{key}}</td>
|
||||||
|
<td>{{value}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-ng-show="!authenticationProviders || authenticationProviders.length == 0">
|
<tr data-ng-show="!authenticationProviders || authenticationProviders.length == 0">
|
||||||
<td>No authentication providers available</td>
|
<td>No authentication providers available</td>
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
<div class="modal-dialog" tabindex="-1" role="dialog">
|
<div class="modal-header">
|
||||||
<div class="modal-content">
|
<button type="button" class="close" ng-click="cancel()">
|
||||||
<div class="modal-header">
|
<span class="pficon pficon-close"></span>
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
|
</button>
|
||||||
<span class="pficon pficon-close"></span>
|
<h4 class="modal-title">{{title}}</h4>
|
||||||
</button>
|
</div>
|
||||||
<h4 class="modal-title" id="myModalLabel">{{ title }}</h4>
|
<div class="modal-body">{{message}}</div>
|
||||||
</div>
|
<div class="modal-footer">
|
||||||
<div class="modal-body"><p ng-bind-html-unsafe="message"></p></div>
|
<button type="button" data-ng-class="btns.cancel.cssClass" ng-click="cancel()">{{btns.cancel.label}}</button>
|
||||||
<div class="modal-footer">
|
<button type="button" data-ng-class="btns.ok.cssClass" ng-click="ok()">{{btns.ok.label}}</button>
|
||||||
<button ng-repeat="btn in buttons" ng-click="close(btn.result)" class="" ng-class="btn.cssClass">{{ btn.label }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
|
@ -1,6 +1,6 @@
|
||||||
<ul class="nav nav-tabs nav-tabs-pf">
|
<ul class="nav nav-tabs nav-tabs-pf">
|
||||||
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
|
<li ng-class="{active: !path[2]}"><a href="#/realms/{{realm.realm}}">General</a></li>
|
||||||
<li ng-class="{active: path[2] == 'social'}" data-ng-show="realm.social && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
|
<li ng-class="{active: path[2] == 'social-settings'}" data-ng-show="realm.social && access.viewRealm"><a href="#/realms/{{realm.realm}}/social-settings">Social</a></li>
|
||||||
<li ng-class="{active: path[2] == 'roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
|
<li ng-class="{active: path[2] == 'roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
|
||||||
<li ng-class="{active: path[2] == 'default-roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/default-roles">Default Roles</a></li>
|
<li ng-class="{active: path[2] == 'default-roles'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/default-roles">Default Roles</a></li>
|
||||||
<li ng-class="{active: path[2] == 'required-credentials'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/required-credentials">Credentials</a></li>
|
<li ng-class="{active: path[2] == 'required-credentials'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/required-credentials">Credentials</a></li>
|
||||||
|
|
|
@ -310,7 +310,7 @@ form .btn + .btn {
|
||||||
background-color: #c7e5f0;
|
background-color: #c7e5f0;
|
||||||
border-color: #56bae0;
|
border-color: #56bae0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-image: url(../img/icon-sidebar-active.svg);
|
background-image: url(../img/icon-sidebar-active.png);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: right center;
|
background-position: right center;
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,7 @@ legend .kc-icon-collapse {
|
||||||
|
|
||||||
.header .navbar-primary li > .select-kc {
|
.header .navbar-primary li > .select-kc {
|
||||||
background-color: #555A5E;
|
background-color: #555A5E;
|
||||||
background-image: url("../img/sprite-arrow-down.svg");
|
background-image: url("../img/sprite-arrow-down.png");
|
||||||
background-position: right -26px;
|
background-position: right -26px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border: 1px solid #676C6E;
|
border: 1px solid #676C6E;
|
||||||
|
|
|
@ -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="23px" height="14px" viewBox="0 0 23 14" enable-background="new 0 0 23 14" xml:space="preserve">
|
|
||||||
<polyline fill="none" stroke="#009AD8" stroke-miterlimit="10" points="1,13 7,7 1,1 "/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 573 B |
|
@ -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="22px" height="52px" viewBox="0 0 22 52" enable-background="new 0 0 22 52" xml:space="preserve">
|
|
||||||
<polygon fill="#828487" points="11,16 6.818,11.818 7.525,11.111 11,14.586 14.475,11.111 15.182,11.818 "/>
|
|
||||||
<polygon fill="#DBDADA" points="11,42 6.818,37.818 7.525,37.111 11,40.586 14.475,37.111 15.182,37.818 "/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 699 B |
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 250 KiB |
|
@ -1,6 +1,2 @@
|
||||||
@import url("../lib/patternfly/css/patternfly.css");
|
@import url("../lib/patternfly/css/patternfly.css");
|
||||||
@import url("../lib/select2-3.4.1/select2.css");
|
@import url("../lib/select2-3.4.1/select2.css");
|
||||||
|
|
||||||
@import url("admin-console.css");
|
|
||||||
@import url("tables.css");
|
|
||||||
@import url("sprites.css");
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,175 +0,0 @@
|
||||||
/**
|
|
||||||
* @license AngularJS v1.0.7
|
|
||||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
|
||||||
* License: MIT
|
|
||||||
*/
|
|
||||||
(function(window, angular, undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var directive = {};
|
|
||||||
|
|
||||||
directive.dropdownToggle =
|
|
||||||
['$document', '$location', '$window',
|
|
||||||
function ($document, $location, $window) {
|
|
||||||
var openElement = null, close;
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.parent().bind('click', function(event) {
|
|
||||||
close && close();
|
|
||||||
});
|
|
||||||
|
|
||||||
element.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
var iWasOpen = false;
|
|
||||||
|
|
||||||
if (openElement) {
|
|
||||||
iWasOpen = openElement === element;
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!iWasOpen){
|
|
||||||
element.parent().addClass('open');
|
|
||||||
openElement = element;
|
|
||||||
|
|
||||||
close = function (event) {
|
|
||||||
event && event.preventDefault();
|
|
||||||
event && event.stopPropagation();
|
|
||||||
$document.unbind('click', close);
|
|
||||||
element.parent().removeClass('open');
|
|
||||||
close = null;
|
|
||||||
openElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$document.bind('click', close);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
|
|
||||||
|
|
||||||
directive.tabbable = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'C',
|
|
||||||
compile: function(element) {
|
|
||||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
|
||||||
tabContent = angular.element('<div class="tab-content"></div>');
|
|
||||||
|
|
||||||
tabContent.append(element.contents());
|
|
||||||
element.append(navTabs).append(tabContent);
|
|
||||||
},
|
|
||||||
controller: ['$scope', '$element', function($scope, $element) {
|
|
||||||
var navTabs = $element.contents().eq(0),
|
|
||||||
ngModel = $element.controller('ngModel') || {},
|
|
||||||
tabs = [],
|
|
||||||
selectedTab;
|
|
||||||
|
|
||||||
ngModel.$render = function() {
|
|
||||||
var $viewValue = this.$viewValue;
|
|
||||||
|
|
||||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
|
||||||
if(selectedTab) {
|
|
||||||
selectedTab.paneElement.removeClass('active');
|
|
||||||
selectedTab.tabElement.removeClass('active');
|
|
||||||
selectedTab = null;
|
|
||||||
}
|
|
||||||
if($viewValue) {
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
|
||||||
if ($viewValue == tabs[i].value) {
|
|
||||||
selectedTab = tabs[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedTab) {
|
|
||||||
selectedTab.paneElement.addClass('active');
|
|
||||||
selectedTab.tabElement.addClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addPane = function(element, attr) {
|
|
||||||
var li = angular.element('<li><a href></a></li>'),
|
|
||||||
a = li.find('a'),
|
|
||||||
tab = {
|
|
||||||
paneElement: element,
|
|
||||||
paneAttrs: attr,
|
|
||||||
tabElement: li
|
|
||||||
};
|
|
||||||
|
|
||||||
tabs.push(tab);
|
|
||||||
|
|
||||||
attr.$observe('value', update)();
|
|
||||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
tab.title = attr.title;
|
|
||||||
tab.value = attr.value || attr.title;
|
|
||||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
}
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
|
|
||||||
navTabs.append(li);
|
|
||||||
li.bind('click', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (ngModel.$setViewValue) {
|
|
||||||
$scope.$apply(function() {
|
|
||||||
ngModel.$setViewValue(tab.value);
|
|
||||||
ngModel.$render();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// we are not part of angular
|
|
||||||
ngModel.$viewValue = tab.value;
|
|
||||||
ngModel.$render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
tab.tabElement.remove();
|
|
||||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
|
||||||
if (tab == tabs[i]) {
|
|
||||||
tabs.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.table = function() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
link: function(scope, element, attrs) {
|
|
||||||
element[0].className = 'table table-bordered table-striped code-table';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
directive.tabPane = function() {
|
|
||||||
return {
|
|
||||||
require: '^tabbable',
|
|
||||||
restrict: 'C',
|
|
||||||
link: function(scope, element, attrs, tabsCtrl) {
|
|
||||||
element.bind('$remove', tabsCtrl.addPane(element, attrs));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
angular.module('bootstrap', []).directive(directive);
|
|
||||||
|
|
||||||
|
|
||||||
})(window, window.angular);
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -10,9 +10,9 @@
|
||||||
<div id="kc-code">
|
<div id="kc-code">
|
||||||
<#if code.success>
|
<#if code.success>
|
||||||
<p>Please copy this code and paste it into your application:</p>
|
<p>Please copy this code and paste it into your application:</p>
|
||||||
<textarea id="code">${code.code}</textarea>
|
<textarea id="code" class="${properties.kcTextareaClass!}">${code.code}</textarea>
|
||||||
<#else>
|
<#else>
|
||||||
<p>${code.error}</p>
|
<p id="error">${code.error}</p>
|
||||||
</#if>
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -186,6 +186,10 @@ ol#kc-totp-settings li:first-of-type {
|
||||||
width: 125px;
|
width: 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zocial:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
.zocial.facebook,
|
.zocial.facebook,
|
||||||
.zocial.github,
|
.zocial.github,
|
||||||
.zocial.google,
|
.zocial.google,
|
||||||
|
|
|
@ -23,5 +23,6 @@ kcInputClass=form-control
|
||||||
kcInputWrapperClass=col-xs-12 col-sm-12 col-md-8 col-lg-9
|
kcInputWrapperClass=col-xs-12 col-sm-12 col-md-8 col-lg-9
|
||||||
kcFormOptionsClass=col-xs-4 col-sm-5 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
|
kcFormOptionsClass=col-xs-4 col-sm-5 col-md-offset-4 col-md-4 col-lg-offset-3 col-lg-5
|
||||||
kcFormButtonsClass=col-xs-8 col-sm-7 col-md-4 col-lg-4 submit
|
kcFormButtonsClass=col-xs-8 col-sm-7 col-md-4 col-lg-4 submit
|
||||||
|
kcTextareaClass=form-control
|
||||||
|
|
||||||
kcInfoAreaClass=col-xs-12 col-sm-4 col-md-4 col-lg-6 details
|
kcInfoAreaClass=col-xs-12 col-sm-4 col-md-4 col-lg-6 details
|
|
@ -532,7 +532,6 @@ var Keycloak = function (config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var src = getRealmUrl() + '/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId);
|
var src = getRealmUrl() + '/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId);
|
||||||
console.log('iframe src='+ src);
|
|
||||||
iframe.setAttribute('src', src );
|
iframe.setAttribute('src', src );
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
@ -571,7 +570,6 @@ var Keycloak = function (config) {
|
||||||
msg.callbackId = createCallbackId();
|
msg.callbackId = createCallbackId();
|
||||||
loginIframe.callbackMap[msg.callbackId] = promise;
|
loginIframe.callbackMap[msg.callbackId] = promise;
|
||||||
var origin = loginIframe.iframeOrigin;
|
var origin = loginIframe.iframeOrigin;
|
||||||
console.log('*** origin: ' + origin);
|
|
||||||
loginIframe.iframe.contentWindow.postMessage(msg, origin);
|
loginIframe.iframe.contentWindow.postMessage(msg, origin);
|
||||||
} else {
|
} else {
|
||||||
promise.setSuccess();
|
promise.setSuccess();
|
||||||
|
@ -612,8 +610,6 @@ var Keycloak = function (config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == 'cordova') {
|
if (type == 'cordova') {
|
||||||
console.debug('Enabling Cordova support');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login: function(options) {
|
login: function(options) {
|
||||||
var promise = createPromise();
|
var promise = createPromise();
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
|
||||||
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
|
HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
|
||||||
HttpSession session = req.getSession(true);
|
HttpSession session = req.getSession(true);
|
||||||
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
|
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
|
||||||
userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session, account.getPrincipal().getName(), account.getKeycloakSecurityContext().getToken().getSessionState());
|
userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager(), session.getId(), account.getPrincipal().getName(), account.getKeycloakSecurityContext().getToken().getSessionState());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import io.undertow.server.session.SessionManager;
|
||||||
import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler;
|
import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -62,9 +61,7 @@ public class UndertowUserSessionManagement implements SessionListener {
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void login(SessionManager manager, HttpSession session, String username, String keycloakSessionId) {
|
public synchronized void login(SessionManager manager, String sessionId, String username, String keycloakSessionId) {
|
||||||
String sessionId = session.getId();
|
|
||||||
|
|
||||||
UserSessions sessions = userSessionMap.get(username);
|
UserSessions sessions = userSessionMap.get(username);
|
||||||
if (sessions == null) {
|
if (sessions == null) {
|
||||||
sessions = new UserSessions();
|
sessions = new UserSessions();
|
||||||
|
|
|
@ -237,7 +237,7 @@ public class MongoStoreImpl implements MongoStore {
|
||||||
public <T extends MongoIdentifiableEntity> T loadEntity(Class<T> type, String id, MongoStoreInvocationContext context) {
|
public <T extends MongoIdentifiableEntity> T loadEntity(Class<T> type, String id, MongoStoreInvocationContext context) {
|
||||||
// First look if we already read the object with this oid and type during this transaction. If yes, use it instead of DB lookup
|
// First look if we already read the object with this oid and type during this transaction. If yes, use it instead of DB lookup
|
||||||
T cached = context.getLoadedEntity(type, id);
|
T cached = context.getLoadedEntity(type, id);
|
||||||
if (cached != null) return cached;
|
if (cached != null && type.isAssignableFrom(cached.getClass())) return cached;
|
||||||
|
|
||||||
DBCollection dbCollection = getDBCollectionForType(type);
|
DBCollection dbCollection = getDBCollectionForType(type);
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> implements ApplicationModel {
|
public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> implements ApplicationModel {
|
||||||
|
|
||||||
public ApplicationAdapter(RealmModel realm, MongoApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) {
|
public ApplicationAdapter(RealmAdapter realm, MongoApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) {
|
||||||
super(realm, applicationEntity, invContext);
|
super(realm, applicationEntity, invContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,11 @@ import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.entities.ClientEntity;
|
import org.keycloak.models.entities.ClientEntity;
|
||||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssociationEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -22,9 +21,9 @@ import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssocia
|
||||||
public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMongoAdapter<T> implements ClientModel {
|
public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMongoAdapter<T> implements ClientModel {
|
||||||
|
|
||||||
protected final T clientEntity;
|
protected final T clientEntity;
|
||||||
private final RealmModel realm;
|
private final RealmAdapter realm;
|
||||||
|
|
||||||
public ClientAdapter(RealmModel realm, T clientEntity, MongoStoreInvocationContext invContext) {
|
public ClientAdapter(RealmAdapter realm, T clientEntity, MongoStoreInvocationContext invContext) {
|
||||||
super(invContext);
|
super(invContext);
|
||||||
this.clientEntity = clientEntity;
|
this.clientEntity = clientEntity;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -154,7 +153,7 @@ public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMo
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmModel getRealm() {
|
public RealmAdapter getRealm() {
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,14 +171,13 @@ public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMo
|
||||||
@Override
|
@Override
|
||||||
public Set<UserSessionModel> getUserSessions() {
|
public Set<UserSessionModel> getUserSessions() {
|
||||||
DBObject query = new QueryBuilder()
|
DBObject query = new QueryBuilder()
|
||||||
.and("clientId").is(getId())
|
.and("associatedClientIds").is(getId())
|
||||||
.get();
|
.get();
|
||||||
List<MongoClientUserSessionAssociationEntity> associations = getMongoStore().loadEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
List<MongoUserSessionEntity> sessions = getMongoStore().loadEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||||
|
|
||||||
Set<UserSessionModel> result = new HashSet<UserSessionModel>();
|
Set<UserSessionModel> result = new HashSet<UserSessionModel>();
|
||||||
for (MongoClientUserSessionAssociationEntity association : associations) {
|
for (MongoUserSessionEntity session : sessions) {
|
||||||
UserSessionModel session = realm.getUserSession(association.getSessionId());
|
result.add(new UserSessionAdapter(session, realm, invocationContext));
|
||||||
result.add(session);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
|
||||||
try {
|
try {
|
||||||
String host = config.get("host", ServerAddress.defaultHost());
|
String host = config.get("host", ServerAddress.defaultHost());
|
||||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||||
String dbName = config.get("db", "keycloak-audit");
|
String dbName = config.get("db", "keycloak");
|
||||||
boolean clearOnStartup = config.getBoolean("clearOnStartup", false);
|
boolean clearOnStartup = config.getBoolean("clearOnStartup", false);
|
||||||
|
|
||||||
String user = config.get("user");
|
String user = config.get("user");
|
||||||
|
@ -79,6 +79,8 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
|
||||||
DB db = client.getDB(dbName);
|
DB db = client.getDB(dbName);
|
||||||
|
|
||||||
this.mongoStore = new MongoStoreImpl(db, clearOnStartup, MANAGED_ENTITY_TYPES);
|
this.mongoStore = new MongoStoreImpl(db, clearOnStartup, MANAGED_ENTITY_TYPES);
|
||||||
|
|
||||||
|
logger.infof("Initialized mongo model. host: %s, port: %d, db: %s, clearOnStartup: %b", host, port, dbName, clearOnStartup);
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
|
||||||
*/
|
*/
|
||||||
public class OAuthClientAdapter extends ClientAdapter<MongoOAuthClientEntity> implements OAuthClientModel {
|
public class OAuthClientAdapter extends ClientAdapter<MongoOAuthClientEntity> implements OAuthClientModel {
|
||||||
|
|
||||||
public OAuthClientAdapter(RealmModel realm, MongoOAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
|
public OAuthClientAdapter(RealmAdapter realm, MongoOAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
|
||||||
super(realm, oauthClientEntity, invContext);
|
super(realm, oauthClientEntity, invContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -299,6 +299,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
@Override
|
@Override
|
||||||
public void setSsoSessionIdleTimeout(int seconds) {
|
public void setSsoSessionIdleTimeout(int seconds) {
|
||||||
realm.setSsoSessionIdleTimeout(seconds);
|
realm.setSsoSessionIdleTimeout(seconds);
|
||||||
|
updateRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -309,6 +310,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
@Override
|
@Override
|
||||||
public void setSsoSessionMaxLifespan(int seconds) {
|
public void setSsoSessionMaxLifespan(int seconds) {
|
||||||
realm.setSsoSessionMaxLifespan(seconds);
|
realm.setSsoSessionMaxLifespan(seconds);
|
||||||
|
updateRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,7 +7,6 @@ import java.util.Set;
|
||||||
|
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||||
|
@ -25,13 +24,13 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
|
||||||
|
|
||||||
private final MongoRoleEntity role;
|
private final MongoRoleEntity role;
|
||||||
private RoleContainerModel roleContainer;
|
private RoleContainerModel roleContainer;
|
||||||
private RealmModel realm;
|
private RealmAdapter realm;
|
||||||
|
|
||||||
public RoleAdapter(RealmModel realm, MongoRoleEntity roleEntity, MongoStoreInvocationContext invContext) {
|
public RoleAdapter(RealmAdapter realm, MongoRoleEntity roleEntity, MongoStoreInvocationContext invContext) {
|
||||||
this(realm, roleEntity, null, invContext);
|
this(realm, roleEntity, null, invContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoleAdapter(RealmModel realm, MongoRoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) {
|
public RoleAdapter(RealmAdapter realm, MongoRoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) {
|
||||||
super(invContext);
|
super(invContext);
|
||||||
this.role = roleEntity;
|
this.role = roleEntity;
|
||||||
this.roleContainer = roleContainer;
|
this.roleContainer = roleContainer;
|
||||||
|
|
|
@ -2,27 +2,27 @@ package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.entities.ClientEntity;
|
||||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoClientUserSessionAssociationEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
|
||||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEntity> implements UserSessionModel {
|
public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEntity> implements UserSessionModel {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(RealmAdapter.class);
|
||||||
|
|
||||||
private MongoUserSessionEntity entity;
|
private MongoUserSessionEntity entity;
|
||||||
private RealmAdapter realm;
|
private RealmAdapter realm;
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
@Override
|
@Override
|
||||||
public void setId(String id) {
|
public void setId(String id) {
|
||||||
entity.setId(id);
|
entity.setId(id);
|
||||||
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,6 +57,7 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
@Override
|
@Override
|
||||||
public void setUser(UserModel user) {
|
public void setUser(UserModel user) {
|
||||||
entity.setUser(user.getId());
|
entity.setUser(user.getId());
|
||||||
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -66,6 +68,7 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
@Override
|
@Override
|
||||||
public void setIpAddress(String ipAddress) {
|
public void setIpAddress(String ipAddress) {
|
||||||
entity.setIpAddress(ipAddress);
|
entity.setIpAddress(ipAddress);
|
||||||
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,6 +79,7 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
@Override
|
@Override
|
||||||
public void setStarted(int started) {
|
public void setStarted(int started) {
|
||||||
entity.setStarted(started);
|
entity.setStarted(started);
|
||||||
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -86,58 +90,39 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
@Override
|
@Override
|
||||||
public void setLastSessionRefresh(int seconds) {
|
public void setLastSessionRefresh(int seconds) {
|
||||||
entity.setLastSessionRefresh(seconds);
|
entity.setLastSessionRefresh(seconds);
|
||||||
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void associateClient(ClientModel client) {
|
public void associateClient(ClientModel client) {
|
||||||
List<ClientModel> clients = getClientAssociations();
|
getMongoStore().pushItemToList(entity, "associatedClientIds", client.getId(), true, invocationContext);
|
||||||
for (ClientModel ass : clients) {
|
|
||||||
if (ass.getId().equals(client.getId())) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MongoClientUserSessionAssociationEntity association = new MongoClientUserSessionAssociationEntity();
|
|
||||||
association.setClientId(client.getId());
|
|
||||||
association.setSessionId(getId());
|
|
||||||
|
|
||||||
getMongoStore().insertEntity(association, invocationContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ClientModel> getClientAssociations() {
|
public List<ClientModel> getClientAssociations() {
|
||||||
DBObject query = new QueryBuilder()
|
List<String> associatedClientIds = getMongoEntity().getAssociatedClientIds();
|
||||||
.and("sessionId").is(getId())
|
|
||||||
.get();
|
|
||||||
List<MongoClientUserSessionAssociationEntity> associations = getMongoStore().loadEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
|
||||||
|
|
||||||
List<ClientModel> result = new ArrayList<ClientModel>();
|
List<ClientModel> clients = new ArrayList<ClientModel>();
|
||||||
for (MongoClientUserSessionAssociationEntity association : associations) {
|
for (String clientId : associatedClientIds) {
|
||||||
ClientModel client = realm.findClientById(association.getClientId());
|
// Try application first
|
||||||
result.add(client);
|
ClientModel client = realm.getApplicationById(clientId);
|
||||||
|
|
||||||
|
// And then OAuthClient
|
||||||
|
if (client == null) {
|
||||||
|
client = realm.getOAuthClientById(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
clients.add(client);
|
||||||
|
} else {
|
||||||
|
logger.warnf("Not found associated client with Id: %s", clientId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAssociatedClient(ClientModel client) {
|
public void removeAssociatedClient(ClientModel client) {
|
||||||
DBObject query = new QueryBuilder()
|
getMongoStore().pullItemFromList(entity, "associatedClientIds", client.getId(), invocationContext);
|
||||||
.and("sessionId").is(getId())
|
|
||||||
.and("clientId").is(client.getId())
|
|
||||||
.get();
|
|
||||||
getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
if (!super.equals(o)) return false;
|
|
||||||
|
|
||||||
UserSessionAdapter that = (UserSessionAdapter) o;
|
|
||||||
return getId().equals(that.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getId().hashCode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package org.keycloak.models.mongo.keycloak.entities;
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.entities.ApplicationEntity;
|
import org.keycloak.models.entities.ApplicationEntity;
|
||||||
import org.keycloak.models.mongo.api.MongoCollection;
|
import org.keycloak.models.mongo.api.MongoCollection;
|
||||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||||
|
@ -23,10 +26,13 @@ public class MongoApplicationEntity extends ApplicationEntity implements MongoId
|
||||||
.get();
|
.get();
|
||||||
context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
|
context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
|
||||||
|
|
||||||
|
// Remove all session associations
|
||||||
query = new QueryBuilder()
|
query = new QueryBuilder()
|
||||||
.and("clientId").is(getId())
|
.and("associatedClientIds").is(getId())
|
||||||
.get();
|
.get();
|
||||||
context.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, context);
|
List<MongoUserSessionEntity> sessions = context.getMongoStore().loadEntities(MongoUserSessionEntity.class, query, context);
|
||||||
|
for (MongoUserSessionEntity session : sessions) {
|
||||||
|
context.getMongoStore().pullItemFromList(session, "associatedClientIds", getId(), context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
package org.keycloak.models.mongo.keycloak.entities;
|
|
||||||
|
|
||||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
|
||||||
import org.keycloak.models.mongo.api.MongoCollection;
|
|
||||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
|
||||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
@MongoCollection(collectionName = "session-client-associations")
|
|
||||||
public class MongoClientUserSessionAssociationEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
|
|
||||||
private String clientId;
|
|
||||||
private String sessionId;
|
|
||||||
|
|
||||||
public String getClientId() {
|
|
||||||
return clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientId(String clientId) {
|
|
||||||
this.clientId = clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSessionId() {
|
|
||||||
return sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSessionId(String sessionId) {
|
|
||||||
this.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.keycloak.models.mongo.keycloak.entities;
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
import org.keycloak.models.entities.OAuthClientEntity;
|
import org.keycloak.models.entities.OAuthClientEntity;
|
||||||
|
@ -16,10 +18,14 @@ import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||||
public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoIdentifiableEntity {
|
public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoIdentifiableEntity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
public void afterRemove(MongoStoreInvocationContext context) {
|
||||||
|
// Remove all session associations
|
||||||
DBObject query = new QueryBuilder()
|
DBObject query = new QueryBuilder()
|
||||||
.and("clientId").is(getId())
|
.and("associatedClientIds").is(getId())
|
||||||
.get();
|
.get();
|
||||||
invocationContext.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, invocationContext);
|
List<MongoUserSessionEntity> sessions = context.getMongoStore().loadEntities(MongoUserSessionEntity.class, query, context);
|
||||||
|
for (MongoUserSessionEntity session : sessions) {
|
||||||
|
context.getMongoStore().pullItemFromList(session, "associatedClientIds", getId(), context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package org.keycloak.models.mongo.keycloak.entities;
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
import com.mongodb.DBObject;
|
import java.util.ArrayList;
|
||||||
import com.mongodb.QueryBuilder;
|
import java.util.List;
|
||||||
|
|
||||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||||
import org.keycloak.models.mongo.api.MongoCollection;
|
import org.keycloak.models.mongo.api.MongoCollection;
|
||||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||||
|
@ -23,6 +24,8 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
||||||
|
|
||||||
private int lastSessionRefresh;
|
private int lastSessionRefresh;
|
||||||
|
|
||||||
|
private List<String> associatedClientIds = new ArrayList<String>();
|
||||||
|
|
||||||
public String getRealmId() {
|
public String getRealmId() {
|
||||||
return realmId;
|
return realmId;
|
||||||
}
|
}
|
||||||
|
@ -63,13 +66,16 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
||||||
this.lastSessionRefresh = lastSessionRefresh;
|
this.lastSessionRefresh = lastSessionRefresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getAssociatedClientIds() {
|
||||||
|
return associatedClientIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssociatedClientIds(List<String> associatedClientIds) {
|
||||||
|
this.associatedClientIds = associatedClientIds;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRemove(MongoStoreInvocationContext context) {
|
public void afterRemove(MongoStoreInvocationContext context) {
|
||||||
// Remove all roles, which belongs to this application
|
|
||||||
DBObject query = new QueryBuilder()
|
|
||||||
.and("sessionId").is(getId())
|
|
||||||
.get();
|
|
||||||
context.getMongoStore().removeEntities(MongoClientUserSessionAssociationEntity.class, query, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -762,4 +762,49 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
assertNull(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
assertNull(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userSessionAssociations() {
|
||||||
|
RealmModel realm = realmManager.createRealm("userSessions");
|
||||||
|
UserModel user = realm.addUser("userSessions1");
|
||||||
|
UserSessionModel userSession = realm.createUserSession(user, "127.0.0.1");
|
||||||
|
|
||||||
|
ApplicationModel app1 = realm.addApplication("app1");
|
||||||
|
ApplicationModel app2 = realm.addApplication("app2");
|
||||||
|
OAuthClientModel client1 = realm.addOAuthClient("client1");
|
||||||
|
|
||||||
|
Assert.assertEquals(0, userSession.getClientAssociations().size());
|
||||||
|
|
||||||
|
userSession.associateClient(app1);
|
||||||
|
userSession.associateClient(client1);
|
||||||
|
|
||||||
|
Assert.assertEquals(2, userSession.getClientAssociations().size());
|
||||||
|
Assert.assertTrue(app1.getUserSessions().contains(userSession));
|
||||||
|
Assert.assertFalse(app2.getUserSessions().contains(userSession));
|
||||||
|
Assert.assertTrue(client1.getUserSessions().contains(userSession));
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Refresh all
|
||||||
|
realm = realmManager.getRealm("userSessions");
|
||||||
|
userSession = realm.getUserSession(userSession.getId());
|
||||||
|
app1 = realm.getApplicationByName("app1");
|
||||||
|
client1 = realm.getOAuthClient("client1");
|
||||||
|
|
||||||
|
userSession.removeAssociatedClient(app1);
|
||||||
|
Assert.assertEquals(1, userSession.getClientAssociations().size());
|
||||||
|
Assert.assertEquals(client1, userSession.getClientAssociations().get(0));
|
||||||
|
Assert.assertFalse(app1.getUserSessions().contains(userSession));
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Refresh all
|
||||||
|
realm = realmManager.getRealm("userSessions");
|
||||||
|
userSession = realm.getUserSession(userSession.getId());
|
||||||
|
client1 = realm.getOAuthClient("client1");
|
||||||
|
|
||||||
|
userSession.removeAssociatedClient(client1);
|
||||||
|
Assert.assertEquals(0, userSession.getClientAssociations().size());
|
||||||
|
Assert.assertFalse(client1.getUserSessions().contains(userSession));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ public class DefaultProviderSessionFactory implements ProviderSessionFactory {
|
||||||
ProviderFactory factory = loadProviderFactory(spi, provider);
|
ProviderFactory factory = loadProviderFactory(spi, provider);
|
||||||
Config.Scope scope = Config.scope(spi.getName(), provider);
|
Config.Scope scope = Config.scope(spi.getName(), provider);
|
||||||
factory.init(scope);
|
factory.init(scope);
|
||||||
log.debug("Initialized " + factory.getClass().getName() + " (config = " + scope + ")");
|
|
||||||
|
|
||||||
factories.put(factory.getId(), factory);
|
factories.put(factory.getId(), factory);
|
||||||
|
|
||||||
|
@ -42,7 +41,6 @@ public class DefaultProviderSessionFactory implements ProviderSessionFactory {
|
||||||
for (ProviderFactory factory : ServiceLoader.load(spi.getProviderFactoryClass())) {
|
for (ProviderFactory factory : ServiceLoader.load(spi.getProviderFactoryClass())) {
|
||||||
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
|
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
|
||||||
factory.init(scope);
|
factory.init(scope);
|
||||||
log.debug("Initialized " + factory.getClass().getName() + " (config = " + scope + ")");
|
|
||||||
|
|
||||||
factories.put(factory.getId(), factory);
|
factories.put(factory.getId(), factory);
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,9 @@ public class AccountService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
account.setFeatures(realm.isSocial(), auditProvider != null, passwordUpdateSupported);
|
boolean auditEnabled = auditProvider != null && realm.isAuditEnabled();
|
||||||
|
|
||||||
|
account.setFeatures(realm.isSocial(), auditEnabled, passwordUpdateSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
|
public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
|
||||||
|
@ -246,6 +248,16 @@ public class AccountService {
|
||||||
public Response logPage() {
|
public Response logPage() {
|
||||||
if (auth != null) {
|
if (auth != null) {
|
||||||
List<Event> events = auditProvider.createQuery().event(AUDIT_EVENTS).user(auth.getUser().getId()).maxResults(30).getResultList();
|
List<Event> events = auditProvider.createQuery().event(AUDIT_EVENTS).user(auth.getUser().getId()).maxResults(30).getResultList();
|
||||||
|
for (Event e : events) {
|
||||||
|
if (e.getDetails() != null) {
|
||||||
|
Iterator<Map.Entry<String, String>> itr = e.getDetails().entrySet().iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
if (!AUDIT_DETAILS.contains(itr.next().getKey())) {
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
account.setEvents(events);
|
account.setEvents(events);
|
||||||
}
|
}
|
||||||
return forwardToPage("log", AccountPages.LOG);
|
return forwardToPage("log", AccountPages.LOG);
|
||||||
|
@ -560,7 +572,7 @@ public class AccountService {
|
||||||
ApplicationModel application = realm.getApplicationByName(referrer);
|
ApplicationModel application = realm.getApplicationByName(referrer);
|
||||||
if (application != null) {
|
if (application != null) {
|
||||||
if (referrerUri != null) {
|
if (referrerUri != null) {
|
||||||
referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application);
|
referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
|
||||||
} else {
|
} else {
|
||||||
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl());
|
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl());
|
||||||
}
|
}
|
||||||
|
@ -571,7 +583,7 @@ public class AccountService {
|
||||||
} else if (referrerUri != null) {
|
} else if (referrerUri != null) {
|
||||||
ClientModel client = realm.getOAuthClient(referrer);
|
ClientModel client = realm.getOAuthClient(referrer);
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application);
|
referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, realm, application);
|
||||||
|
|
||||||
if (referrerUri != null) {
|
if (referrerUri != null) {
|
||||||
return new String[]{referrer, referrerUri};
|
return new String[]{referrer, referrerUri};
|
||||||
|
|
|
@ -84,12 +84,12 @@ public class KeycloakApplication extends Application {
|
||||||
classes.add(JsResource.class);
|
classes.add(JsResource.class);
|
||||||
classes.add(WelcomeResource.class);
|
classes.add(WelcomeResource.class);
|
||||||
|
|
||||||
|
checkExportImportProvider();
|
||||||
|
|
||||||
setupDefaultRealm(context.getContextPath());
|
setupDefaultRealm(context.getContextPath());
|
||||||
|
|
||||||
setupScheduledTasks(providerSessionFactory);
|
setupScheduledTasks(providerSessionFactory);
|
||||||
importRealms(context);
|
importRealms(context);
|
||||||
|
|
||||||
checkExportImportProvider();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getContextPath() {
|
public String getContextPath() {
|
||||||
|
@ -108,7 +108,19 @@ public class KeycloakApplication extends Application {
|
||||||
|
|
||||||
protected void loadConfig() {
|
protected void loadConfig() {
|
||||||
try {
|
try {
|
||||||
URL config = Thread.currentThread().getContextClassLoader().getResource("META-INF/keycloak-server.json");
|
URL config = null;
|
||||||
|
|
||||||
|
String configDir = System.getProperty("jboss.server.config.dir");
|
||||||
|
if (configDir != null) {
|
||||||
|
File f = new File(configDir + File.separator + "keycloak-server.json");
|
||||||
|
if (f.isFile()) {
|
||||||
|
config = f.toURI().toURL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
config = Thread.currentThread().getContextClassLoader().getResource("META-INF/keycloak-server.json");
|
||||||
|
}
|
||||||
|
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
JsonNode node = new ObjectMapper().readTree(config);
|
JsonNode node = new ObjectMapper().readTree(config);
|
||||||
|
@ -116,6 +128,8 @@ public class KeycloakApplication extends Application {
|
||||||
|
|
||||||
log.info("Loaded config from " + config);
|
log.info("Loaded config from " + config);
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
log.warn("Config 'keycloak-server.json' not found");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Failed to load config", e);
|
throw new RuntimeException("Failed to load config", e);
|
||||||
|
|
|
@ -122,6 +122,10 @@ public class SocialResource {
|
||||||
Map<String, String[]> queryParams = getQueryParams();
|
Map<String, String[]> queryParams = getQueryParams();
|
||||||
|
|
||||||
RequestDetails requestData = getRequestDetails(queryParams);
|
RequestDetails requestData = getRequestDetails(queryParams);
|
||||||
|
if (requestData == null) {
|
||||||
|
Flows.forms(providerSession, null, uriInfo).setError("Unexpected callback").createErrorPage();
|
||||||
|
}
|
||||||
|
|
||||||
SocialProvider provider = SocialLoader.load(requestData.getProviderId());
|
SocialProvider provider = SocialLoader.load(requestData.getProviderId());
|
||||||
|
|
||||||
String realmName = requestData.getClientAttribute("realm");
|
String realmName = requestData.getClientAttribute("realm");
|
||||||
|
@ -296,7 +300,7 @@ public class SocialResource {
|
||||||
logger.warn("Login requester not enabled.");
|
logger.warn("Login requester not enabled.");
|
||||||
return Flows.forms(providerSession, realm, uriInfo).setError("Login requester not enabled.").createErrorPage();
|
return Flows.forms(providerSession, realm, uriInfo).setError("Login requester not enabled.").createErrorPage();
|
||||||
}
|
}
|
||||||
redirectUri = TokenService.verifyRedirectUri(uriInfo, redirectUri, client);
|
redirectUri = TokenService.verifyRedirectUri(uriInfo, redirectUri, realm, client);
|
||||||
if (redirectUri == null) {
|
if (redirectUri == null) {
|
||||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||||
return Flows.forms(providerSession, realm, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
|
return Flows.forms(providerSession, realm, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.keycloak.authentication.AuthenticationProviderException;
|
||||||
import org.keycloak.authentication.AuthenticationProviderManager;
|
import org.keycloak.authentication.AuthenticationProviderManager;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -42,6 +43,7 @@ import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||||
|
import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.util.BasicAuthHelper;
|
import org.keycloak.util.BasicAuthHelper;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
@ -363,7 +365,7 @@ public class TokenService {
|
||||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect = verifyRedirectUri(uriInfo, redirect, client);
|
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
@ -457,7 +459,7 @@ public class TokenService {
|
||||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect = verifyRedirectUri(uriInfo, redirect, client);
|
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
@ -754,7 +756,7 @@ public class TokenService {
|
||||||
audit.error(Errors.NOT_ALLOWED);
|
audit.error(Errors.NOT_ALLOWED);
|
||||||
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate login");
|
return oauth.forwardToSecurityFailure("Bearer-only applications are not allowed to initiate login");
|
||||||
}
|
}
|
||||||
redirect = verifyRedirectUri(uriInfo, redirect, client);
|
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
@ -811,7 +813,7 @@ public class TokenService {
|
||||||
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
return oauth.forwardToSecurityFailure("Login requester not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect = verifyRedirectUri(uriInfo, redirect, client);
|
redirect = verifyRedirectUri(uriInfo, redirect, realm, client);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
audit.error(Errors.INVALID_REDIRECT_URI);
|
audit.error(Errors.INVALID_REDIRECT_URI);
|
||||||
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
|
||||||
|
@ -937,6 +939,17 @@ public class TokenService {
|
||||||
return oauth.redirectAccessCode(accessCodeEntry, session, state, redirect);
|
return oauth.redirectAccessCode(accessCodeEntry, session, state, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("oauth/oob")
|
||||||
|
@GET
|
||||||
|
public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
|
||||||
|
LoginFormsProvider forms = Flows.forms(providerSession, realm, uriInfo);
|
||||||
|
if (code != null) {
|
||||||
|
return forms.setAccessCode(null, code).createCode();
|
||||||
|
} else {
|
||||||
|
return forms.setError(error).createCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected Response redirectAccessDenied(String redirect, String state) {
|
protected Response redirectAccessDenied(String redirect, String state) {
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
||||||
if (state != null)
|
if (state != null)
|
||||||
|
@ -961,7 +974,7 @@ public class TokenService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, ClientModel client) {
|
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
|
||||||
Set<String> validRedirects = client.getRedirectUris();
|
Set<String> validRedirects = client.getRedirectUris();
|
||||||
if (redirectUri == null) {
|
if (redirectUri == null) {
|
||||||
if (validRedirects.size() != 1) return null;
|
if (validRedirects.size() != 1) return null;
|
||||||
|
@ -970,10 +983,10 @@ public class TokenService {
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
validRedirect = validRedirect.substring(0, idx);
|
validRedirect = validRedirect.substring(0, idx);
|
||||||
}
|
}
|
||||||
return validRedirect;
|
redirectUri = validRedirect;
|
||||||
} else if (validRedirects.isEmpty()) {
|
} else if (validRedirects.isEmpty()) {
|
||||||
logger.error("Redirect URI is required for client: " + client.getClientId());
|
logger.error("Redirect URI is required for client: " + client.getClientId());
|
||||||
return null;
|
redirectUri = null;
|
||||||
} else {
|
} else {
|
||||||
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
|
String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
|
||||||
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
|
Set<String> resolveValidRedirects = resolveValidRedirects(uriInfo, validRedirects);
|
||||||
|
@ -996,7 +1009,13 @@ public class TokenService {
|
||||||
|
|
||||||
valid = matchesRedirects(resolveValidRedirects, r);
|
valid = matchesRedirects(resolveValidRedirects, r);
|
||||||
}
|
}
|
||||||
return valid ? redirectUri : null;
|
redirectUri = valid ? redirectUri : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Constants.INSTALLED_APP_URN.equals(redirectUri)) {
|
||||||
|
return Urls.realmInstalledAppUrnCallback(uriInfo.getBaseUri(), realm.getName()).toString();
|
||||||
|
} else {
|
||||||
|
return redirectUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -250,9 +250,11 @@ public class AdminConsole {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public Response getMainPage() throws URISyntaxException {
|
public Response getMainPage() throws URISyntaxException {
|
||||||
return Response.status(302).location(
|
if (!uriInfo.getRequestUri().getPath().endsWith("/")) {
|
||||||
AdminRoot.adminConsoleUrl(uriInfo).path("index.html").build(realm.getName())
|
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build();
|
||||||
).build();
|
} else {
|
||||||
|
return getResource("index.html");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class AdminRoot {
|
||||||
public Response masterRealmAdminConsoleRedirect() {
|
public Response masterRealmAdminConsoleRedirect() {
|
||||||
RealmModel master = new RealmManager(session).getKeycloakAdminstrationRealm();
|
RealmModel master = new RealmManager(session).getKeycloakAdminstrationRealm();
|
||||||
return Response.status(302).location(
|
return Response.status(302).location(
|
||||||
uriInfo.getBaseUriBuilder().path(AdminRoot.class).path(AdminRoot.class, "getAdminConsole").path("index.html").build(master.getName())
|
uriInfo.getBaseUriBuilder().path(AdminRoot.class).path(AdminRoot.class, "getAdminConsole").path("/").build(master.getName())
|
||||||
).build();
|
).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,34 +86,25 @@ public class OAuthFlows {
|
||||||
|
|
||||||
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect, boolean rememberMe) {
|
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect, boolean rememberMe) {
|
||||||
String code = accessCode.getCode();
|
String code = accessCode.getCode();
|
||||||
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
|
||||||
if (Constants.INSTALLED_APP_URN.equals(redirect)) {
|
log.debugv("redirectAccessCode: state: {0}", state);
|
||||||
return Flows.forms(providerSession, realm, uriInfo).setAccessCode(accessCode.getId(), code).createCode();
|
if (state != null)
|
||||||
} else {
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
|
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||||
log.debugv("redirectAccessCode: state: {0}", state);
|
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
||||||
if (state != null)
|
rememberMe = rememberMe || remember != null;
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
// refresh the cookies!
|
||||||
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe);
|
||||||
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
|
||||||
rememberMe = rememberMe || remember != null;
|
return location.build();
|
||||||
// refresh the cookies!
|
|
||||||
authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe);
|
|
||||||
if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
|
|
||||||
return location.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response redirectError(ClientModel client, String error, String state, String redirect) {
|
public Response redirectError(ClientModel client, String error, String state, String redirect) {
|
||||||
if (Constants.INSTALLED_APP_URN.equals(redirect)) {
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
|
||||||
return Flows.forms(providerSession, realm, uriInfo).setError(error).createCode();
|
if (state != null) {
|
||||||
} else {
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
|
|
||||||
if (state != null) {
|
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
|
||||||
}
|
|
||||||
return Response.status(302).location(redirectUri.build()).build();
|
|
||||||
}
|
}
|
||||||
|
return Response.status(302).location(redirectUri.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, String username, boolean rememberMe, String authMethod, Audit audit) {
|
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, String username, boolean rememberMe, String authMethod, Audit audit) {
|
||||||
|
|
|
@ -148,6 +148,10 @@ public class Urls {
|
||||||
return tokenBase(baseUri).path(TokenService.class, "registerPage").build(realmId);
|
return tokenBase(baseUri).path(TokenService.class, "registerPage").build(realmId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static URI realmInstalledAppUrnCallback(URI baseUri, String realmId) {
|
||||||
|
return tokenBase(baseUri).path(TokenService.class, "installedAppUrnCallback").build(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
public static URI realmOauthAction(URI baseUri, String realmId) {
|
public static URI realmOauthAction(URI baseUri, String realmId) {
|
||||||
return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId);
|
return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,11 @@ public class JsonConfigProvider implements Config.ConfigProvider {
|
||||||
if (n == null) {
|
if (n == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (n.isArray()) {
|
} else if (n.isArray()) {
|
||||||
ArrayList<String> l = new ArrayList<String>();
|
String[] a = new String[n.size()];
|
||||||
for (JsonNode e : n) {
|
for (int i = 0; i < a.length; i++) {
|
||||||
l.add(StringPropertyReplacer.replaceProperties(e.getTextValue()));
|
a[i] = StringPropertyReplacer.replaceProperties(n.get(i).getTextValue());
|
||||||
}
|
}
|
||||||
return (String[]) l.toArray();
|
return a;
|
||||||
} else {
|
} else {
|
||||||
return new String[] { StringPropertyReplacer.replaceProperties(n.getTextValue()) };
|
return new String[] { StringPropertyReplacer.replaceProperties(n.getTextValue()) };
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,7 @@ public class TwitterProvider implements SocialProvider {
|
||||||
Twitter twitter = new TwitterFactory().getInstance();
|
Twitter twitter = new TwitterFactory().getInstance();
|
||||||
twitter.setOAuthConsumer(config.getKey(), config.getSecret());
|
twitter.setOAuthConsumer(config.getKey(), config.getSecret());
|
||||||
|
|
||||||
String redirectUri = config.getCallbackUrl();
|
RequestToken requestToken = twitter.getOAuthRequestToken(config.getCallbackUrl());
|
||||||
redirectUri = redirectUri.replace("//localhost", "//127.0.0.1");
|
|
||||||
|
|
||||||
RequestToken requestToken = twitter.getOAuthRequestToken(redirectUri);
|
|
||||||
|
|
||||||
return AuthRequest.create(requestToken.getToken(), requestToken.getAuthenticationURL())
|
return AuthRequest.create(requestToken.getToken(), requestToken.getAuthenticationURL())
|
||||||
.setAttribute("token", requestToken.getToken()).setAttribute("tokenSecret", requestToken.getTokenSecret())
|
.setAttribute("token", requestToken.getToken()).setAttribute("tokenSecret", requestToken.getTokenSecret())
|
||||||
|
|
|
@ -58,6 +58,8 @@ By default it's using database `keycloak` on localhost/27017 and it uses already
|
||||||
|
|
||||||
mvn exec:java -Pkeycloak-server -Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.host=localhost -Dkeycloak.model.mongo.port=27017 -Dkeycloak.model.mongo.db=keycloak -Dkeycloak.model.mongo.clearOnStartup=false
|
mvn exec:java -Pkeycloak-server -Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.host=localhost -Dkeycloak.model.mongo.port=27017 -Dkeycloak.model.mongo.db=keycloak -Dkeycloak.model.mongo.clearOnStartup=false
|
||||||
|
|
||||||
|
Note that if you are using Mongo model, it would mean that Mongo will be used for audit as well. You may need to use audit related properties for configuration of Mongo if you want to override default ones (For example keycloak.audit.mongo.host, keycloak.audit.mongo.port etc)
|
||||||
|
|
||||||
TOTP codes
|
TOTP codes
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
|
@ -439,7 +439,7 @@
|
||||||
<id>mongo</id>
|
<id>mongo</id>
|
||||||
<activation>
|
<activation>
|
||||||
<property>
|
<property>
|
||||||
<name>keycloak.model</name>
|
<name>keycloak.model.provider</name>
|
||||||
<value>mongo</value>
|
<value>mongo</value>
|
||||||
</property>
|
</property>
|
||||||
</activation>
|
</activation>
|
||||||
|
@ -471,13 +471,13 @@
|
||||||
<keycloak.model.mongo.host>${keycloak.model.mongo.host}</keycloak.model.mongo.host>
|
<keycloak.model.mongo.host>${keycloak.model.mongo.host}</keycloak.model.mongo.host>
|
||||||
<keycloak.model.mongo.port>${keycloak.model.mongo.port}</keycloak.model.mongo.port>
|
<keycloak.model.mongo.port>${keycloak.model.mongo.port}</keycloak.model.mongo.port>
|
||||||
<keycloak.model.mongo.db>${keycloak.model.mongo.db}</keycloak.model.mongo.db>
|
<keycloak.model.mongo.db>${keycloak.model.mongo.db}</keycloak.model.mongo.db>
|
||||||
|
<keycloak.model.mongo.clearOnStartup>${keycloak.model.mongo.clearOnStartup}</keycloak.model.mongo.clearOnStartup>
|
||||||
|
|
||||||
<keycloak.audit.provider>mongo</keycloak.audit.provider>
|
<keycloak.audit.provider>mongo</keycloak.audit.provider>
|
||||||
<keycloak.audit.mongo.host>${keycloak.model.mongo.host}</keycloak.audit.mongo.host>
|
<keycloak.audit.mongo.host>${keycloak.model.mongo.host}</keycloak.audit.mongo.host>
|
||||||
<keycloak.audit.mongo.port>${keycloak.model.mongo.port}</keycloak.audit.mongo.port>
|
<keycloak.audit.mongo.port>${keycloak.model.mongo.port}</keycloak.audit.mongo.port>
|
||||||
<keycloak.audit.mongo.db>${keycloak.model.mongo.db}</keycloak.audit.mongo.db>
|
<keycloak.audit.mongo.db>${keycloak.model.mongo.db}</keycloak.audit.mongo.db>
|
||||||
|
<keycloak.audit.mongo.clearOnStartup>${keycloak.model.mongo.clearOnStartup}</keycloak.audit.mongo.clearOnStartup>
|
||||||
<keycloak.model.mongo.clearOnStartup>${keycloak.model.mongo.clearOnStartup}</keycloak.model.mongo.clearOnStartup>
|
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
|
|
|
@ -4,11 +4,23 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"audit": {
|
"audit": {
|
||||||
"provider": "${keycloak.audit.provider:jpa}"
|
"provider": "${keycloak.audit.provider,keycloak.model.provider:jpa}",
|
||||||
|
"mongo": {
|
||||||
|
"host": "${keycloak.audit.mongo.host:127.0.0.1}",
|
||||||
|
"port": "${keycloak.audit.mongo.port:27017}",
|
||||||
|
"db": "${keycloak.audit.mongo.db:keycloak-audit}",
|
||||||
|
"clearOnStartup": "${keycloak.audit.mongo.clearOnStartup:false}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"model": {
|
"model": {
|
||||||
"provider": "${keycloak.model.provider:jpa}"
|
"provider": "${keycloak.model.provider:jpa}",
|
||||||
|
"mongo": {
|
||||||
|
"host": "${keycloak.model.mongo.host:127.0.0.1}",
|
||||||
|
"port": "${keycloak.model.mongo.port:27017}",
|
||||||
|
"db": "${keycloak.model.mongo.db:keycloak}",
|
||||||
|
"clearOnStartup": "${keycloak.model.mongo.clearOnStartup:false}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"timer": {
|
"timer": {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.keycloak.testsuite.org.keycloak.testsuite.util;
|
package org.keycloak.testsuite;
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
|
@ -34,7 +34,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.org.keycloak.testsuite.util.MailUtil;
|
import org.keycloak.testsuite.MailUtil;
|
||||||
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;
|
||||||
|
@ -50,8 +50,6 @@ import org.openqa.selenium.WebDriver;
|
||||||
import javax.mail.MessagingException;
|
import javax.mail.MessagingException;
|
||||||
import javax.mail.internet.MimeMessage;
|
import javax.mail.internet.MimeMessage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.org.keycloak.testsuite.util.MailUtil;
|
import org.keycloak.testsuite.MailUtil;
|
||||||
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;
|
||||||
|
|
|
@ -112,6 +112,35 @@ public class AuthorizationCodeTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authorizationRequestInstalledAppCancel() throws IOException {
|
||||||
|
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.getApplicationNameMap().get("test-app").addRedirectUri(Constants.INSTALLED_APP_URN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
oauth.redirectUri(Constants.INSTALLED_APP_URN);
|
||||||
|
|
||||||
|
oauth.openLoginForm();
|
||||||
|
driver.findElement(By.name("cancel")).click();
|
||||||
|
|
||||||
|
String title = driver.getTitle();
|
||||||
|
Assert.assertTrue(title.equals("Error error=access_denied"));
|
||||||
|
|
||||||
|
String error = driver.findElement(By.id(OAuth2Constants.ERROR)).getText();
|
||||||
|
Assert.assertEquals("access_denied", error);
|
||||||
|
|
||||||
|
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
|
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
appRealm.getApplicationNameMap().get("test-app").removeRedirectUri(Constants.INSTALLED_APP_URN);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authorizationValidRedirectUri() throws IOException {
|
public void authorizationValidRedirectUri() throws IOException {
|
||||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
Loading…
Reference in a new issue