Added audit to admin console
This commit is contained in:
parent
8caf3fa83a
commit
88ddc8ebca
17 changed files with 285 additions and 29 deletions
|
@ -11,6 +11,9 @@ table caption {
|
||||||
table tbody tr:nth-child(even) {
|
table tbody tr:nth-child(even) {
|
||||||
background-color: #f6f6f6;
|
background-color: #f6f6f6;
|
||||||
}
|
}
|
||||||
|
table tbody tr:nth-child(odd) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
table tbody tr td,
|
table tbody tr td,
|
||||||
table thead tr th {
|
table thead tr th {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
@ -138,10 +141,14 @@ table tfoot tr {
|
||||||
table tfoot tr .table-nav {
|
table tfoot tr .table-nav {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a {
|
table tfoot tr .table-nav a,
|
||||||
|
table tfoot tr .table-nav button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 2.4em;
|
line-height: 22px;
|
||||||
border-left: 1px solid #d9d9d9;
|
border-left: 1px solid #d9d9d9;
|
||||||
|
border-right: none;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
width: 3.5em;
|
width: 3.5em;
|
||||||
background-color: #f3f3f3;
|
background-color: #f3f3f3;
|
||||||
background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
|
background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
|
||||||
|
@ -156,28 +163,35 @@ table tfoot tr .table-nav a {
|
||||||
background-position: left top;
|
background-position: left top;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a.last {
|
table tfoot tr .table-nav a.last,
|
||||||
|
table tfoot tr .table-nav button.last {
|
||||||
background-position: top right;
|
background-position: top right;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a.prev {
|
table tfoot tr .table-nav a.prev,
|
||||||
|
table tfoot tr .table-nav button.prev {
|
||||||
background-position: bottom left;
|
background-position: bottom left;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a.next {
|
table tfoot tr .table-nav a.next,
|
||||||
|
table tfoot tr .table-nav button.next {
|
||||||
background-position: bottom right;
|
background-position: bottom right;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a:hover {
|
table tfoot tr .table-nav a:hover,
|
||||||
|
table tfoot tr .table-nav button:hover {
|
||||||
background-image: url(img/sprite-table-nav.png);
|
background-image: url(img/sprite-table-nav.png);
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a:active {
|
table tfoot tr .table-nav a:active,
|
||||||
|
table tfoot tr .table-nav button:active {
|
||||||
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25) inset;
|
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25) inset;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a.disabled {
|
table tfoot tr .table-nav a.disabled,
|
||||||
|
table tfoot tr .table-nav button:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
filter: alpha(opacity=50);
|
filter: alpha(opacity=50);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav a.disabled:active {
|
table tfoot tr .table-nav a.disabled:active,
|
||||||
|
table tfoot tr .table-nav button:disabled:active {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
table tfoot tr .table-nav span {
|
table tfoot tr .table-nav span {
|
||||||
|
@ -195,3 +209,20 @@ table tfoot tr .table-nav span {
|
||||||
td .form-group {
|
td .form-group {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.audit-success {
|
||||||
|
background-color: #E4F1E1;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.audit-error {
|
||||||
|
background-color: #F8E7E7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-table-actions .form-group {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-table-actions select {
|
||||||
|
height: 26px;
|
||||||
|
}
|
|
@ -131,6 +131,15 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'RealmLdapSettingsCtrl'
|
controller : 'RealmLdapSettingsCtrl'
|
||||||
})
|
})
|
||||||
|
.when('/realms/:realm/audit', {
|
||||||
|
templateUrl : 'partials/realm-audit.html',
|
||||||
|
resolve : {
|
||||||
|
realm : function(RealmLoader) {
|
||||||
|
return RealmLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller : 'RealmAuditCtrl'
|
||||||
|
})
|
||||||
.when('/create/user/:realm', {
|
.when('/create/user/:realm', {
|
||||||
templateUrl : 'partials/user-detail.html',
|
templateUrl : 'partials/user-detail.html',
|
||||||
resolve : {
|
resolve : {
|
||||||
|
|
|
@ -42,6 +42,10 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
|
||||||
return getAccess('view-users') || this.manageClients;
|
return getAccess('view-users') || this.manageClients;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get viewAudit() {
|
||||||
|
return getAccess('view-audit') || this.manageClients;
|
||||||
|
},
|
||||||
|
|
||||||
get manageRealm() {
|
get manageRealm() {
|
||||||
return getAccess('manage-realm');
|
return getAccess('manage-realm');
|
||||||
},
|
},
|
||||||
|
@ -56,6 +60,10 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
|
||||||
|
|
||||||
get manageUsers() {
|
get manageUsers() {
|
||||||
return getAccess('manage-users');
|
return getAccess('manage-users');
|
||||||
|
},
|
||||||
|
|
||||||
|
get manageAudit() {
|
||||||
|
return getAccess('manage-audit');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -914,4 +922,49 @@ module.controller('RealmLdapSettingsCtrl', function($scope, Realm, realm, $locat
|
||||||
$scope.realm = angular.copy(oldCopy);
|
$scope.realm = angular.copy(oldCopy);
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.controller('RealmAuditCtrl', function($scope, RealmAudit, realm) {
|
||||||
|
$scope.realm = realm;
|
||||||
|
$scope.page = 0;
|
||||||
|
|
||||||
|
$scope.query = {
|
||||||
|
id : realm.realm,
|
||||||
|
max : 5,
|
||||||
|
first : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.update = function() {
|
||||||
|
for (var i in $scope.query) {
|
||||||
|
if ($scope.query[i] === '') {
|
||||||
|
delete $scope.query[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.debug($scope.query.first);
|
||||||
|
$scope.events = RealmAudit.query($scope.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.firstPage = function() {
|
||||||
|
$scope.query.first = 0;
|
||||||
|
if ($scope.query.first < 0) {
|
||||||
|
$scope.query.first = 0;
|
||||||
|
}
|
||||||
|
$scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.previousPage = function() {
|
||||||
|
$scope.query.first -= parseInt($scope.query.max);
|
||||||
|
if ($scope.query.first < 0) {
|
||||||
|
$scope.query.first = 0;
|
||||||
|
}
|
||||||
|
$scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.nextPage = function() {
|
||||||
|
$scope.query.first += parseInt($scope.query.max);
|
||||||
|
$scope.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.update();
|
||||||
});
|
});
|
|
@ -142,6 +142,12 @@ module.factory('Realm', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('RealmAudit', function($resource) {
|
||||||
|
return $resource('/auth/rest/admin/realms/:id/audit', {
|
||||||
|
id : '@realm'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.factory('ServerInfo', function($resource) {
|
module.factory('ServerInfo', function($resource) {
|
||||||
return $resource('/auth/rest/admin/serverinfo');
|
return $resource('/auth/rest/admin/serverinfo');
|
||||||
});
|
});
|
||||||
|
|
100
admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-audit.html
Executable file
100
admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-audit.html
Executable file
|
@ -0,0 +1,100 @@
|
||||||
|
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||||
|
<div id="content-area" class="col-sm-9" role="main">
|
||||||
|
<data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
|
||||||
|
<div id="content">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||||
|
<li><a href="#/realms/{{realm.realm}}">Audit</a></li>
|
||||||
|
<li class="active">Social</li>
|
||||||
|
</ol>
|
||||||
|
<h2><span>{{realm.realm}}</span> Audit Log</h2>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="kc-table-actions" colspan="4">
|
||||||
|
<div class="pull-right">
|
||||||
|
<select data-ng-model="query.max" data-ng-click="update()" class="btn btn-default">
|
||||||
|
<option>5</option>
|
||||||
|
<option>10</option>
|
||||||
|
<option>50</option>
|
||||||
|
<option>100</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-default" data-ng-click="filter = !filter">
|
||||||
|
<span class="glyphicon glyphicon-plus" data-ng-show="!filter"></span>
|
||||||
|
<span class="glyphicon glyphicon-minus" data-ng-show="filter"></span>
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default btn-primary" data-ng-click="update()">Update</button>
|
||||||
|
</div>
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group" data-ng-show="filter">
|
||||||
|
<label class="col-sm-2 control-label" for="event">Event</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input class="form-control" type="text" id="event" name="event" data-ng-model="query.event">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" data-ng-show="filter">
|
||||||
|
<label class="col-sm-2 control-label" for="client">Client</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input class="form-control" type="text" id="client" name="client" data-ng-model="query.client">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" data-ng-show="filter">
|
||||||
|
<label class="col-sm-2 control-label" for="user">User</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input class="form-control" type="text" id="user" name="user" data-ng-model="query.user">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th width="100px">Time</th>
|
||||||
|
<th width="180px">Event</th>
|
||||||
|
<th>Details</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">
|
||||||
|
<div class="table-nav">
|
||||||
|
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">First page</button>
|
||||||
|
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">Previous page</button>
|
||||||
|
<button data-ng-click="nextPage()" class="next" ng-disabled="events.length < query.max">Next page</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="event in events">
|
||||||
|
<td>{{event.time|date:'shortDate'}}<br>{{event.time|date:'mediumTime'}}</td>
|
||||||
|
<td data-ng-class="event.error && 'audit-error' || 'audit-success'">{{event.event}}<br/>{{event.error}}</td>
|
||||||
|
<td>
|
||||||
|
<table>
|
||||||
|
<tr><td width="100px">Client</td><td>{{event.clientId}}</td></tr>
|
||||||
|
<tr><td>User</td><td>{{event.userId}}</td></tr>
|
||||||
|
<tr><td>IP Address</td><td>{{event.ipAddress}}</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td>Details</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-default btn-xs" ng-click="event.collapse = !event.collapse">
|
||||||
|
<span class="glyphicon glyphicon-plus" data-ng-show="!event.collapse"></span>
|
||||||
|
<span class="glyphicon glyphicon-minus" data-ng-show="event.collapse"></span>
|
||||||
|
</button>
|
||||||
|
<table data-ng-show="event.collapse">
|
||||||
|
<tr ng-repeat="(key, value) in event.details">
|
||||||
|
<td>{{key}}</td>
|
||||||
|
<td>{{value}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -7,4 +7,5 @@
|
||||||
<li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
<li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||||
<li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
<li data-ng-show="access.viewClients" data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
||||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/revocation">Sessions</a></li>
|
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/revocation">Sessions</a></li>
|
||||||
|
<li data-ng-show="access.viewAudit" data-ng-class="(path[2] == 'audit') && 'active'"><a href="#/realms/{{realm.realm}}/audit">Audit</a></li>
|
||||||
</ul>
|
</ul>
|
|
@ -72,10 +72,6 @@ public class Event {
|
||||||
this.ipAddress = ipAddress;
|
this.ipAddress = ipAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return error != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError() {
|
public String getError() {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ public interface EventQuery {
|
||||||
|
|
||||||
public EventQuery user(String userId);
|
public EventQuery user(String userId);
|
||||||
|
|
||||||
|
public EventQuery ipAddress(String ipAddress);
|
||||||
|
|
||||||
public EventQuery firstResult(int result);
|
public EventQuery firstResult(int result);
|
||||||
|
|
||||||
public EventQuery maxResults(int results);
|
public EventQuery maxResults(int results);
|
||||||
|
|
|
@ -19,7 +19,7 @@ public class JBossLoggingAuditListener implements AuditListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(Event event) {
|
public void onEvent(Event event) {
|
||||||
Logger.Level level = event.isError() ? Logger.Level.WARN : Logger.Level.INFO;
|
Logger.Level level = event.getError() != null ? Logger.Level.WARN : Logger.Level.INFO;
|
||||||
|
|
||||||
if (logger.isEnabled(level)) {
|
if (logger.isEnabled(level)) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
@ -35,7 +35,7 @@ public class JBossLoggingAuditListener implements AuditListener {
|
||||||
sb.append(", ipAddress=");
|
sb.append(", ipAddress=");
|
||||||
sb.append(event.getIpAddress());
|
sb.append(event.getIpAddress());
|
||||||
|
|
||||||
if (event.isError()) {
|
if (event.getError() != null) {
|
||||||
sb.append(", error=");
|
sb.append(", error=");
|
||||||
sb.append(event.getError());
|
sb.append(event.getError());
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,12 @@ public class JpaEventQuery implements EventQuery {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery ipAddress(String ipAddress) {
|
||||||
|
predicates.add(cb.equal(root.get("ipAddress"), ipAddress));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventQuery firstResult(int firstResult) {
|
public EventQuery firstResult(int firstResult) {
|
||||||
this.firstResult = firstResult;
|
this.firstResult = firstResult;
|
||||||
|
|
|
@ -48,6 +48,12 @@ public class MongoEventQuery implements EventQuery {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventQuery ipAddress(String ipAddress) {
|
||||||
|
query.put("ipAddress", ipAddress);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventQuery firstResult(int firstResult) {
|
public EventQuery firstResult(int firstResult) {
|
||||||
this.firstResult = firstResult;
|
this.firstResult = firstResult;
|
||||||
|
|
|
@ -15,13 +15,15 @@ public class AdminRoles {
|
||||||
public static String VIEW_USERS = "view-users";
|
public static String VIEW_USERS = "view-users";
|
||||||
public static String VIEW_APPLICATIONS = "view-applications";
|
public static String VIEW_APPLICATIONS = "view-applications";
|
||||||
public static String VIEW_CLIENTS = "view-clients";
|
public static String VIEW_CLIENTS = "view-clients";
|
||||||
|
public static String VIEW_AUDIT = "view-audit";
|
||||||
|
|
||||||
public static String MANAGE_REALM = "manage-realm";
|
public static String MANAGE_REALM = "manage-realm";
|
||||||
public static String MANAGE_USERS = "manage-users";
|
public static String MANAGE_USERS = "manage-users";
|
||||||
public static String MANAGE_APPLICATIONS = "manage-applications";
|
public static String MANAGE_APPLICATIONS = "manage-applications";
|
||||||
public static String MANAGE_CLIENTS = "manage-clients";
|
public static String MANAGE_CLIENTS = "manage-clients";
|
||||||
|
public static String MANAGE_AUDIT = "manage-audit";
|
||||||
|
|
||||||
public static String[] ALL_REALM_ROLES = {VIEW_REALM, VIEW_USERS, VIEW_APPLICATIONS, VIEW_CLIENTS, MANAGE_REALM, MANAGE_USERS, MANAGE_APPLICATIONS, MANAGE_CLIENTS};
|
public static String[] ALL_REALM_ROLES = {VIEW_REALM, VIEW_USERS, VIEW_APPLICATIONS, VIEW_CLIENTS, VIEW_AUDIT, MANAGE_REALM, MANAGE_USERS, MANAGE_APPLICATIONS, MANAGE_CLIENTS, MANAGE_AUDIT};
|
||||||
|
|
||||||
public static String getAdminApp(RealmModel realm) {
|
public static String getAdminApp(RealmModel realm) {
|
||||||
return realm.getName() + APP_SUFFIX;
|
return realm.getName() + APP_SUFFIX;
|
||||||
|
|
|
@ -95,7 +95,7 @@ public class AccountService {
|
||||||
private static final Logger logger = Logger.getLogger(AccountService.class);
|
private static final Logger logger = Logger.getLogger(AccountService.class);
|
||||||
|
|
||||||
private static final String[] AUDIT_EVENTS = {Events.LOGIN, Events.LOGOUT, Events.REGISTER, Events.REMOVE_SOCIAL_LINK, Events.REMOVE_TOTP, Events.SEND_RESET_PASSWORD,
|
private static final String[] AUDIT_EVENTS = {Events.LOGIN, Events.LOGOUT, Events.REGISTER, Events.REMOVE_SOCIAL_LINK, Events.REMOVE_TOTP, Events.SEND_RESET_PASSWORD,
|
||||||
Events.SEND_VERIFY_EMAIL, Events.SOCIAL_LINK, Events.UPDATE_EMAIL, Events.UPDATE_PASSWORD, Events.UPDATE_PASSWORD, Events.UPDATE_TOTP, Events.VERIFY_EMAIL};
|
Events.SEND_VERIFY_EMAIL, Events.SOCIAL_LINK, Events.UPDATE_EMAIL, Events.UPDATE_PASSWORD, Events.UPDATE_PROFILE, Events.UPDATE_TOTP, Events.VERIFY_EMAIL};
|
||||||
|
|
||||||
private static final Set<String> AUDIT_DETAILS = new HashSet<String>();
|
private static final Set<String> AUDIT_DETAILS = new HashSet<String>();
|
||||||
static {
|
static {
|
||||||
|
|
|
@ -243,7 +243,7 @@ public class SocialResource {
|
||||||
realm.addSocialLink(user, socialLink);
|
realm.addSocialLink(user, socialLink);
|
||||||
|
|
||||||
audit.clone().user(user).event(Events.REGISTER)
|
audit.clone().user(user).event(Events.REGISTER)
|
||||||
.detail(Details.REGISTER_METHOD, "social")
|
.detail(Details.REGISTER_METHOD, "social@" + provider.getId())
|
||||||
.detail(Details.EMAIL, socialUser.getEmail())
|
.detail(Details.EMAIL, socialUser.getEmail())
|
||||||
.removeDetail("auth_method")
|
.removeDetail("auth_method")
|
||||||
.success();
|
.success();
|
||||||
|
@ -256,7 +256,7 @@ public class SocialResource {
|
||||||
return oauth.forwardToSecurityFailure("Your account is not enabled.");
|
return oauth.forwardToSecurityFailure("Your account is not enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return oauth.processAccessCode(scope, state, redirectUri, client, user, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social", audit);
|
return oauth.processAccessCode(scope, state, redirectUri, client, user, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social@" + provider.getId(), audit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|
|
@ -2,11 +2,15 @@ package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.logging.Logger;
|
import org.jboss.resteasy.logging.Logger;
|
||||||
|
import org.keycloak.audit.AuditProvider;
|
||||||
|
import org.keycloak.audit.Event;
|
||||||
|
import org.keycloak.audit.EventQuery;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.adapters.action.SessionStats;
|
import org.keycloak.representations.adapters.action.SessionStats;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.services.ProviderSession;
|
||||||
import org.keycloak.services.managers.ModelToRepresentation;
|
import org.keycloak.services.managers.ModelToRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
|
@ -36,6 +40,9 @@ public class RealmAdminResource {
|
||||||
@Context
|
@Context
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected ProviderSession providers;
|
||||||
|
|
||||||
public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
|
public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -129,7 +136,7 @@ public class RealmAdminResource {
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Map<String,SessionStats> getSessionStats() {
|
public Map<String, SessionStats> getSessionStats() {
|
||||||
logger.info("session-stats");
|
logger.info("session-stats");
|
||||||
auth.requireView();
|
auth.requireView();
|
||||||
Map<String, SessionStats> stats = new HashMap<String, SessionStats>();
|
Map<String, SessionStats> stats = new HashMap<String, SessionStats>();
|
||||||
|
@ -141,4 +148,37 @@ public class RealmAdminResource {
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("audit")
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public List<Event> getAudit(@QueryParam("client") String client, @QueryParam("event") String event, @QueryParam("user") String user,
|
||||||
|
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
|
||||||
|
auth.init(RealmAuth.Resource.AUDIT).requireView();
|
||||||
|
|
||||||
|
AuditProvider audit = providers.getProvider(AuditProvider.class);
|
||||||
|
|
||||||
|
EventQuery query = audit.createQuery().realm(realm.getId());
|
||||||
|
if (client != null) {
|
||||||
|
query.client(client);
|
||||||
|
}
|
||||||
|
if (event != null) {
|
||||||
|
query.event(event);
|
||||||
|
}
|
||||||
|
if (user != null) {
|
||||||
|
query.user(user);
|
||||||
|
}
|
||||||
|
if (ipAddress != null) {
|
||||||
|
query.ipAddress(ipAddress);
|
||||||
|
}
|
||||||
|
if (firstResult != null) {
|
||||||
|
query.firstResult(firstResult);
|
||||||
|
}
|
||||||
|
if (maxResults != null) {
|
||||||
|
query.maxResults(maxResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ public class RealmAuth {
|
||||||
private Resource resource;
|
private Resource resource;
|
||||||
|
|
||||||
public enum Resource {
|
public enum Resource {
|
||||||
APPLICATION, CLIENT, USER, REALM
|
APPLICATION, CLIENT, USER, REALM, AUDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
private Auth auth;
|
private Auth auth;
|
||||||
|
@ -65,6 +65,8 @@ public class RealmAuth {
|
||||||
return AdminRoles.VIEW_USERS;
|
return AdminRoles.VIEW_USERS;
|
||||||
case REALM:
|
case REALM:
|
||||||
return AdminRoles.VIEW_REALM;
|
return AdminRoles.VIEW_REALM;
|
||||||
|
case AUDIT:
|
||||||
|
return AdminRoles.VIEW_AUDIT;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
@ -80,6 +82,8 @@ public class RealmAuth {
|
||||||
return AdminRoles.MANAGE_USERS;
|
return AdminRoles.MANAGE_USERS;
|
||||||
case REALM:
|
case REALM:
|
||||||
return AdminRoles.MANAGE_REALM;
|
return AdminRoles.MANAGE_REALM;
|
||||||
|
case AUDIT:
|
||||||
|
return AdminRoles.MANAGE_AUDIT;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,12 +113,12 @@ public class SocialLoginTest {
|
||||||
.user(AssertEvents.isUUID())
|
.user(AssertEvents.isUUID())
|
||||||
.detail(Details.EMAIL, "bob@builder.com")
|
.detail(Details.EMAIL, "bob@builder.com")
|
||||||
.detail(Details.RESPONSE_TYPE, "code")
|
.detail(Details.RESPONSE_TYPE, "code")
|
||||||
.detail(Details.REGISTER_METHOD, "social")
|
.detail(Details.REGISTER_METHOD, "social@dummy")
|
||||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||||
.detail(Details.USERNAME, "1@dummy")
|
.detail(Details.USERNAME, "1@dummy")
|
||||||
.assertEvent().getUserId();
|
.assertEvent().getUserId();
|
||||||
|
|
||||||
String codeId = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social").assertEvent().getDetails().get(Details.CODE_ID);
|
String codeId = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent().getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ public class SocialLoginTest {
|
||||||
driver.findElement(By.id("username")).sendKeys("dummy-user1");
|
driver.findElement(By.id("username")).sendKeys("dummy-user1");
|
||||||
driver.findElement(By.id("login")).click();
|
driver.findElement(By.id("login")).click();
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -160,7 +160,7 @@ public class SocialLoginTest {
|
||||||
Assert.assertTrue(loginPage.isCurrent());
|
Assert.assertTrue(loginPage.isCurrent());
|
||||||
Assert.assertEquals("Access denied", loginPage.getWarning());
|
Assert.assertEquals("Access denied", loginPage.getWarning());
|
||||||
|
|
||||||
events.expectLogin().error("rejected_by_user").user((String) null).detail(Details.AUTH_METHOD, "social").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
events.expectLogin().error("rejected_by_user").user((String) null).detail(Details.AUTH_METHOD, "social@dummy").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||||
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
@ -200,19 +200,19 @@ public class SocialLoginTest {
|
||||||
.user(AssertEvents.isUUID())
|
.user(AssertEvents.isUUID())
|
||||||
.detail(Details.EMAIL, "bob@builder.com")
|
.detail(Details.EMAIL, "bob@builder.com")
|
||||||
.detail(Details.RESPONSE_TYPE, "code")
|
.detail(Details.RESPONSE_TYPE, "code")
|
||||||
.detail(Details.REGISTER_METHOD, "social")
|
.detail(Details.REGISTER_METHOD, "social@dummy")
|
||||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||||
.detail(Details.USERNAME, "2@dummy")
|
.detail(Details.USERNAME, "2@dummy")
|
||||||
.assertEvent().getUserId();
|
.assertEvent().getUserId();
|
||||||
|
|
||||||
profilePage.update("Dummy", "User", "dummy-user-reg@dummy-social");
|
profilePage.update("Dummy", "User", "dummy-user-reg@dummy-social");
|
||||||
|
|
||||||
events.expectRequiredAction("update_profile").user(userId).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").assertEvent();
|
events.expectRequiredAction("update_profile").user(userId).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").assertEvent();
|
||||||
events.expectRequiredAction("update_email").user(userId).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").detail(Details.PREVIOUS_EMAIL, "bob@builder.com").detail(Details.UPDATED_EMAIL, "dummy-user-reg@dummy-social").assertEvent();
|
events.expectRequiredAction("update_email").user(userId).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").detail(Details.PREVIOUS_EMAIL, "bob@builder.com").detail(Details.UPDATED_EMAIL, "dummy-user-reg@dummy-social").assertEvent();
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
String codeId = events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").assertEvent().getDetails().get(Details.CODE_ID);
|
String codeId = events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").assertEvent().getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||||
|
|
Loading…
Reference in a new issue