KEYCLOAK-1976: Add support for events into admin-rest-client. Also,
arquillian tests for events.
This commit is contained in:
parent
23e5357870
commit
e89f511465
10 changed files with 798 additions and 14 deletions
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class AdminEventRepresentation {
|
||||||
|
|
||||||
|
private long time;
|
||||||
|
private String realmId;
|
||||||
|
private AuthDetailsRepresentation authDetails;
|
||||||
|
private String operationType;
|
||||||
|
private String resourcePath;
|
||||||
|
private String representation;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(long time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthDetailsRepresentation getAuthDetails() {
|
||||||
|
return authDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthDetails(AuthDetailsRepresentation authDetails) {
|
||||||
|
this.authDetails = authDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOperationType() {
|
||||||
|
return operationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperationType(String operationType) {
|
||||||
|
this.operationType = operationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourcePath() {
|
||||||
|
return resourcePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourcePath(String resourcePath) {
|
||||||
|
this.resourcePath = resourcePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRepresentation() {
|
||||||
|
return representation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRepresentation(String representation) {
|
||||||
|
this.representation = representation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class AuthDetailsRepresentation {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
private String clientId;
|
||||||
|
private String userId;
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(String ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class EventRepresentation {
|
||||||
|
|
||||||
|
private long time;
|
||||||
|
private String type;
|
||||||
|
private String realmId;
|
||||||
|
private String clientId;
|
||||||
|
private String userId;
|
||||||
|
private String sessionId;
|
||||||
|
private String ipAddress;
|
||||||
|
private String error;
|
||||||
|
private Map<String, String> details;
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(long time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSessionId() {
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionId(String sessionId) {
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(String ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getDetails() {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDetails(Map<String, String> details) {
|
||||||
|
this.details = details;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
|
||||||
|
@ -26,6 +27,8 @@ import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author rodrigo.sasaki@icarros.com.br
|
* @author rodrigo.sasaki@icarros.com.br
|
||||||
|
@ -61,6 +64,52 @@ public interface RealmResource {
|
||||||
@Path("groups")
|
@Path("groups")
|
||||||
GroupsResource groups();
|
GroupsResource groups();
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("events")
|
||||||
|
void clearEvents();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("events")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
List<EventRepresentation> getEvents();
|
||||||
|
|
||||||
|
@Path("events")
|
||||||
|
@GET
|
||||||
|
@NoCache
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public List<EventRepresentation> getEvents(@QueryParam("type") List<String> types, @QueryParam("client") String client,
|
||||||
|
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
|
||||||
|
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults);
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("admin-events")
|
||||||
|
void clearAdminEvents();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("admin-events")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
List<AdminEventRepresentation> getAdminEvents();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("admin-events")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
List<AdminEventRepresentation> getAdminEvents(@QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
|
||||||
|
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
|
||||||
|
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
|
||||||
|
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("events/config")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public RealmEventsConfigRepresentation getRealmEventsConfig();
|
||||||
|
|
||||||
|
@PUT
|
||||||
|
@Path("events/config")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public void updateRealmEventsConfig(RealmEventsConfigRepresentation rep);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("group-by-path/{path: .*}")
|
@Path("group-by-path/{path: .*}")
|
||||||
@NoCache
|
@NoCache
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import org.keycloak.events.Event;
|
||||||
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.events.admin.AuthDetails;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
import org.keycloak.models.AuthenticatorConfigModel;
|
||||||
|
@ -41,12 +44,15 @@ import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
|
import org.keycloak.representations.idm.AuthDetailsRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientTemplateRepresentation;
|
import org.keycloak.representations.idm.ClientTemplateRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||||
|
@ -167,6 +173,43 @@ public class ModelToRepresentation {
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static EventRepresentation toRepresentation(Event event) {
|
||||||
|
EventRepresentation rep = new EventRepresentation();
|
||||||
|
rep.setTime(event.getTime());
|
||||||
|
rep.setType(event.getType().toString());
|
||||||
|
rep.setRealmId(event.getRealmId());
|
||||||
|
rep.setClientId(event.getClientId());
|
||||||
|
rep.setUserId(event.getUserId());
|
||||||
|
rep.setSessionId(event.getSessionId());
|
||||||
|
rep.setError(event.getError());
|
||||||
|
rep.setDetails(event.getDetails());
|
||||||
|
return rep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdminEventRepresentation toRepresentation(AdminEvent adminEvent) {
|
||||||
|
AdminEventRepresentation rep = new AdminEventRepresentation();
|
||||||
|
rep.setTime(adminEvent.getTime());
|
||||||
|
rep.setRealmId(adminEvent.getRealmId());
|
||||||
|
if (adminEvent.getAuthDetails() != null) {
|
||||||
|
rep.setAuthDetails(toRepresentation(adminEvent.getAuthDetails()));
|
||||||
|
}
|
||||||
|
rep.setOperationType(adminEvent.getOperationType().toString());
|
||||||
|
rep.setResourcePath(adminEvent.getResourcePath());
|
||||||
|
rep.setRepresentation(adminEvent.getRepresentation());
|
||||||
|
rep.setError(adminEvent.getError());
|
||||||
|
|
||||||
|
return rep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AuthDetailsRepresentation toRepresentation(AuthDetails authDetails) {
|
||||||
|
AuthDetailsRepresentation rep = new AuthDetailsRepresentation();
|
||||||
|
rep.setRealmId(authDetails.getRealmId());
|
||||||
|
rep.setClientId(authDetails.getClientId());
|
||||||
|
rep.setUserId(authDetails.getUserId());
|
||||||
|
rep.setIpAddress(authDetails.getIpAddress());
|
||||||
|
return rep;
|
||||||
|
}
|
||||||
|
|
||||||
public static RoleRepresentation toRepresentation(RoleModel role) {
|
public static RoleRepresentation toRepresentation(RoleModel role) {
|
||||||
RoleRepresentation rep = new RoleRepresentation();
|
RoleRepresentation rep = new RoleRepresentation();
|
||||||
rep.setId(role.getId());
|
rep.setId(role.getId());
|
||||||
|
|
|
@ -45,8 +45,11 @@ import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.GroupRepresentation;
|
import org.keycloak.representations.idm.GroupRepresentation;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -75,6 +78,7 @@ import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -82,7 +86,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.PatternSyntaxException;
|
import java.util.regex.PatternSyntaxException;
|
||||||
import org.keycloak.partialimport.PartialImportManager;
|
import org.keycloak.partialimport.PartialImportManager;
|
||||||
import org.keycloak.representations.idm.PartialImportRepresentation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource class for the admin REST api of one realm
|
* Base resource class for the admin REST api of one realm
|
||||||
|
@ -428,6 +431,7 @@ public class RealmAdminResource {
|
||||||
*
|
*
|
||||||
* Returns all events, or filters them based on URL query parameters listed here
|
* Returns all events, or filters them based on URL query parameters listed here
|
||||||
*
|
*
|
||||||
|
* @param types The types of events to return
|
||||||
* @param client App or oauth client name
|
* @param client App or oauth client name
|
||||||
* @param user User id
|
* @param user User id
|
||||||
* @param ipAddress IP address
|
* @param ipAddress IP address
|
||||||
|
@ -441,7 +445,7 @@ public class RealmAdminResource {
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public List<Event> getEvents(@QueryParam("client") String client,
|
public List<EventRepresentation> getEvents(@QueryParam("type") List<String> types, @QueryParam("client") String client,
|
||||||
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
|
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
|
||||||
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
|
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResults) {
|
@QueryParam("max") Integer maxResults) {
|
||||||
|
@ -454,8 +458,7 @@ public class RealmAdminResource {
|
||||||
query.client(client);
|
query.client(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> types = uriInfo.getQueryParameters().get("type");
|
if (types != null & !types.isEmpty()) {
|
||||||
if (types != null) {
|
|
||||||
EventType[] t = new EventType[types.size()];
|
EventType[] t = new EventType[types.size()];
|
||||||
for (int i = 0; i < t.length; i++) {
|
for (int i = 0; i < t.length; i++) {
|
||||||
t[i] = EventType.valueOf(types.get(i));
|
t[i] = EventType.valueOf(types.get(i));
|
||||||
|
@ -499,7 +502,15 @@ public class RealmAdminResource {
|
||||||
query.maxResults(maxResults);
|
query.maxResults(maxResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.getResultList();
|
return toEventListRep(query.getResultList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventRepresentation> toEventListRep(List<Event> events) {
|
||||||
|
List<EventRepresentation> reps = new ArrayList<>();
|
||||||
|
for (Event event : events) {
|
||||||
|
reps.add(ModelToRepresentation.toRepresentation(event));
|
||||||
|
}
|
||||||
|
return reps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -507,6 +518,7 @@ public class RealmAdminResource {
|
||||||
*
|
*
|
||||||
* Returns all admin events, or filters events based on URL query parameters listed here
|
* Returns all admin events, or filters events based on URL query parameters listed here
|
||||||
*
|
*
|
||||||
|
* @param operationTypes
|
||||||
* @param authRealm
|
* @param authRealm
|
||||||
* @param authClient
|
* @param authClient
|
||||||
* @param authUser user id
|
* @param authUser user id
|
||||||
|
@ -514,7 +526,6 @@ public class RealmAdminResource {
|
||||||
* @param resourcePath
|
* @param resourcePath
|
||||||
* @param dateTo
|
* @param dateTo
|
||||||
* @param dateFrom
|
* @param dateFrom
|
||||||
* @param resourcePath
|
|
||||||
* @param firstResult
|
* @param firstResult
|
||||||
* @param maxResults
|
* @param maxResults
|
||||||
* @return
|
* @return
|
||||||
|
@ -523,7 +534,7 @@ public class RealmAdminResource {
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public List<AdminEvent> getEvents(@QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
|
public List<AdminEventRepresentation> getEvents(@QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
|
||||||
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
|
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
|
||||||
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
|
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
|
||||||
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
|
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
|
||||||
|
@ -553,8 +564,7 @@ public class RealmAdminResource {
|
||||||
query.resourcePath(resourcePath);
|
query.resourcePath(resourcePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> operationTypes = uriInfo.getQueryParameters().get("operationTypes");
|
if (operationTypes != null && !operationTypes.isEmpty()) {
|
||||||
if (operationTypes != null) {
|
|
||||||
OperationType[] t = new OperationType[operationTypes.size()];
|
OperationType[] t = new OperationType[operationTypes.size()];
|
||||||
for (int i = 0; i < t.length; i++) {
|
for (int i = 0; i < t.length; i++) {
|
||||||
t[i] = OperationType.valueOf(operationTypes.get(i));
|
t[i] = OperationType.valueOf(operationTypes.get(i));
|
||||||
|
@ -591,7 +601,16 @@ public class RealmAdminResource {
|
||||||
query.maxResults(maxResults);
|
query.maxResults(maxResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query.getResultList();
|
return toAdminEventRep(query.getResultList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AdminEventRepresentation> toAdminEventRep(List<AdminEvent> events) {
|
||||||
|
List<AdminEventRepresentation> reps = new ArrayList<>();
|
||||||
|
for (AdminEvent event : events) {
|
||||||
|
reps.add(ModelToRepresentation.toRepresentation(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
return reps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.event;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractAuthTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractEventTest extends AbstractAuthTest {
|
||||||
|
|
||||||
|
protected RealmEventsConfigRepresentation configRep;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setConfigRep() {
|
||||||
|
RealmResource testRsc = testRealmResource();
|
||||||
|
configRep = testRsc.getRealmEventsConfig();
|
||||||
|
configRep.setAdminEventsDetailsEnabled(false);
|
||||||
|
configRep.setAdminEventsEnabled(false);
|
||||||
|
configRep.setEventsEnabled(false);
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void saveConfig() {
|
||||||
|
RealmResource testRsc = testRealmResource();
|
||||||
|
testRsc.updateRealmEventsConfig(configRep);
|
||||||
|
configRep = testRsc.getRealmEventsConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void enableEvents() {
|
||||||
|
configRep.setEventsEnabled(true);
|
||||||
|
configRep.setAdminEventsEnabled(Boolean.TRUE);
|
||||||
|
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.event;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test getting and filtering admin events.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class AdminEventTest extends AbstractEventTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void initConfig() {
|
||||||
|
enableEvents();
|
||||||
|
testRealmResource().clearAdminEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AdminEventRepresentation> events() {
|
||||||
|
return testRealmResource().getAdminEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUser(String username) {
|
||||||
|
UserRepresentation user = createUserRepresentation(username, username + "@foo.com", "foo", "bar", true);
|
||||||
|
ApiUtil.createUserWithAdminClient(testRealmResource(), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRealm() {
|
||||||
|
RealmRepresentation realm = testRealmResource().toRepresentation();
|
||||||
|
realm.setDisplayName("Fury Road");
|
||||||
|
testRealmResource().update(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String realmName() {
|
||||||
|
return testRealmResource().toRepresentation().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearAdminEventsTest() {
|
||||||
|
createUser("user0");
|
||||||
|
assertEquals(1, events().size());
|
||||||
|
testRealmResource().clearAdminEvents();
|
||||||
|
assertEquals(Collections.EMPTY_LIST, events());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void retrieveAdminEventTest() {
|
||||||
|
createUser("user1");
|
||||||
|
List<AdminEventRepresentation> events = events();
|
||||||
|
|
||||||
|
assertEquals(1, events.size());
|
||||||
|
AdminEventRepresentation event = events().get(0);
|
||||||
|
assertEquals("CREATE", event.getOperationType());
|
||||||
|
|
||||||
|
assertEquals(realmName(), event.getRealmId());
|
||||||
|
assertEquals(realmName(), event.getAuthDetails().getRealmId());
|
||||||
|
assertNull(event.getRepresentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRepresentation() {
|
||||||
|
configRep.setAdminEventsDetailsEnabled(Boolean.TRUE);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
createUser("user2");
|
||||||
|
AdminEventRepresentation event = events().get(0);
|
||||||
|
assertNotNull(event.getRepresentation());
|
||||||
|
assertTrue(event.getRepresentation().contains("foo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterAdminEvents() {
|
||||||
|
// two CREATE and one UPDATE
|
||||||
|
createUser("user3");
|
||||||
|
createUser("user4");
|
||||||
|
updateRealm();
|
||||||
|
assertEquals(3, events().size());
|
||||||
|
|
||||||
|
List<AdminEventRepresentation> events = testRealmResource().getAdminEvents(Arrays.asList("CREATE"), realmName(), null, null, null, null, null, null, null, null);
|
||||||
|
assertEquals(2, events.size());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.event;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test updates to the events configuration.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class EventConfigTest extends AbstractEventTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultEventConfigTest() {
|
||||||
|
assertFalse(configRep.isAdminEventsDetailsEnabled());
|
||||||
|
assertFalse(configRep.isAdminEventsEnabled());
|
||||||
|
assertFalse(configRep.isEventsEnabled());
|
||||||
|
|
||||||
|
List<String> eventListeners = configRep.getEventsListeners();
|
||||||
|
assertEquals(1, eventListeners.size());
|
||||||
|
assertEquals("jboss-logging", eventListeners.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enableEventsTest() {
|
||||||
|
enableEvents();
|
||||||
|
|
||||||
|
assertTrue(configRep.isEventsEnabled());
|
||||||
|
assertTrue(configRep.isAdminEventsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addRemoveListenerTest() {
|
||||||
|
configRep.setEventsListeners(Collections.EMPTY_LIST);
|
||||||
|
saveConfig();
|
||||||
|
assertEquals(0, configRep.getEventsListeners().size());
|
||||||
|
|
||||||
|
configRep.setEventsListeners(Arrays.asList("email"));
|
||||||
|
saveConfig();
|
||||||
|
List<String> eventListeners = configRep.getEventsListeners();
|
||||||
|
assertEquals(1, eventListeners.size());
|
||||||
|
assertEquals("email", eventListeners.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginEventSettingsTest() {
|
||||||
|
enableEvents();
|
||||||
|
|
||||||
|
assertTrue(hasEventType("LOGIN"));
|
||||||
|
assertTrue(hasEventType("LOGOUT"));
|
||||||
|
assertTrue(hasEventType("CLIENT_DELETE_ERROR"));
|
||||||
|
|
||||||
|
int defaultEventCount = configRep.getEnabledEventTypes().size();
|
||||||
|
|
||||||
|
configRep.setEnabledEventTypes(Arrays.asList("CLIENT_DELETE", "CLEINT_DELETE_ERROR"));
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
List<String> enabledEventTypes = configRep.getEnabledEventTypes();
|
||||||
|
assertEquals(2, enabledEventTypes.size());
|
||||||
|
|
||||||
|
// remove all event types
|
||||||
|
configRep.setEnabledEventTypes(Collections.EMPTY_LIST);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
// removing all event types restores default events
|
||||||
|
assertEquals(defaultEventCount, configRep.getEnabledEventTypes().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasEventType(String eventType) {
|
||||||
|
for (String event : configRep.getEnabledEventTypes()) {
|
||||||
|
if (eventType.equals(event)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void includeRepresentationTest() {
|
||||||
|
enableEvents();
|
||||||
|
|
||||||
|
assertTrue(configRep.isAdminEventsEnabled());
|
||||||
|
assertFalse(configRep.isAdminEventsDetailsEnabled());
|
||||||
|
|
||||||
|
configRep.setAdminEventsDetailsEnabled(Boolean.TRUE);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
assertTrue(configRep.isAdminEventsDetailsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setLoginEventExpirationTest() {
|
||||||
|
enableEvents();
|
||||||
|
|
||||||
|
assertNull(configRep.getEventsExpiration());
|
||||||
|
|
||||||
|
Long oneHour = 3600L;
|
||||||
|
configRep.setEventsExpiration(oneHour);
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
assertEquals(oneHour, configRep.getEventsExpiration());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.testsuite.event;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
|
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test getting and filtering login-related events.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class LoginEventsTest extends AbstractEventTest {
|
||||||
|
|
||||||
|
@Page
|
||||||
|
private LoginEvents loginEventsPage;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
configRep.setEventsEnabled(true);
|
||||||
|
configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types
|
||||||
|
saveConfig();
|
||||||
|
testRealmResource().clearEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventRepresentation> events() {
|
||||||
|
return testRealmResource().getEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void badLogin() {
|
||||||
|
loginEventsPage.navigateTo();
|
||||||
|
loginPage.form().login("bad", "user");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pause(int seconds) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(seconds * 1000L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearEventsTest() {
|
||||||
|
assertEquals(0, events().size());
|
||||||
|
badLogin();
|
||||||
|
badLogin();
|
||||||
|
assertEquals(2, events().size());
|
||||||
|
testRealmResource().clearEvents();
|
||||||
|
assertEquals(0, events().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loggingOfCertainTypeTest() {
|
||||||
|
assertEquals(0, events().size());
|
||||||
|
configRep.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT"));
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
badLogin();
|
||||||
|
assertEquals(0, events().size());
|
||||||
|
|
||||||
|
configRep.setEnabledEventTypes(Arrays.asList("LOGIN_ERROR"));
|
||||||
|
saveConfig();
|
||||||
|
|
||||||
|
badLogin();
|
||||||
|
assertEquals(1, events().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterTest() {
|
||||||
|
badLogin();
|
||||||
|
badLogin();
|
||||||
|
assertEquals(2, events().size());
|
||||||
|
|
||||||
|
List<EventRepresentation> filteredEvents = testRealmResource().getEvents(Arrays.asList("REVOKE_GRANT"), null, null, null, null, null, null, null);
|
||||||
|
assertEquals(0, filteredEvents.size());
|
||||||
|
|
||||||
|
filteredEvents = testRealmResource().getEvents(Arrays.asList("LOGIN_ERROR"), null, null, null, null, null, null, null);
|
||||||
|
assertEquals(2, filteredEvents.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removed this test because it takes too long. The default interval for
|
||||||
|
event cleanup is 15 minutes (900 seconds). I don't have time to figure out
|
||||||
|
a way to set the cleanup thread to a lower interval for testing.
|
||||||
|
@Test
|
||||||
|
public void eventExpirationTest() {
|
||||||
|
configRep.setEventsExpiration(1L); // second
|
||||||
|
saveConfig();
|
||||||
|
badLogin();
|
||||||
|
assertEquals(1, events().size());
|
||||||
|
pause(900); // pause 900 seconds
|
||||||
|
assertEquals(0, events().size());
|
||||||
|
}**/
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue