Merge pull request #317 from stianst/master
Added audit to admin console
This commit is contained in:
commit
afea032322
19 changed files with 355 additions and 36 deletions
|
@ -11,6 +11,9 @@ table caption {
|
|||
table tbody tr:nth-child(even) {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
table tbody tr:nth-child(odd) {
|
||||
background-color: #fff;
|
||||
}
|
||||
table tbody tr td,
|
||||
table thead tr th {
|
||||
font-weight: normal;
|
||||
|
@ -138,10 +141,14 @@ table tfoot tr {
|
|||
table tfoot tr .table-nav {
|
||||
float: right;
|
||||
}
|
||||
table tfoot tr .table-nav a {
|
||||
table tfoot tr .table-nav a,
|
||||
table tfoot tr .table-nav button {
|
||||
display: inline-block;
|
||||
line-height: 2.4em;
|
||||
line-height: 22px;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
width: 3.5em;
|
||||
background-color: #f3f3f3;
|
||||
background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
|
||||
|
@ -156,28 +163,35 @@ table tfoot tr .table-nav a {
|
|||
background-position: left 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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-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;
|
||||
}
|
||||
table tfoot tr .table-nav a.disabled {
|
||||
table tfoot tr .table-nav a.disabled,
|
||||
table tfoot tr .table-nav button:disabled {
|
||||
opacity: 0.5;
|
||||
filter: alpha(opacity=50);
|
||||
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;
|
||||
}
|
||||
table tfoot tr .table-nav span {
|
||||
|
@ -195,3 +209,20 @@ table tfoot tr .table-nav span {
|
|||
td .form-group {
|
||||
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'
|
||||
})
|
||||
.when('/realms/:realm/audit', {
|
||||
templateUrl : 'partials/realm-audit.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmAuditCtrl'
|
||||
})
|
||||
.when('/create/user/:realm', {
|
||||
templateUrl : 'partials/user-detail.html',
|
||||
resolve : {
|
||||
|
|
|
@ -42,6 +42,10 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
|
|||
return getAccess('view-users') || this.manageClients;
|
||||
},
|
||||
|
||||
get viewAudit() {
|
||||
return getAccess('view-audit') || this.manageClients;
|
||||
},
|
||||
|
||||
get manageRealm() {
|
||||
return getAccess('manage-realm');
|
||||
},
|
||||
|
@ -56,6 +60,10 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
|
|||
|
||||
get manageUsers() {
|
||||
return getAccess('manage-users');
|
||||
},
|
||||
|
||||
get manageAudit() {
|
||||
return getAccess('manage-audit');
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -915,3 +923,48 @@ module.controller('RealmLdapSettingsCtrl', function($scope, Realm, realm, $locat
|
|||
$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) {
|
||||
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.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.viewAudit" data-ng-class="(path[2] == 'audit') && 'active'"><a href="#/realms/{{realm.realm}}/audit">Audit</a></li>
|
||||
</ul>
|
|
@ -72,10 +72,6 @@ public class Event {
|
|||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error != null;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ public interface EventQuery {
|
|||
|
||||
public EventQuery user(String userId);
|
||||
|
||||
public EventQuery ipAddress(String ipAddress);
|
||||
|
||||
public EventQuery firstResult(int result);
|
||||
|
||||
public EventQuery maxResults(int results);
|
||||
|
|
|
@ -19,7 +19,7 @@ public class JBossLoggingAuditListener implements AuditListener {
|
|||
|
||||
@Override
|
||||
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)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -35,7 +35,7 @@ public class JBossLoggingAuditListener implements AuditListener {
|
|||
sb.append(", ipAddress=");
|
||||
sb.append(event.getIpAddress());
|
||||
|
||||
if (event.isError()) {
|
||||
if (event.getError() != null) {
|
||||
sb.append(", error=");
|
||||
sb.append(event.getError());
|
||||
}
|
||||
|
|
|
@ -59,6 +59,12 @@ public class JpaEventQuery implements EventQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery ipAddress(String ipAddress) {
|
||||
predicates.add(cb.equal(root.get("ipAddress"), ipAddress));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery firstResult(int firstResult) {
|
||||
this.firstResult = firstResult;
|
||||
|
|
|
@ -48,6 +48,12 @@ public class MongoEventQuery implements EventQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery ipAddress(String ipAddress) {
|
||||
query.put("ipAddress", ipAddress);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery firstResult(int firstResult) {
|
||||
this.firstResult = firstResult;
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.audit.Event;
|
|||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -37,7 +38,7 @@ public class LogBean {
|
|||
}
|
||||
|
||||
public String getEvent() {
|
||||
return event.getEvent().replace('_', ' ');
|
||||
return event.getEvent();
|
||||
}
|
||||
|
||||
public String getClient() {
|
||||
|
@ -48,6 +49,32 @@ public class LogBean {
|
|||
return event.getIpAddress();
|
||||
}
|
||||
|
||||
public List<DetailBean> getDetails() {
|
||||
List<DetailBean> details = new LinkedList<DetailBean>();
|
||||
for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
|
||||
details.add(new DetailBean(e));
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class DetailBean {
|
||||
|
||||
private Map.Entry<String, String> entry;
|
||||
|
||||
public DetailBean(Map.Entry<String, String> entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return entry.getKey();
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<td>Event</td>
|
||||
<td>IP</td>
|
||||
<td>Client</td>
|
||||
<td>Details</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
@ -23,10 +24,12 @@
|
|||
<td>${event.date?datetime}</td>
|
||||
<td>${event.event}</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>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</@layout.mainLayout>
|
|
@ -15,13 +15,15 @@ public class AdminRoles {
|
|||
public static String VIEW_USERS = "view-users";
|
||||
public static String VIEW_APPLICATIONS = "view-applications";
|
||||
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_USERS = "manage-users";
|
||||
public static String MANAGE_APPLICATIONS = "manage-applications";
|
||||
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) {
|
||||
return realm.getName() + APP_SUFFIX;
|
||||
|
|
|
@ -78,7 +78,13 @@ import javax.ws.rs.core.UriBuilder;
|
|||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.core.Variant;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
@ -88,6 +94,20 @@ public class AccountService {
|
|||
|
||||
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,
|
||||
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>();
|
||||
static {
|
||||
AUDIT_DETAILS.add(Details.UPDATED_EMAIL);
|
||||
AUDIT_DETAILS.add(Details.EMAIL);
|
||||
AUDIT_DETAILS.add(Details.PREVIOUS_EMAIL);
|
||||
AUDIT_DETAILS.add(Details.USERNAME);
|
||||
AUDIT_DETAILS.add(Details.REMEMBER_ME);
|
||||
AUDIT_DETAILS.add(Details.REGISTER_METHOD);
|
||||
AUDIT_DETAILS.add(Details.AUTH_METHOD);
|
||||
}
|
||||
|
||||
public static final String KEYCLOAK_ACCOUNT_IDENTITY_COOKIE = "KEYCLOAK_ACCOUNT_IDENTITY";
|
||||
|
||||
private RealmModel realm;
|
||||
|
@ -198,7 +218,20 @@ public class AccountService {
|
|||
@GET
|
||||
public Response logPage() {
|
||||
if (auth != null) {
|
||||
List<Event> events = auditProvider.createQuery().user(auth.getUser().getId()).maxResults(20).getResultList();
|
||||
List<Event> events = auditProvider.createQuery().event(AUDIT_EVENTS).user(auth.getUser().getId()).maxResults(30).getResultList();
|
||||
for (Event e : events) {
|
||||
e.setEvent(e.getEvent().replace('_', ' '));
|
||||
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
Iterator<String> itr = e.getDetails().keySet().iterator();
|
||||
for (Map.Entry<String, String> d : e.getDetails().entrySet()) {
|
||||
if (AUDIT_DETAILS.contains(d.getKey())) {
|
||||
details.put(d.getKey().replace('_', ' '), d.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
e.setDetails(details);
|
||||
}
|
||||
account.setEvents(events);
|
||||
}
|
||||
return forwardToPage("log", AccountPages.LOG);
|
||||
|
|
|
@ -132,7 +132,7 @@ public class SocialResource {
|
|||
Audit audit = createAudit(realm)
|
||||
.event(Events.LOGIN)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "social");
|
||||
.detail(Details.AUTH_METHOD, "social@" + provider.getId());
|
||||
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
|
@ -243,7 +243,7 @@ public class SocialResource {
|
|||
realm.addSocialLink(user, socialLink);
|
||||
|
||||
audit.clone().user(user).event(Events.REGISTER)
|
||||
.detail(Details.REGISTER_METHOD, "social")
|
||||
.detail(Details.REGISTER_METHOD, "social@" + provider.getId())
|
||||
.detail(Details.EMAIL, socialUser.getEmail())
|
||||
.removeDetail("auth_method")
|
||||
.success();
|
||||
|
@ -256,7 +256,7 @@ public class SocialResource {
|
|||
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
|
||||
|
@ -272,7 +272,7 @@ public class SocialResource {
|
|||
.event(Events.LOGIN).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirectUri)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "social");
|
||||
.detail(Details.AUTH_METHOD, "social@" + providerId);
|
||||
|
||||
SocialProvider provider = SocialLoader.load(providerId);
|
||||
if (provider == null) {
|
||||
|
|
|
@ -2,11 +2,15 @@ package org.keycloak.services.resources.admin;
|
|||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
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.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.adapters.action.SessionStats;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.ProviderSession;
|
||||
import org.keycloak.services.managers.ModelToRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
|
@ -36,6 +40,9 @@ public class RealmAdminResource {
|
|||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected ProviderSession providers;
|
||||
|
||||
public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
|
||||
this.auth = auth;
|
||||
this.realm = realm;
|
||||
|
@ -141,4 +148,37 @@ public class RealmAdminResource {
|
|||
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;
|
||||
|
||||
public enum Resource {
|
||||
APPLICATION, CLIENT, USER, REALM
|
||||
APPLICATION, CLIENT, USER, REALM, AUDIT
|
||||
}
|
||||
|
||||
private Auth auth;
|
||||
|
@ -65,6 +65,8 @@ public class RealmAuth {
|
|||
return AdminRoles.VIEW_USERS;
|
||||
case REALM:
|
||||
return AdminRoles.VIEW_REALM;
|
||||
case AUDIT:
|
||||
return AdminRoles.VIEW_AUDIT;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
@ -80,6 +82,8 @@ public class RealmAuth {
|
|||
return AdminRoles.MANAGE_USERS;
|
||||
case REALM:
|
||||
return AdminRoles.MANAGE_REALM;
|
||||
case AUDIT:
|
||||
return AdminRoles.MANAGE_AUDIT;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
|
|
@ -113,12 +113,12 @@ public class SocialLoginTest {
|
|||
.user(AssertEvents.isUUID())
|
||||
.detail(Details.EMAIL, "bob@builder.com")
|
||||
.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.USERNAME, "1@dummy")
|
||||
.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");
|
||||
|
||||
|
@ -146,7 +146,7 @@ public class SocialLoginTest {
|
|||
driver.findElement(By.id("username")).sendKeys("dummy-user1");
|
||||
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
|
||||
|
@ -160,7 +160,7 @@ public class SocialLoginTest {
|
|||
Assert.assertTrue(loginPage.isCurrent());
|
||||
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");
|
||||
|
||||
|
@ -200,19 +200,19 @@ public class SocialLoginTest {
|
|||
.user(AssertEvents.isUUID())
|
||||
.detail(Details.EMAIL, "bob@builder.com")
|
||||
.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.USERNAME, "2@dummy")
|
||||
.assertEvent().getUserId();
|
||||
|
||||
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_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_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@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());
|
||||
|
||||
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");
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
|
Loading…
Reference in a new issue