/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.rest;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.AdminEventQuery;
import org.keycloak.events.admin.AuthDetails;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthDetailsRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.components.TestProvider;
import org.keycloak.testsuite.components.TestProviderFactory;
import org.keycloak.testsuite.events.EventsListenerProvider;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.forms.PassThroughAuthenticator;
import org.keycloak.testsuite.forms.PassThroughClientAuthenticator;
import org.keycloak.testsuite.rest.representation.AuthenticatorState;
import org.keycloak.testsuite.rest.resource.TestCacheResource;
import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
import org.keycloak.testsuite.runonserver.ModuleUtil;
import org.keycloak.testsuite.runonserver.FetchOnServer;
import org.keycloak.testsuite.runonserver.RunOnServer;
import org.keycloak.testsuite.runonserver.SerializationUtil;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class TestingResourceProvider implements RealmResourceProvider {
private KeycloakSession session;
@Override
public Object getResource() {
return this;
}
public TestingResourceProvider(KeycloakSession session) {
this.session = session;
}
@POST
@Path("/remove-user-session")
@Produces(MediaType.APPLICATION_JSON)
public Response removeUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
if (sessionModel == null) {
throw new NotFoundException("Session not found");
}
session.sessions().removeUserSession(realm, sessionModel);
return Response.ok().build();
}
@POST
@Path("/remove-user-sessions")
@Produces(MediaType.APPLICATION_JSON)
public Response removeUserSessions(@QueryParam("realm") final String realmName) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(realmName);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
session.sessions().removeUserSessions(realm);
return Response.ok().build();
}
@GET
@Path("/get-user-session")
@Produces(MediaType.APPLICATION_JSON)
public Integer getLastSessionRefresh(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
if (sessionModel == null) {
throw new NotFoundException("Session not found");
}
return sessionModel.getLastSessionRefresh();
}
@POST
@Path("/remove-expired")
@Produces(MediaType.APPLICATION_JSON)
public Response removeExpired(@QueryParam("realm") final String name) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
session.sessions().removeExpired(realm);
session.authenticationSessions().removeExpired(realm);
return Response.ok().build();
}
@GET
@Path("/time-offset")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, String> getTimeOffset() {
Map<String, String> response = new HashMap<>();
response.put("currentTime", String.valueOf(Time.currentTime()));
response.put("offset", String.valueOf(Time.getOffset()));
return response;
}
@PUT
@Path("/time-offset")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Map<String, String> setTimeOffset(Map<String, String> time) {
int offset = Integer.parseInt(time.get("offset"));
Time.setOffset(offset);
return getTimeOffset();
}
@POST
@Path("/poll-event-queue")
@Produces(MediaType.APPLICATION_JSON)
public EventRepresentation getEvent() {
Event event = EventsListenerProvider.poll();
if (event != null) {
return ModelToRepresentation.toRepresentation(event);
} else {
return null;
}
}
@POST
@Path("/poll-admin-event-queue")
@Produces(MediaType.APPLICATION_JSON)
public AdminEventRepresentation getAdminEvent() {
AdminEvent adminEvent = EventsListenerProvider.pollAdminEvent();
if (adminEvent != null) {
return ModelToRepresentation.toRepresentation(adminEvent);
} else {
return null;
}
}
@POST
@Path("/clear-event-queue")
@Produces(MediaType.APPLICATION_JSON)
public Response clearEventQueue() {
EventsListenerProvider.clear();
return Response.ok().build();
}
@POST
@Path("/clear-admin-event-queue")
@Produces(MediaType.APPLICATION_JSON)
public Response clearAdminEventQueue() {
EventsListenerProvider.clearAdminEvents();
return Response.ok().build();
}
@GET
@Path("/clear-event-store")
@Produces(MediaType.APPLICATION_JSON)
public Response clearEventStore() {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clear();
return Response.ok().build();
}
@GET
@Path("/clear-event-store-for-realm")
@Produces(MediaType.APPLICATION_JSON)
public Response clearEventStore(@QueryParam("realmId") String realmId) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clear(realmId);
return Response.ok().build();
}
@GET
@Path("/clear-event-store-older-than")
@Produces(MediaType.APPLICATION_JSON)
public Response clearEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clear(realmId, olderThan);
return Response.ok().build();
}
/**
* Query events
*
* Returns all events, or filters them based on URL query parameters listed here
*
* @param realmId The realm
* @param types The types of events to return
* @param client App or oauth client name
* @param user User id
* @param dateFrom From date
* @param dateTo To date
* @param ipAddress IP address
* @param firstResult Paging offset
* @param maxResults Paging size
* @return
*/
@Path("query-events")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<EventRepresentation> queryEvents(@QueryParam("realmId") String realmId, @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) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
EventQuery query = eventStore.createQuery();
if (realmId != null) {
query.realm(realmId);
}
if (client != null) {
query.client(client);
}
if (types != null & !types.isEmpty()) {
EventType[] t = new EventType[types.size()];
for (int i = 0; i < t.length; i++) {
t[i] = EventType.valueOf(types.get(i));
}
query.type(t);
}
if (user != null) {
query.user(user);
}
if(dateFrom != null) {
Date from = formatDate(dateFrom, "Date(From)");
query.fromDate(from);
}
if(dateTo != null) {
Date to = formatDate(dateTo, "Date(To)");
query.toDate(to);
}
if (ipAddress != null) {
query.ipAddress(ipAddress);
}
if (firstResult != null) {
query.firstResult(firstResult);
}
if (maxResults != null) {
query.maxResults(maxResults);
}
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;
}
@PUT
@Path("/on-event")
@Consumes(MediaType.APPLICATION_JSON)
public void onEvent(final EventRepresentation rep) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.onEvent(repToModel(rep));
}
private Event repToModel(EventRepresentation rep) {
Event event = new Event();
event.setClientId(rep.getClientId());
event.setDetails(rep.getDetails());
event.setError(rep.getError());
event.setIpAddress(rep.getIpAddress());
event.setRealmId(rep.getRealmId());
event.setSessionId(rep.getSessionId());
event.setTime(rep.getTime());
event.setType(EventType.valueOf(rep.getType()));
event.setUserId(rep.getUserId());
return event;
}
@GET
@Path("/clear-admin-event-store")
@Produces(MediaType.APPLICATION_JSON)
public Response clearAdminEventStore() {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clearAdmin();
return Response.ok().build();
}
@GET
@Path("/clear-admin-event-store-for-realm")
@Produces(MediaType.APPLICATION_JSON)
public Response clearAdminEventStore(@QueryParam("realmId") String realmId) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clearAdmin(realmId);
return Response.ok().build();
}
@GET
@Path("/clear-admin-event-store-older-than")
@Produces(MediaType.APPLICATION_JSON)
public Response clearAdminEventStore(@QueryParam("realmId") String realmId, @QueryParam("olderThan") long olderThan) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.clearAdmin(realmId, olderThan);
return Response.ok().build();
}
/**
* Get admin events
*
* Returns all admin events, or filters events based on URL query parameters listed here
*
* @param realmId
* @param operationTypes
* @param authRealm
* @param authClient
* @param authUser user id
* @param authIpAddress
* @param resourcePath
* @param dateFrom
* @param dateTo
* @param firstResult
* @param maxResults
* @return
*/
@Path("query-admin-events")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<AdminEventRepresentation> getAdminEvents(@QueryParam("realmId") String realmId, @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) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
AdminEventQuery query = eventStore.createAdminQuery();
if (realmId != null) {
query.realm(realmId);
};
if (authRealm != null) {
query.authRealm(authRealm);
}
if (authClient != null) {
query.authClient(authClient);
}
if (authUser != null) {
query.authUser(authUser);
}
if (authIpAddress != null) {
query.authIpAddress(authIpAddress);
}
if (resourcePath != null) {
query.resourcePath(resourcePath);
}
if (operationTypes != null && !operationTypes.isEmpty()) {
OperationType[] t = new OperationType[operationTypes.size()];
for (int i = 0; i < t.length; i++) {
t[i] = OperationType.valueOf(operationTypes.get(i));
}
query.operation(t);
}
if(dateFrom != null) {
Date from = formatDate(dateFrom, "Date(From)");
query.fromTime(from);
}
if(dateTo != null) {
Date to = formatDate(dateTo, "Date(To)");
query.toTime(to);
}
if (firstResult != null || maxResults != null) {
if (firstResult == null) {
firstResult = 0;
}
if (maxResults == null) {
maxResults = 100;
}
query.firstResult(firstResult);
query.maxResults(maxResults);
}
return toAdminEventRep(query.getResultList());
}
private Date formatDate(String date, String paramName) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
return df.parse(date);
} catch (ParseException e) {
throw new BadRequestException("Invalid value for '" + paramName + "', expected format is yyyy-MM-dd");
}
}
private List<AdminEventRepresentation> toAdminEventRep(List<AdminEvent> events) {
List<AdminEventRepresentation> reps = new ArrayList<>();
for (AdminEvent event : events) {
reps.add(ModelToRepresentation.toRepresentation(event));
}
return reps;
}
@POST
@Path("/on-admin-event")
@Consumes(MediaType.APPLICATION_JSON)
public void onAdminEvent(final AdminEventRepresentation rep, @QueryParam("includeRepresentation") boolean includeRepresentation) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
eventStore.onEvent(repToModel(rep), includeRepresentation);
}
private AdminEvent repToModel(AdminEventRepresentation rep) {
AdminEvent event = new AdminEvent();
event.setAuthDetails(repToModel(rep.getAuthDetails()));
event.setError(rep.getError());
event.setOperationType(OperationType.valueOf(rep.getOperationType()));
if (rep.getResourceType() != null) {
event.setResourceType(ResourceType.valueOf(rep.getResourceType()));
}
event.setRealmId(rep.getRealmId());
event.setRepresentation(rep.getRepresentation());
event.setResourcePath(rep.getResourcePath());
event.setTime(rep.getTime());
return event;
}
private AuthDetails repToModel(AuthDetailsRepresentation rep) {
AuthDetails details = new AuthDetails();
details.setClientId(rep.getClientId());
details.setIpAddress(rep.getIpAddress());
details.setRealmId(rep.getRealmId());
details.setUserId(rep.getUserId());
return details;
}
@Path("/cache/{cache}")
public TestCacheResource getCacheResource(@PathParam("cache") String cacheName) {
return new TestCacheResource(session, cacheName);
}
@Override
public void close() {
}
@POST
@Path("/update-pass-through-auth-state")
@Produces(MediaType.APPLICATION_JSON)
public AuthenticatorState updateAuthenticator(AuthenticatorState state) {
if (state.getClientId() != null) {
PassThroughClientAuthenticator.clientId = state.getClientId();
}
if (state.getUsername() != null) {
PassThroughAuthenticator.username = state.getUsername();
}
AuthenticatorState result = new AuthenticatorState();
result.setClientId(PassThroughClientAuthenticator.clientId);
result.setUsername(PassThroughAuthenticator.username);
return result;
}
@GET
@Path("/valid-credentials")
@Produces(MediaType.APPLICATION_JSON)
public boolean validCredentials(@QueryParam("realmName") String realmName, @QueryParam("userName") String userName, @QueryParam("password") String password) {
RealmModel realm = session.realms().getRealm(realmName);
if (realm == null) return false;
UserProvider userProvider = session.getProvider(UserProvider.class);
UserModel user = userProvider.getUserByUsername(userName, realm);
return session.userCredentialManager().isValid(realm, user, UserCredentialModel.password(password));
}
@GET
@Path("/user-by-federated-identity")
@Produces(MediaType.APPLICATION_JSON)
public UserRepresentation getUserByFederatedIdentity(@QueryParam("realmName") String realmName,
@QueryParam("identityProvider") String identityProvider,
@QueryParam("userId") String userId,
@QueryParam("userName") String userName) {
RealmModel realm = getRealmByName(realmName);
UserModel foundFederatedUser = session.users().getUserByFederatedIdentity(new FederatedIdentityModel(identityProvider, userId, userName), realm);
if (foundFederatedUser == null) return null;
return ModelToRepresentation.toRepresentation(session, realm, foundFederatedUser);
}
@GET
@Path("/user-by-username-from-fed-factory")
@Produces(MediaType.APPLICATION_JSON)
public UserRepresentation getUserByUsernameFromFedProviderFactory(@QueryParam("realmName") String realmName,
@QueryParam("userName") String userName) {
RealmModel realm = getRealmByName(realmName);
DummyUserFederationProviderFactory factory = (DummyUserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, "dummy");
UserModel user = factory.create(session, null).getUserByUsername(userName, realm);
if (user == null) return null;
return ModelToRepresentation.toRepresentation(session, realm, user);
}
@GET
@Path("/get-client-auth-flow")
@Produces(MediaType.APPLICATION_JSON)
public AuthenticationFlowRepresentation getClientAuthFlow(@QueryParam("realmName") String realmName) {
RealmModel realm = getRealmByName(realmName);
AuthenticationFlowModel flow = realm.getClientAuthenticationFlow();
if (flow == null) return null;
return ModelToRepresentation.toRepresentation(realm, flow);
}
@GET
@Path("/get-reset-cred-flow")
@Produces(MediaType.APPLICATION_JSON)
public AuthenticationFlowRepresentation getResetCredFlow(@QueryParam("realmName") String realmName) {
RealmModel realm = getRealmByName(realmName);
AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
if (flow == null) return null;
return ModelToRepresentation.toRepresentation(realm, flow);
}
@GET
@Path("/get-user-by-service-account-client")
@Produces(MediaType.APPLICATION_JSON)
public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId) {
RealmModel realm = getRealmByName(realmName);
ClientModel client = realm.getClientByClientId(clientId);
UserModel user = session.users().getServiceAccount(client);
if (user == null) return null;
return ModelToRepresentation.toRepresentation(session, realm, user);
}
@Path("/export-import")
public TestingExportImportResource getExportImportResource() {
return new TestingExportImportResource(session);
}
@GET
@Path("/test-component")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, TestProvider.DetailsRepresentation> getTestComponentDetails() {
Map<String, TestProvider.DetailsRepresentation> reps = new HashMap<>();
RealmModel realm = session.getContext().getRealm();
for (ComponentModel c : realm.getComponents(realm.getId(), TestProvider.class.getName())) {
ProviderFactory<TestProvider> f = session.getKeycloakSessionFactory().getProviderFactory(TestProvider.class, c.getProviderId());
TestProviderFactory factory = (TestProviderFactory) f;
TestProvider p = (TestProvider) factory.create(session, c);
reps.put(c.getName(), p.getDetails());
}
return reps;
}
@GET
@Path("/identity-config")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, String> getIdentityProviderConfig(@QueryParam("alias") String alias) {
return session.getContext().getRealm().getIdentityProviderByAlias(alias).getConfig();
}
@PUT
@Path("/set-krb5-conf-file")
@Consumes(MediaType.APPLICATION_JSON)
public void setKrb5ConfFile(@QueryParam("krb5-conf-file") String krb5ConfFile) {
System.setProperty("java.security.krb5.conf", krb5ConfFile);
}
@POST
@Path("/run-on-server")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String runOnServer(String runOnServer) throws Exception {
try {
ClassLoader cl = ModuleUtil.isModules() ? ModuleUtil.getClassLoader() : getClass().getClassLoader();
Object r = SerializationUtil.decode(runOnServer, cl);
if (r instanceof FetchOnServer) {
Object result = ((FetchOnServer) r).run(session);
return result != null ? JsonSerialization.writeValueAsString(result) : null;
} else if (r instanceof RunOnServer) {
((RunOnServer) r).run(session);
return null;
} else {
throw new IllegalArgumentException();
}
} catch (Throwable t) {
return SerializationUtil.encodeException(t);
}
}
private RealmModel getRealmByName(String realmName) {
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
return realmProvider.getRealmByName(realmName);
}
}