Merge pull request #324 from stianst/master
Audit configurable through admin console
This commit is contained in:
commit
a197bda363
26 changed files with 509 additions and 92 deletions
|
@ -141,6 +141,21 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmAuditEventsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/audit-settings', {
|
||||
templateUrl : 'partials/realm-audit-config.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
},
|
||||
auditConfig : function(RealmAuditLoader) {
|
||||
return RealmAuditLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmAuditCtrl'
|
||||
})
|
||||
.when('/realms/:realm/auth-settings', {
|
||||
|
|
|
@ -1022,7 +1022,65 @@ module.controller('RealmAuthSettingsDetailCtrl', function($scope, $routeParams,
|
|||
};
|
||||
});
|
||||
|
||||
module.controller('RealmAuditCtrl', function($scope, RealmAudit, realm) {
|
||||
module.controller('RealmAuditCtrl', function($scope, auditConfig, RealmAudit, RealmAuditEvents, realm, serverInfo, $location, Notifications, TimeUnit, Dialog) {
|
||||
$scope.realm = realm;
|
||||
|
||||
$scope.auditConfig = auditConfig;
|
||||
|
||||
$scope.auditConfig.expirationUnit = TimeUnit.autoUnit(auditConfig.auditExpiration);
|
||||
if ($scope.auditConfig.expirationUnit) {
|
||||
$scope.auditConfig.expirationUnit = 'Hours';
|
||||
}
|
||||
|
||||
$scope.auditConfig.auditExpiration = TimeUnit.toUnit(auditConfig.auditExpiration, $scope.auditConfig.expirationUnit);
|
||||
$scope.$watch('auditConfig.expirationUnit', function(to, from) {
|
||||
if ($scope.auditConfig.auditExpiration) {
|
||||
$scope.auditConfig.auditExpiration = TimeUnit.convert($scope.auditConfig.auditExpiration, from, to);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.auditListeners = serverInfo.auditListeners;
|
||||
|
||||
var oldCopy = angular.copy($scope.auditConfig);
|
||||
$scope.changed = false;
|
||||
|
||||
$scope.$watch('auditConfig', function() {
|
||||
if (!angular.equals($scope.auditConfig, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
$scope.changed = false;
|
||||
|
||||
var copy = angular.copy($scope.auditConfig)
|
||||
delete copy['expirationUnit'];
|
||||
|
||||
copy.auditExpiration = TimeUnit.toSeconds($scope.auditConfig.auditExpiration, $scope.auditConfig.expirationUnit);
|
||||
|
||||
RealmAudit.update({
|
||||
id : realm.realm
|
||||
}, copy, function () {
|
||||
$location.url("/realms/" + realm.realm + "/audit-settings");
|
||||
Notifications.success("Your changes have been saved to the realm.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.auditConfig = angular.copy(oldCopy);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
$scope.clearAudit = function() {
|
||||
Dialog.confirmDelete($scope.realm.realm, 'audit events', function() {
|
||||
RealmAuditEvents.remove({ id : $scope.realm.realm }, function() {
|
||||
Notifications.success("The audit events has been cleared.");
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('RealmAuditEventsCtrl', function($scope, RealmAuditEvents, realm) {
|
||||
$scope.realm = realm;
|
||||
$scope.page = 0;
|
||||
|
||||
|
@ -1038,8 +1096,7 @@ module.controller('RealmAuditCtrl', function($scope, RealmAudit, realm) {
|
|||
delete $scope.query[i];
|
||||
}
|
||||
}
|
||||
console.debug($scope.query.first);
|
||||
$scope.events = RealmAudit.query($scope.query);
|
||||
$scope.events = RealmAuditEvents.query($scope.query);
|
||||
}
|
||||
|
||||
$scope.firstPage = function() {
|
||||
|
|
|
@ -47,6 +47,14 @@ module.factory('RealmLoader', function(Loader, Realm, $route, $q) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('RealmAuditLoader', function(Loader, RealmAudit, $route, $q) {
|
||||
return Loader.get(RealmAudit, function() {
|
||||
return {
|
||||
id : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserListLoader', function(Loader, User, $route, $q) {
|
||||
return Loader.query(User, function() {
|
||||
return {
|
||||
|
|
|
@ -145,6 +145,16 @@ module.factory('Realm', function($resource) {
|
|||
module.factory('RealmAudit', function($resource) {
|
||||
return $resource(authUrl + '/rest/admin/realms/:id/audit', {
|
||||
id : '@realm'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RealmAuditEvents', function($resource) {
|
||||
return $resource(authUrl + '/rest/admin/realms/:id/audit/events', {
|
||||
id : '@realm'
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<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">
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf">
|
||||
<li data-ng-class="(path[2] == 'audit') && 'active'"><a href="#/realms/{{realm.realm}}/audit">View</a></li>
|
||||
<li data-ng-class="(path[2] == 'audit-settings') && 'active'"><a href="#/realms/{{realm.realm}}/audit-settings">Config</a></li>
|
||||
</ul>
|
||||
|
||||
<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 Config</h2>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageAudit">
|
||||
<fieldset>
|
||||
<div class="form-group" data-ng-show="access.manageAudit">
|
||||
<label class="col-sm-2 control-label" for="password">Clear Audit</label>
|
||||
<div class="col-sm-4">
|
||||
<button class="btn btn-danger" type="submit" data-ng-click="clearAudit()" >Clear Audit</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="auditConfig.auditEnabled" name="enabled" id="enabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group input-select">
|
||||
<label class="col-sm-2 control-label" for="expiration">Expiration</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control" type="number"
|
||||
data-ng-model="auditConfig.auditExpiration"
|
||||
id="expiration" name="expiration"/>
|
||||
</div>
|
||||
<div class="col-sm-2 select-kc">
|
||||
<select name="expirationUnit" data-ng-model="auditConfig.expirationUnit" >
|
||||
<option>Hours</option>
|
||||
<option>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="auditListeners" class="control-label">Audit Listeners</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<select ui-select2 ng-model="auditConfig.auditListeners" data-placeholder="Select an action..." multiple>
|
||||
<option ng-repeat="listener in auditListeners" value="{{listener}}">{{listener}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="access.manageAudit">
|
||||
<button data-kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button data-kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,6 +1,11 @@
|
|||
<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>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf">
|
||||
<li data-ng-class="(path[2] == 'audit') && 'active'"><a href="#/realms/{{realm.realm}}/audit">View</a></li>
|
||||
<li data-ng-class="(path[2] == 'audit-settings') && 'active'"><a href="#/realms/{{realm.realm}}/audit-settings">Config</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
|
|
|
@ -9,5 +9,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>
|
||||
<li data-ng-show="access.viewAudit" data-ng-class="(path[2] == 'audit' || path[2] == 'audit-settings') && 'active'"><a href="#/realms/{{realm.realm}}/audit">Audit</a></li>
|
||||
</ul>
|
|
@ -7,8 +7,8 @@ public interface AuditProvider extends AuditListener {
|
|||
|
||||
public EventQuery createQuery();
|
||||
|
||||
public void clear();
|
||||
public void clear(String realmId);
|
||||
|
||||
public void clear(long olderThan);
|
||||
public void clear(String realmId, long olderThan);
|
||||
|
||||
}
|
||||
|
|
|
@ -29,15 +29,15 @@ public class JpaAuditProvider implements AuditProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
public void clear(String realmId) {
|
||||
beginTx();
|
||||
em.createQuery("delete from EventEntity").executeUpdate();
|
||||
em.createQuery("delete from EventEntity where realmId = :realmId").setParameter("realmId", realmId).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(long olderThan) {
|
||||
public void clear(String realmId, long olderThan) {
|
||||
beginTx();
|
||||
em.createQuery("delete from EventEntity where time < :time").setParameter("time", olderThan).executeUpdate();
|
||||
em.createQuery("delete from EventEntity where realmId = :realmId and time < :time").setParameter("realmId", realmId).setParameter("time", olderThan).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.audit.mongo;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBObject;
|
||||
|
@ -27,13 +28,16 @@ public class MongoAuditProvider implements AuditProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
audit.remove(new BasicDBObject());
|
||||
public void clear(String realmId) {
|
||||
audit.remove(new BasicDBObject("realmId", realmId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(long olderThan) {
|
||||
audit.remove(new BasicDBObject("time", new BasicDBObject("$lt", olderThan)));
|
||||
public void clear(String realmId, long olderThan) {
|
||||
BasicDBObject q = new BasicDBObject();
|
||||
q.put("realmId", realmId);
|
||||
q.put("time", new BasicDBObject("$lt", olderThan));
|
||||
audit.remove(q);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,7 +34,8 @@ public abstract class AbstractAuditProviderTest {
|
|||
|
||||
@After
|
||||
public void after() {
|
||||
provider.clear();
|
||||
provider.clear("realmId");
|
||||
provider.clear("realmId2");
|
||||
provider.close();
|
||||
factory.close();
|
||||
}
|
||||
|
@ -51,6 +52,7 @@ public abstract class AbstractAuditProviderTest {
|
|||
|
||||
provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(newest, "event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(newest, "event2", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
|
||||
provider.onEvent(create("event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(oldest, "event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
|
||||
|
@ -58,19 +60,19 @@ public abstract class AbstractAuditProviderTest {
|
|||
provider.close();
|
||||
provider = factory.create();
|
||||
|
||||
Assert.assertEquals(4, provider.createQuery().client("clientId").getResultList().size());
|
||||
Assert.assertEquals(4, provider.createQuery().realm("realmId").getResultList().size());
|
||||
Assert.assertEquals(5, provider.createQuery().client("clientId").getResultList().size());
|
||||
Assert.assertEquals(5, provider.createQuery().realm("realmId").getResultList().size());
|
||||
Assert.assertEquals(4, provider.createQuery().event("event").getResultList().size());
|
||||
Assert.assertEquals(5, provider.createQuery().event("event", "event2").getResultList().size());
|
||||
Assert.assertEquals(6, provider.createQuery().event("event", "event2").getResultList().size());
|
||||
Assert.assertEquals(4, provider.createQuery().user("userId").getResultList().size());
|
||||
|
||||
Assert.assertEquals(1, provider.createQuery().user("userId").event("event2").getResultList().size());
|
||||
|
||||
Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
|
||||
Assert.assertEquals(1, provider.createQuery().firstResult(4).getResultList().size());
|
||||
Assert.assertEquals(1, provider.createQuery().firstResult(5).getResultList().size());
|
||||
|
||||
Assert.assertEquals(newest, provider.createQuery().maxResults(1).getResultList().get(0).getTime());
|
||||
Assert.assertEquals(oldest, provider.createQuery().firstResult(4).maxResults(1).getResultList().get(0).getTime());
|
||||
Assert.assertEquals(oldest, provider.createQuery().firstResult(5).maxResults(1).getResultList().get(0).getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -79,13 +81,14 @@ public abstract class AbstractAuditProviderTest {
|
|||
provider.onEvent(create(System.currentTimeMillis() - 20000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
|
||||
|
||||
provider.close();
|
||||
provider = factory.create();
|
||||
|
||||
provider.clear();
|
||||
provider.clear("realmId");
|
||||
|
||||
Assert.assertEquals(0, provider.createQuery().getResultList().size());
|
||||
Assert.assertEquals(1, provider.createQuery().getResultList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -94,13 +97,14 @@ public abstract class AbstractAuditProviderTest {
|
|||
provider.onEvent(create(System.currentTimeMillis() - 20000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
|
||||
provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
|
||||
|
||||
provider.close();
|
||||
provider = factory.create();
|
||||
|
||||
provider.clear(System.currentTimeMillis() - 10000);
|
||||
provider.clear("realmId", System.currentTimeMillis() - 10000);
|
||||
|
||||
Assert.assertEquals(2, provider.createQuery().getResultList().size());
|
||||
Assert.assertEquals(3, provider.createQuery().getResultList().size());
|
||||
}
|
||||
|
||||
private Event create(String event, String realmId, String clientId, String userId, String ipAddress, String error) {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RealmAuditRepresentation {
|
||||
protected boolean auditEnabled;
|
||||
protected Long auditExpiration;
|
||||
protected List<String> auditListeners;
|
||||
|
||||
public boolean isAuditEnabled() {
|
||||
return auditEnabled;
|
||||
}
|
||||
|
||||
public void setAuditEnabled(boolean auditEnabled) {
|
||||
this.auditEnabled = auditEnabled;
|
||||
}
|
||||
|
||||
public Long getAuditExpiration() {
|
||||
return auditExpiration;
|
||||
}
|
||||
|
||||
public void setAuditExpiration(Long auditExpiration) {
|
||||
this.auditExpiration = auditExpiration;
|
||||
}
|
||||
|
||||
public List<String> getAuditListeners() {
|
||||
return auditListeners;
|
||||
}
|
||||
|
||||
public void setAuditListeners(List<String> auditListeners) {
|
||||
this.auditListeners = auditListeners;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -47,6 +48,9 @@ public class RealmRepresentation {
|
|||
protected List<AuthenticationProviderRepresentation> authenticationProviders;
|
||||
protected String loginTheme;
|
||||
protected String accountTheme;
|
||||
protected boolean auditEnabled;
|
||||
protected long auditExpiration;
|
||||
protected List<String> auditListeners;
|
||||
|
||||
public String getSelf() {
|
||||
return self;
|
||||
|
|
|
@ -208,6 +208,14 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
|||
|
||||
boolean removeRoleById(String id);
|
||||
|
||||
boolean isAuditEnabled();
|
||||
|
||||
void setAuditEnabled(boolean enabled);
|
||||
|
||||
long getAuditExpiration();
|
||||
|
||||
void setAuditExpiration(long expiration);
|
||||
|
||||
Set<String> getAuditListeners();
|
||||
|
||||
void setAuditListeners(Set<String> listeners);
|
||||
|
|
|
@ -1184,6 +1184,28 @@ public class RealmAdapter implements RealmModel {
|
|||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuditEnabled() {
|
||||
return realm.isAuditEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuditEnabled(boolean enabled) {
|
||||
realm.setAuditEnabled(enabled);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAuditExpiration() {
|
||||
return realm.getAuditExpiration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuditExpiration(long expiration) {
|
||||
realm.setAuditExpiration(expiration);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAuditListeners() {
|
||||
return realm.getAuditListeners();
|
||||
|
|
|
@ -99,8 +99,11 @@ public class RealmEntity {
|
|||
@JoinTable(name="RealmDefaultRoles")
|
||||
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
|
||||
|
||||
private boolean auditEnabled;
|
||||
private long auditExpiration;
|
||||
|
||||
@ElementCollection
|
||||
protected Set<String> auditListeners= new HashSet<String>();
|
||||
private Set<String> auditListeners= new HashSet<String>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -349,6 +352,22 @@ public class RealmEntity {
|
|||
this.bruteForceProtected = bruteForceProtected;
|
||||
}
|
||||
|
||||
public boolean isAuditEnabled() {
|
||||
return auditEnabled;
|
||||
}
|
||||
|
||||
public void setAuditEnabled(boolean auditEnabled) {
|
||||
this.auditEnabled = auditEnabled;
|
||||
}
|
||||
|
||||
public long getAuditExpiration() {
|
||||
return auditExpiration;
|
||||
}
|
||||
|
||||
public void setAuditExpiration(long auditExpiration) {
|
||||
this.auditExpiration = auditExpiration;
|
||||
}
|
||||
|
||||
public Set<String> getAuditListeners() {
|
||||
return auditListeners;
|
||||
}
|
||||
|
|
|
@ -1141,6 +1141,28 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
|||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuditEnabled() {
|
||||
return realm.isAuditEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuditEnabled(boolean enabled) {
|
||||
realm.setAuditEnabled(enabled);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAuditExpiration() {
|
||||
return realm.getAuditExpiration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuditExpiration(long expiration) {
|
||||
realm.setAuditExpiration(expiration);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAuditListeners() {
|
||||
return new HashSet<String>(realm.getAuditListeners());
|
||||
|
|
|
@ -57,6 +57,8 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
|||
private Map<String, String> socialConfig = new HashMap<String, String>();
|
||||
private Map<String, String> ldapServerConfig;
|
||||
|
||||
private boolean auditEnabled;
|
||||
private long auditExpiration;
|
||||
private List<String> auditListeners = new ArrayList<String>();
|
||||
|
||||
@MongoField
|
||||
|
@ -302,6 +304,24 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
|||
this.ldapServerConfig = ldapServerConfig;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public boolean isAuditEnabled() {
|
||||
return auditEnabled;
|
||||
}
|
||||
|
||||
public void setAuditEnabled(boolean auditEnabled) {
|
||||
this.auditEnabled = auditEnabled;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public long getAuditExpiration() {
|
||||
return auditExpiration;
|
||||
}
|
||||
|
||||
public void setAuditExpiration(long auditExpiration) {
|
||||
this.auditExpiration = auditExpiration;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getAuditListeners() {
|
||||
return auditListeners;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package org.keycloak.services.managers;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.audit.Audit;
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.ClientConnection;
|
||||
import org.keycloak.services.ProviderSession;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AuditManager {
|
||||
|
||||
private Logger log = Logger.getLogger(AuditManager.class);
|
||||
|
||||
private RealmModel realm;
|
||||
private ProviderSession providers;
|
||||
private ClientConnection clientConnection;
|
||||
|
||||
public AuditManager(RealmModel realm, ProviderSession providers, ClientConnection clientConnection) {
|
||||
this.realm = realm;
|
||||
this.providers = providers;
|
||||
this.clientConnection = clientConnection;
|
||||
}
|
||||
|
||||
public Audit createAudit() {
|
||||
List<AuditListener> listeners = new LinkedList<AuditListener>();
|
||||
|
||||
if (realm.isAuditEnabled()) {
|
||||
AuditProvider auditProvider = providers.getProvider(AuditProvider.class);
|
||||
if (auditProvider != null) {
|
||||
listeners.add(auditProvider);
|
||||
} else {
|
||||
log.error("Audit enabled, but no audit provider configured");
|
||||
}
|
||||
}
|
||||
|
||||
if (realm.getAuditListeners() != null) {
|
||||
for (String id : realm.getAuditListeners()) {
|
||||
AuditListener listener = providers.getProvider(AuditListener.class, id);
|
||||
if (listener != null) {
|
||||
listeners.add(listener);
|
||||
} else {
|
||||
log.error("Audit listener '" + id + "' registered, but not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Audit(listeners, realm, clientConnection.getRemoteAddr());
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
||||
import org.keycloak.representations.idm.ClaimRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.RealmAuditRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -20,6 +21,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -124,6 +126,20 @@ public class ModelToRepresentation {
|
|||
return rep;
|
||||
}
|
||||
|
||||
public static RealmAuditRepresentation toAuditReprensetation(RealmModel realm) {
|
||||
RealmAuditRepresentation rep = new RealmAuditRepresentation();
|
||||
rep.setAuditEnabled(realm.isAuditEnabled());
|
||||
|
||||
if (realm.getAuditExpiration() != 0) {
|
||||
rep.setAuditExpiration(realm.getAuditExpiration());
|
||||
}
|
||||
|
||||
if (realm.getAuditListeners() != null) {
|
||||
rep.setAuditListeners(new LinkedList<String>(realm.getAuditListeners()));
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static CredentialRepresentation toRepresentation(UserCredentialModel cred) {
|
||||
CredentialRepresentation rep = new CredentialRepresentation();
|
||||
rep.setType(CredentialRepresentation.SECRET);
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
|
|||
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmAuditRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.ScopeMappingRepresentation;
|
||||
|
@ -39,6 +40,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -166,6 +168,14 @@ public class RealmManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void updateRealmAudit(RealmAuditRepresentation rep, RealmModel realm) {
|
||||
realm.setAuditEnabled(rep.isAuditEnabled());
|
||||
realm.setAuditExpiration(rep.getAuditExpiration());
|
||||
if (rep.getAuditListeners() != null) {
|
||||
realm.setAuditListeners(new HashSet<String>(rep.getAuditListeners()));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupAdminManagement(RealmModel realm) {
|
||||
RealmModel adminRealm;
|
||||
RoleModel adminRole;
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.ClientConnection;
|
||||
import org.keycloak.services.ProviderSession;
|
||||
import org.keycloak.services.managers.AuditManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.SocialRequestManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
|
@ -68,7 +69,7 @@ public class RealmsResource {
|
|||
public TokenService getTokenService(final @PathParam("realm") String name) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = locateRealm(name, realmManager);
|
||||
Audit audit = createAudit(realm);
|
||||
Audit audit = new AuditManager(realm, providers, clientConnection).createAudit();
|
||||
TokenService tokenService = new TokenService(realm, tokenManager, audit);
|
||||
resourceContext.initResource(tokenService);
|
||||
return tokenService;
|
||||
|
@ -93,7 +94,7 @@ public class RealmsResource {
|
|||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
Audit audit = createAudit(realm);
|
||||
Audit audit = new AuditManager(realm, providers, clientConnection).createAudit();
|
||||
AccountService accountService = new AccountService(realm, application, tokenManager, socialRequestManager, audit);
|
||||
resourceContext.initResource(accountService);
|
||||
accountService.init();
|
||||
|
@ -109,24 +110,4 @@ public class RealmsResource {
|
|||
return realmResource;
|
||||
}
|
||||
|
||||
private Audit createAudit(RealmModel realm) {
|
||||
List<AuditListener> listeners = new LinkedList<AuditListener>();
|
||||
|
||||
AuditProvider auditProvider = providers.getProvider(AuditProvider.class);
|
||||
if (auditProvider != null) {
|
||||
listeners.add(auditProvider);
|
||||
}
|
||||
|
||||
if (realm.getAuditListeners() != null) {
|
||||
for (String id : realm.getAuditListeners()) {
|
||||
AuditListener listener = providers.getProvider(AuditListener.class, id);
|
||||
if (listener != null) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Audit(listeners, realm, clientConnection.getRemoteAddr());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.services.ClientConnection;
|
||||
import org.keycloak.services.ProviderSession;
|
||||
import org.keycloak.services.managers.AuditManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.SocialRequestManager;
|
||||
|
@ -129,7 +130,7 @@ public class SocialResource {
|
|||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
|
||||
Audit audit = createAudit(realm)
|
||||
Audit audit = new AuditManager(realm, providers, clientConnection).createAudit()
|
||||
.event(Events.LOGIN)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "social@" + provider.getId());
|
||||
|
@ -268,7 +269,7 @@ public class SocialResource {
|
|||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
|
||||
Audit audit = createAudit(realm)
|
||||
Audit audit = new AuditManager(realm, providers, clientConnection).createAudit()
|
||||
.event(Events.LOGIN).client(clientId)
|
||||
.detail(Details.REDIRECT_URI, redirectUri)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
|
@ -335,24 +336,4 @@ public class SocialResource {
|
|||
return queryParams;
|
||||
}
|
||||
|
||||
private Audit createAudit(RealmModel realm) {
|
||||
List<AuditListener> listeners = new LinkedList<AuditListener>();
|
||||
|
||||
AuditProvider auditProvider = providers.getProvider(AuditProvider.class);
|
||||
if (auditProvider != null) {
|
||||
listeners.add(auditProvider);
|
||||
}
|
||||
|
||||
if (realm.getAuditListeners() != null) {
|
||||
for (String id : realm.getAuditListeners()) {
|
||||
AuditListener listener = providers.getProvider(AuditListener.class, id);
|
||||
if (listener != null) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Audit(listeners, realm, clientConnection.getRemoteAddr());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ 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.RealmAuditRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.ProviderSession;
|
||||
import org.keycloak.services.managers.ModelToRepresentation;
|
||||
|
@ -148,7 +149,26 @@ public class RealmAdminResource {
|
|||
return stats;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("audit")
|
||||
@Produces("application/json")
|
||||
public RealmAuditRepresentation getRealmAudit() {
|
||||
auth.init(RealmAuth.Resource.AUDIT).requireView();
|
||||
|
||||
return ModelToRepresentation.toAuditReprensetation(realm);
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("audit")
|
||||
@Consumes("application/json")
|
||||
public void updateRealmAudit(final RealmAuditRepresentation rep) {
|
||||
auth.init(RealmAuth.Resource.AUDIT).requireManage();
|
||||
|
||||
logger.debug("updating realm audit: " + realm.getName());
|
||||
new RealmManager(session).updateRealmAudit(rep, realm);
|
||||
}
|
||||
|
||||
@Path("audit/events")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
|
@ -181,4 +201,12 @@ public class RealmAdminResource {
|
|||
return query.getResultList();
|
||||
}
|
||||
|
||||
@Path("audit/events")
|
||||
@DELETE
|
||||
public void clearAudit() {
|
||||
auth.init(RealmAuth.Resource.AUDIT).requireManage();
|
||||
|
||||
AuditProvider audit = providers.getProvider(AuditProvider.class);
|
||||
audit.clear(realm.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,38 @@
|
|||
package org.keycloak.services.resources.admin;
|
||||
|
||||
import org.keycloak.audit.AuditListener;
|
||||
import org.keycloak.freemarker.Theme;
|
||||
import org.keycloak.freemarker.ThemeProvider;
|
||||
import org.keycloak.services.ProviderSession;
|
||||
import org.keycloak.social.SocialProvider;
|
||||
import org.keycloak.spi.authentication.AuthenticationProvider;
|
||||
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||
import org.keycloak.util.ProviderLoader;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.core.Context;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ServerInfoAdminResource {
|
||||
|
||||
@Context
|
||||
private ProviderSession providers;
|
||||
|
||||
@GET
|
||||
public ServerInfoRepresentation getInfo() {
|
||||
ServerInfoRepresentation info = new ServerInfoRepresentation();
|
||||
setSocialProviders(info);
|
||||
setThemes(info);
|
||||
setAuthProviders(info);
|
||||
setAuditListeners(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
|
@ -57,6 +65,15 @@ public class ServerInfoAdminResource {
|
|||
}
|
||||
}
|
||||
|
||||
private void setAuditListeners(ServerInfoRepresentation info) {
|
||||
info.auditListeners = new LinkedList<String>();
|
||||
|
||||
Set<String> providers = this.providers.listProviderIds(AuditListener.class);
|
||||
if (providers != null) {
|
||||
info.auditListeners.addAll(providers);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerInfoRepresentation {
|
||||
|
||||
private Map<String, List<String>> themes;
|
||||
|
@ -65,6 +82,8 @@ public class ServerInfoAdminResource {
|
|||
|
||||
private Map<String, List<String>> authProviders;
|
||||
|
||||
private List<String> auditListeners;
|
||||
|
||||
public ServerInfoRepresentation() {
|
||||
}
|
||||
|
||||
|
@ -79,6 +98,10 @@ public class ServerInfoAdminResource {
|
|||
public Map<String, List<String>> getAuthProviders() {
|
||||
return authProviders;
|
||||
}
|
||||
|
||||
public List<String> getAuditListeners() {
|
||||
return auditListeners;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -334,41 +334,56 @@ public class AccountTest {
|
|||
|
||||
@Test
|
||||
public void viewLog() {
|
||||
List<Event> e = new LinkedList<Event>();
|
||||
keycloakRule.configure(new KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
appRealm.setAuditEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
loginPage.open();
|
||||
loginPage.clickRegister();
|
||||
try {
|
||||
List<Event> e = new LinkedList<Event>();
|
||||
|
||||
registerPage.register("view", "log", "view-log@localhost", "view-log", "password", "password");
|
||||
loginPage.open();
|
||||
loginPage.clickRegister();
|
||||
|
||||
e.add(events.poll());
|
||||
e.add(events.poll());
|
||||
registerPage.register("view", "log", "view-log@localhost", "view-log", "password", "password");
|
||||
|
||||
profilePage.open();
|
||||
profilePage.updateProfile("view", "log2", "view-log@localhost");
|
||||
e.add(events.poll());
|
||||
e.add(events.poll());
|
||||
|
||||
e.add(events.poll());
|
||||
profilePage.open();
|
||||
profilePage.updateProfile("view", "log2", "view-log@localhost");
|
||||
|
||||
logPage.open();
|
||||
e.add(events.poll());
|
||||
|
||||
e.add(events.poll());
|
||||
logPage.open();
|
||||
|
||||
Collections.reverse(e);
|
||||
e.add(events.poll());
|
||||
|
||||
Assert.assertTrue(logPage.isCurrent());
|
||||
Collections.reverse(e);
|
||||
|
||||
List<List<String>> actual = logPage.getEvents();
|
||||
Assert.assertTrue(logPage.isCurrent());
|
||||
|
||||
Assert.assertEquals(e.size(), actual.size());
|
||||
List<List<String>> actual = logPage.getEvents();
|
||||
|
||||
Iterator<List<String>> itr = actual.iterator();
|
||||
for (Event event : e) {
|
||||
List<String> a = itr.next();
|
||||
Assert.assertEquals(event.getEvent().replace('_', ' '), a.get(1));
|
||||
Assert.assertEquals(event.getIpAddress(), a.get(2));
|
||||
Assert.assertEquals(event.getClientId(), a.get(3));
|
||||
Assert.assertEquals(e.size(), actual.size());
|
||||
|
||||
Iterator<List<String>> itr = actual.iterator();
|
||||
for (Event event : e) {
|
||||
List<String> a = itr.next();
|
||||
Assert.assertEquals(event.getEvent().replace('_', ' '), a.get(1));
|
||||
Assert.assertEquals(event.getIpAddress(), a.get(2));
|
||||
Assert.assertEquals(event.getClientId(), a.get(3));
|
||||
}
|
||||
} finally {
|
||||
keycloakRule.configure(new KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
appRealm.setAuditEnabled(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue