[KEYCLOAK-8060] - My Resources REST API

This commit is contained in:
Pedro Igor 2019-06-10 17:39:29 -03:00
parent 1463539d32
commit fdc0943a92
12 changed files with 1319 additions and 117 deletions

View file

@ -104,13 +104,13 @@ public class PermissionResource {
if (ticket == null) {
throw new IllegalArgumentException("Permission ticket must not be null or empty");
}
if (ticket.getRequester() == null || ticket.getRequesterName() == null) {
if (ticket.getRequester() == null && ticket.getRequesterName() == null) {
throw new IllegalArgumentException("Permission ticket must have a requester");
}
if (ticket.getResource() == null || ticket.getResourceName() == null) {
if (ticket.getResource() == null && ticket.getResourceName() == null) {
throw new IllegalArgumentException("Permission ticket must have a resource");
}
if (ticket.getScope() == null || ticket.getScopeName() == null) {
if (ticket.getScope() == null && ticket.getScopeName() == null) {
throw new IllegalArgumentException("Permission ticket must have a scope");
}
Callable<PermissionTicketRepresentation> callable = new Callable<PermissionTicketRepresentation>() {

View file

@ -266,6 +266,13 @@ public class ResourceRepresentation {
}
}
public void addScope(ScopeRepresentation scope) {
if (scopes == null) {
scopes = new HashSet<>();
}
scopes.add(scope);
}
public Map<String, List<String>> getAttributes() {
return attributes;
}

View file

@ -146,6 +146,7 @@ public class StoreFactoryCacheManager extends CacheManager {
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByOwner(owner, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResource(resource, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByGranted(requester, serverId));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByGranted(requester, null));
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByResourceNameAndGranted(resourceName, requester, serverId));
if (scope != null) {
invalidations.add(StoreFactoryCacheSession.getPermissionTicketByScope(scope, serverId));

View file

@ -27,8 +27,10 @@ import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
@ -97,6 +99,10 @@ public class SimpleHttp {
return new SimpleHttp(url, "POST", client);
}
public static SimpleHttp doPut(String url, HttpClient client) {
return new SimpleHttp(url, "PUT", client);
}
public SimpleHttp header(String name, String value) {
if (headers == null) {
headers = new HashMap<String, String>();
@ -166,9 +172,11 @@ public class SimpleHttp {
private Response makeRequest() throws IOException {
boolean get = method.equals("GET");
boolean post = method.equals("POST");
boolean put = method.equals("PUT");
boolean delete = method.equals("DELETE");
HttpRequestBase httpRequest = new HttpPost(url);
if (get) {
httpRequest = new HttpGet(appendParameterToUrl(url));
}
@ -177,14 +185,18 @@ public class SimpleHttp {
httpRequest = new HttpDelete(appendParameterToUrl(url));
}
if (post) {
if (put) {
httpRequest = new HttpPut(appendParameterToUrl(url));
}
if (post || put) {
if (params != null) {
((HttpPost) httpRequest).setEntity(getFormEntityFromParameter());
((HttpEntityEnclosingRequestBase) httpRequest).setEntity(getFormEntityFromParameter());
} else if (entity != null) {
if (headers == null || !headers.containsKey("Content-Type")) {
header("Content-Type", "application/json");
}
((HttpPost) httpRequest).setEntity(getJsonEntity());
((HttpEntityEnclosingRequestBase) httpRequest).setEntity(getJsonEntity());
} else {
throw new IllegalStateException("No content set");
}

View file

@ -9,6 +9,7 @@ import org.keycloak.forms.login.freemarker.model.UrlBean;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.theme.FreeMarkerUtil;
@ -64,7 +65,14 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
}
if (!MediaTypeMatcher.isHtmlRequest(headers)) {
return Response.status(statusCode).build();
OAuth2ErrorRepresentation error = new OAuth2ErrorRepresentation();
error.setError(getErrorCode(throwable));
return Response.status(statusCode)
.header(HttpHeaders.CONTENT_TYPE, javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE.toString())
.entity(error)
.build();
}
try {
@ -100,6 +108,16 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
return status;
}
private String getErrorCode(Throwable throwable) {
String error = throwable.getMessage();
if (error == null) {
return "unknown_error";
}
return error;
}
private RealmModel resolveRealm() {
String path = session.getContext().getUri().getPath();
Matcher m = realmNamePattern.matcher(path);

View file

@ -37,6 +37,7 @@ import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.account.resources.ResourcesService;
import org.keycloak.storage.ReadOnlyException;
import javax.ws.rs.*;
@ -288,6 +289,13 @@ public class AccountRestService {
return new AccountCredentialResource(session, event, user, auth);
}
@Path("/resources")
public ResourcesService resources() {
checkAccountApiEnabled();
auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
return new ResourcesService(session, user, auth, request);
}
// TODO Federated identities
// TODO Applications
// TODO Logs

View file

@ -0,0 +1,231 @@
/*
* 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.services.resources.account.resources;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.PermissionTicketStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.resources.Cors;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public abstract class AbstractResourceService {
protected final UserModel user;
protected final AuthorizationProvider provider;
protected final PermissionTicketStore ticketStore;
protected final ResourceStore resourceStore;
protected final ScopeStore scopeStore;
protected HttpRequest request;
protected Auth auth;
protected AbstractResourceService(KeycloakSession session, UserModel user, Auth auth, HttpRequest request) {
this.user = user;
this.auth = auth;
this.request = request;
provider = session.getProvider(AuthorizationProvider.class);
ticketStore = provider.getStoreFactory().getPermissionTicketStore();
resourceStore = provider.getStoreFactory().getResourceStore();
scopeStore = provider.getStoreFactory().getScopeStore();
}
protected Response cors(Response.ResponseBuilder response) {
return Cors.add(request, response).auth().allowedOrigins(auth.getToken()).build();
}
protected Collection<ResourcePermission> getPermissions(List<PermissionTicket> tickets, boolean withRequesters) {
Map<String, ResourcePermission> permissions = new HashMap<>();
for (PermissionTicket ticket : tickets) {
ResourcePermission resource = permissions
.computeIfAbsent(ticket.getResource().getId(), s -> new ResourcePermission(ticket, provider));
if (withRequesters) {
Permission user = resource.getPermission(ticket.getRequester());
if (user == null) {
resource.addPermission(ticket.getRequester(), user = new Permission(ticket.getRequester(), provider));
}
user.addScope(ticket.getScope().getName());
} else {
resource.addScope(new Scope(ticket.getScope()));
}
}
return permissions.values();
}
public static class Resource extends ResourceRepresentation {
private Client client;
public Resource() {
}
Resource(org.keycloak.authorization.model.Resource resource, UserModel owner, AuthorizationProvider provider) {
setId(resource.getId());
setName(resource.getName());
setDisplayName(resource.getDisplayName());
setUris(resource.getUris());
setIconUri(resource.getIconUri());
setScopes(resource.getScopes().stream().map(Scope::new).collect(Collectors.toSet()));
ResourceServer resourceServer = resource.getResourceServer();
this.client = new Client(provider.getRealm().getClientById(resourceServer.getId()));
}
Resource(org.keycloak.authorization.model.Resource resource, AuthorizationProvider provider) {
this(resource, null, provider);
}
public Client getClient() {
return client;
}
}
public static class ResourcePermission extends Resource {
private Map<String, Permission> permissions;
public ResourcePermission() {
}
ResourcePermission(PermissionTicket ticket, AuthorizationProvider provider) {
super(ticket.getResource(), provider);
setScopes(new HashSet<>());
}
public Collection<Permission> getPermissions() {
if (permissions == null) {
return null;
}
return permissions.values();
}
public void setPermissions(Collection<Permission> permissions) {
for (Permission permission : permissions) {
addPermission(permission.getUsername(), permission);
}
}
public void addPermission(String requester, Permission permission) {
if (permissions == null) {
permissions = new HashMap<>();
}
permissions.put(requester, permission);
}
public Permission getPermission(String requester) {
if (permissions == null) {
return null;
}
return permissions.get(requester);
}
}
public static class Permission extends UserRepresentation {
private List<String> scopes;
public Permission() {
}
Permission(String userId, AuthorizationProvider provider) {
UserModel user = provider.getKeycloakSession().users().getUserById(userId, provider.getRealm());
setUsername(user.getUsername());
setFirstName(user.getFirstName());
setLastName(user.getLastName());
setEmail(user.getEmail());
}
Permission(PermissionTicket ticket, AuthorizationProvider provider) {
this(ticket.getRequester(), provider);
}
public Permission(String userName, String... scopes) {
setUsername(userName);
for (String scope : scopes) {
addScope(scope);
}
}
public List<String> getScopes() {
return scopes;
}
public void addScope(String... scope) {
if (scopes == null) {
scopes = new ArrayList<>();
}
scopes.addAll(Arrays.asList(scope));
}
}
public static class Scope extends ScopeRepresentation {
public Scope() {
}
Scope(org.keycloak.authorization.model.Scope scope) {
setName(scope.getName());
setDisplayName(scope.getDisplayName());
setIconUri(scope.getIconUri());
}
}
public static class Client extends ClientRepresentation {
public Client() {
}
Client(ClientModel client) {
setClientId(client.getClientId());
setName(client.getName());
setBaseUrl(client.getBaseUrl());
}
}
}

View file

@ -0,0 +1,213 @@
/*
* 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.services.resources.account.resources;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.services.managers.Auth;
import org.keycloak.utils.MediaType;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ResourceService extends AbstractResourceService {
private final org.keycloak.authorization.model.Resource resource;
private final ResourceServer resourceServer;
ResourceService(org.keycloak.authorization.model.Resource resource, KeycloakSession session, UserModel user,
Auth auth, HttpRequest request) {
super(session, user, auth, request);
this.resource = resource;
this.resourceServer = resource.getResourceServer();
}
/**
* Returns a {@link Resource} where the {@link #user} is the resource owner.
*
* @return the resource
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getResource() {
return cors(Response.ok(new Resource(resource, provider)));
}
/**
* Returns a list of {@link Permission} containing the users to which the {@link #user} granted access to a resource.
*
* @return the users with access to a resource
*/
@GET
@Path("permissions")
@Produces(MediaType.APPLICATION_JSON)
public Response getPermissions() {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.OWNER, user.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
filters.put(PermissionTicket.RESOURCE, resource.getId());
Collection<ResourcePermission> resources = getPermissions(ticketStore.find(filters, null, -1, -1), true);
Collection<Permission> permissions = Collections.EMPTY_LIST;
if (!resources.isEmpty()) {
permissions = resources.iterator().next().getPermissions();
}
return cors(Response.ok(permissions));
}
/**
* Updates the permission set for a resource based on the given {@code permissions}.
*
* @param permissions the permissions that should be updated
* @return if successful, a {@link Response.Status#NO_CONTENT} response
*/
@PUT
@Path("permissions")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response revoke(List<Permission> permissions) {
if (permissions == null || permissions.isEmpty()) {
throw new BadRequestException("invalid_permissions");
}
ResourceServer resourceServer = resource.getResourceServer();
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.RESOURCE, resource.getId());
for (Permission permission : permissions) {
UserModel user = getUser(permission.getUsername());
filters.put(PermissionTicket.REQUESTER, user.getId());
List<PermissionTicket> tickets = ticketStore.find(filters, resource.getResourceServer().getId(), -1, -1);
// grants all requested permissions
if (tickets.isEmpty()) {
for (String scope : permission.getScopes()) {
grantPermission(user, scope);
}
} else {
Iterator<String> scopesIterator = permission.getScopes().iterator();
while (scopesIterator.hasNext()) {
org.keycloak.authorization.model.Scope scope = getScope(scopesIterator.next(), resourceServer);
Iterator<PermissionTicket> ticketIterator = tickets.iterator();
while (ticketIterator.hasNext()) {
PermissionTicket ticket = ticketIterator.next();
if (scope.getId().equals(ticket.getScope().getId())) {
if (!ticket.isGranted()) {
ticket.setGrantedTimestamp(System.currentTimeMillis());
}
// permission exists, remove from the list to avoid deletion
ticketIterator.remove();
// scope already granted, remove from the list to avoid creating it again
scopesIterator.remove();
}
}
}
// only create permissions for the scopes that don't have a tocket
for (String scope : permission.getScopes()) {
grantPermission(user, scope);
}
// remove all tickets that are not within the requested permissions
for (PermissionTicket ticket : tickets) {
ticketStore.delete(ticket.getId());
}
}
}
return cors(Response.noContent());
}
/**
* Returns a list of {@link Permission} requests waiting for the {@link #user} approval.
*
* @return the permission requests waiting for the user approval
*/
@GET
@Path("permissions/requests")
@Produces(MediaType.APPLICATION_JSON)
public Response getPermissionRequests() {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.OWNER, user.getId());
filters.put(PermissionTicket.GRANTED, Boolean.FALSE.toString());
filters.put(PermissionTicket.RESOURCE, resource.getId());
Map<String, Permission> requests = new HashMap<>();
for (PermissionTicket ticket : ticketStore.find(filters, null, -1, -1)) {
requests.computeIfAbsent(ticket.getRequester(), requester -> new Permission(ticket, provider)).addScope(ticket.getScope().getName());
}
return cors(Response.ok(requests.values()));
}
private void grantPermission(UserModel user, String scopeId) {
org.keycloak.authorization.model.Scope scope = getScope(scopeId, resourceServer);
PermissionTicket ticket = ticketStore.create(resource.getId(), scope.getId(), user.getId(), resourceServer);
ticket.setGrantedTimestamp(Calendar.getInstance().getTimeInMillis());
}
private org.keycloak.authorization.model.Scope getScope(String scopeId, ResourceServer resourceServer) {
org.keycloak.authorization.model.Scope scope = scopeStore.findByName(scopeId, resourceServer.getId());
if (scope == null) {
scope = scopeStore.findById(scopeId, resourceServer.getId());
}
return scope;
}
private UserModel getUser(String requester) {
UserProvider users = provider.getKeycloakSession().users();
UserModel user = users.getUserByUsername(requester, provider.getRealm());
if (user == null) {
user = users.getUserById(requester, provider.getRealm());
}
return user;
}
}

View file

@ -0,0 +1,104 @@
/*
* 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.services.resources.account.resources;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.Auth;
import org.keycloak.utils.MediaType;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ResourcesService extends AbstractResourceService {
public ResourcesService(KeycloakSession session, UserModel user, Auth auth, HttpRequest request) {
super(session, user, auth, request);
}
/**
* Returns a list of {@link Resource} where the {@link #user} is the resource owner.
*
* @return a list of {@link Resource} where the {@link #user} is the resource owner
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getResources() {
return cors(Response.ok(resourceStore.findByOwner(user.getId(), null).stream()
.map(resource -> new Resource(resource, user, provider))
.collect(Collectors.toList())));
}
/**
* Returns a list of {@link Resource} shared with the {@link #user}
*
* @return a list of {@link Resource} shared with the {@link #user}
*/
@GET
@Path("shared-with-me")
@Produces(MediaType.APPLICATION_JSON)
public Response getSharedWithMe() {
return cors(Response.ok(getPermissions(ticketStore.findGranted(user.getId(), null), false)));
}
/**
* Returns a list of {@link Resource} where the {@link #user} is the resource owner and the resource is
* shared with other users.
*
* @return a list of {@link Resource} where the {@link #user} is the resource owner and the resource is
* * shared with other users
*/
@GET
@Path("shared-with-others")
@Produces(MediaType.APPLICATION_JSON)
public Response getSharedWithOthers() {
Map<String, String> filters = new HashMap<>();
filters.put(PermissionTicket.OWNER, user.getId());
filters.put(PermissionTicket.GRANTED, Boolean.TRUE.toString());
return cors(Response.ok(getPermissions(ticketStore.find(filters, null, -1, -1), true)));
}
@Path("{id}")
public Object getResource(@PathParam("id") String id) {
org.keycloak.authorization.model.Resource resource = resourceStore.findById(id, null);
if (resource == null) {
throw new NotFoundException("resource_not_found");
}
if (!resource.getOwner().equals(user.getId())) {
throw new BadRequestException("invalid_resource");
}
return new ResourceService(resource, provider.getKeycloakSession(), user, auth, request);
}
}

View file

@ -0,0 +1,113 @@
/*
* 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.account;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.representations.account.SessionRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.util.TokenUtil;
import org.keycloak.testsuite.util.UserBuilder;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractRestServiceTest extends AbstractTestRealmKeycloakTest {
@Rule
public TokenUtil tokenUtil = new TokenUtil();
@Rule
public AssertEvents events = new AssertEvents(this);
protected CloseableHttpClient httpClient;
@Before
public void before() {
httpClient = HttpClientBuilder.create().build();
try {
checkIfFeatureWorks(false);
Response response = testingClient.testing().enableFeature(ACCOUNT_API.toString());
assertEquals(200, response.getStatus());
assumeFeatureEnabled(ACCOUNT_API);
checkIfFeatureWorks(true);
} catch (Exception e) {
disableFeature();
throw e;
}
}
@After
public void after() {
try {
disableFeature();
httpClient.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void disableFeature() {
Response response = testingClient.testing().disableFeature(ACCOUNT_API.toString());
assertEquals(200, response.getStatus());
checkIfFeatureWorks(false);
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.getUsers().add(UserBuilder.create().username("no-account-access").password("password").build());
testRealm.getUsers().add(UserBuilder.create().username("view-account-access").role("account", "view-profile").password("password").build());
}
protected String getAccountUrl(String resource) {
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account" + (resource != null ? "/" + resource : "");
}
// Check if the feature really works
private void checkIfFeatureWorks(boolean shouldWorks) {
try {
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(tokenUtil.getToken())
.asJson(new TypeReference<List<SessionRepresentation>>() {
});
assertEquals(1, sessions.size());
if (!shouldWorks)
fail("Feature is available, but this moment should be disabled");
} catch (Exception e) {
if (shouldWorks) {
e.printStackTrace();
fail("Feature is not available");
}
}
}
}

View file

@ -17,11 +17,6 @@
package org.keycloak.testsuite.account;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.representations.account.SessionRepresentation;
@ -31,12 +26,8 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.account.AccountCredentialResource;
import org.keycloak.services.resources.account.AccountCredentialResource.PasswordUpdate;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.util.TokenUtil;
import org.keycloak.testsuite.util.UserBuilder;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
@ -51,57 +42,11 @@ import static org.keycloak.testsuite.ProfileAssume.assumeFeatureEnabled;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
@Rule
public TokenUtil tokenUtil = new TokenUtil();
@Rule
public AssertEvents events = new AssertEvents(this);
private CloseableHttpClient client;
@Before
public void before() {
client = HttpClientBuilder.create().build();
try {
checkIfFeatureWorks(false);
Response response = testingClient.testing().enableFeature(ACCOUNT_API.toString());
assertEquals(200, response.getStatus());
assumeFeatureEnabled(ACCOUNT_API);
checkIfFeatureWorks(true);
} catch (Exception e) {
disableFeature();
throw e;
}
}
@After
public void after() {
try {
disableFeature();
client.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void disableFeature() {
Response response = testingClient.testing().disableFeature(ACCOUNT_API.toString());
assertEquals(200, response.getStatus());
checkIfFeatureWorks(false);
}
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
testRealm.getUsers().add(UserBuilder.create().username("no-account-access").password("password").build());
testRealm.getUsers().add(UserBuilder.create().username("view-account-access").role("account", "view-profile").password("password").build());
}
public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test
public void testGetProfile() throws IOException {
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
assertEquals("Tom", user.getFirstName());
assertEquals("Brady", user.getLastName());
assertEquals("test-user@localhost", user.getEmail());
@ -111,7 +56,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
@Test
public void testUpdateProfile() throws IOException {
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
String originalFirstName = user.getFirstName();
String originalLastName = user.getLastName();
String originalEmail = user.getEmail();
@ -182,7 +127,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
user.setLastName(originalLastName);
user.setEmail(originalEmail);
user.setAttributes(originalAttributes);
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asResponse();
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
System.out.println(response.asString());
assertEquals(200, response.getStatus());
}
@ -196,7 +141,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
realmRep.setRegistrationEmailAsUsername(true);
adminClient.realm("test").update(realmRep);
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
String originalFirstname = user.getFirstName();
try {
@ -207,20 +152,20 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
assertEquals("Homer1", user.getFirstName());
} finally {
user.setFirstName(originalFirstname);
int status = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asStatus();
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
assertEquals(200, status);
}
}
private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
int status = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asStatus();
int status = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asStatus();
assertEquals(200, status);
return SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
return SimpleHttp.doGet(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
}
private void updateError(UserRepresentation user, int expectedStatus, String expectedMessage) throws IOException {
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asResponse();
SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(tokenUtil.getToken()).json(user).asResponse();
assertEquals(expectedStatus, response.getStatus());
assertEquals(expectedMessage, response.asJson(ErrorRepresentation.class).getErrorMessage());
}
@ -231,13 +176,13 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
// Read with no access
assertEquals(403, SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doGet(getAccountUrl(null), httpClient).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
// Update with no access
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(noaccessToken.getToken()).json(new UserRepresentation()).asStatus());
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(noaccessToken.getToken()).json(new UserRepresentation()).asStatus());
// Update with read only
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(viewToken.getToken()).json(new UserRepresentation()).asStatus());
assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), httpClient).auth(viewToken.getToken()).json(new UserRepresentation()).asStatus());
}
@Test
@ -248,38 +193,38 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
// Read sessions with no access
assertEquals(403, SimpleHttp.doGet(getAccountUrl("sessions"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
// Delete all sessions with no access
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("sessions"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("sessions"), httpClient).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
// Delete all sessions with read only
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("sessions"), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("sessions"), httpClient).header("Accept", "application/json").auth(viewToken.getToken()).asStatus());
// Delete single session with no access
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("session?id=bogusId"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("session?id=bogusId"), httpClient).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
// Delete single session with read only
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("session?id=bogusId"), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doDelete(getAccountUrl("session?id=bogusId"), httpClient).header("Accept", "application/json").auth(viewToken.getToken()).asStatus());
// Read password details with no access
assertEquals(403, SimpleHttp.doGet(getAccountUrl("credentials/password"), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
assertEquals(403, SimpleHttp.doGet(getAccountUrl("credentials/password"), httpClient).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
// Update password with no access
assertEquals(403, SimpleHttp.doPost(getAccountUrl("credentials/password"), client).auth(noaccessToken.getToken()).json(new PasswordUpdate()).asStatus());
assertEquals(403, SimpleHttp.doPost(getAccountUrl("credentials/password"), httpClient).auth(noaccessToken.getToken()).json(new PasswordUpdate()).asStatus());
// Update password with read only
assertEquals(403, SimpleHttp.doPost(getAccountUrl("credentials/password"), client).auth(viewToken.getToken()).json(new PasswordUpdate()).asStatus());
assertEquals(403, SimpleHttp.doPost(getAccountUrl("credentials/password"), httpClient).auth(viewToken.getToken()).json(new PasswordUpdate()).asStatus());
}
@Test
public void testUpdateProfilePermissions() throws IOException {
TokenUtil noaccessToken = new TokenUtil("no-account-access", "password");
int status = SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus();
int status = SimpleHttp.doGet(getAccountUrl(null), httpClient).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus();
assertEquals(403, status);
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
status = SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus();
status = SimpleHttp.doGet(getAccountUrl(null), httpClient).header("Accept", "application/json").auth(viewToken.getToken()).asStatus();
assertEquals(200, status);
}
@ -287,7 +232,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
public void testGetSessions() throws IOException {
assumeFeatureEnabled(ACCOUNT_API);
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
assertEquals(1, sessions.size());
}
@ -337,7 +282,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
}
private AccountCredentialResource.PasswordDetails getPasswordDetails() throws IOException {
AccountCredentialResource.PasswordDetails details = SimpleHttp.doGet(getAccountUrl("credentials/password"), client).auth(tokenUtil.getToken()).asJson(new TypeReference<AccountCredentialResource.PasswordDetails>() {});
AccountCredentialResource.PasswordDetails details = SimpleHttp.doGet(getAccountUrl("credentials/password"), httpClient).auth(tokenUtil.getToken()).asJson(new TypeReference<AccountCredentialResource.PasswordDetails>() {});
assertTrue(details.isRegistered());
assertNotNull(details.getLastUpdate());
return details;
@ -352,18 +297,18 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
passwordUpdate.setCurrentPassword(currentPass);
passwordUpdate.setNewPassword(newPass);
passwordUpdate.setConfirmation(confirmation);
int status = SimpleHttp.doPost(getAccountUrl("credentials/password"), client).auth(tokenUtil.getToken()).json(passwordUpdate).asStatus();
int status = SimpleHttp.doPost(getAccountUrl("credentials/password"), httpClient).auth(tokenUtil.getToken()).json(passwordUpdate).asStatus();
assertEquals(expectedStatus, status);
}
public void testDeleteSessions() throws IOException {
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
oauth.doLogin("view-account-access", "password");
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
assertEquals(2, sessions.size());
int status = SimpleHttp.doDelete(getAccountUrl("sessions?current=false"), client).acceptJson().auth(viewToken.getToken()).asStatus();
int status = SimpleHttp.doDelete(getAccountUrl("sessions?current=false"), httpClient).acceptJson().auth(viewToken.getToken()).asStatus();
assertEquals(200, status);
sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
assertEquals(1, sessions.size());
}
@ -373,41 +318,19 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
TokenUtil viewToken = new TokenUtil("view-account-access", "password");
String sessionId = oauth.doLogin("view-account-access", "password").getSessionState();
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
assertEquals(2, sessions.size());
// With `ViewToken` you can only read
int status = SimpleHttp.doDelete(getAccountUrl("session?id=" + sessionId), client).acceptJson().auth(viewToken.getToken()).asStatus();
int status = SimpleHttp.doDelete(getAccountUrl("session?id=" + sessionId), httpClient).acceptJson().auth(viewToken.getToken()).asStatus();
assertEquals(403, status);
sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(viewToken.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
assertEquals(2, sessions.size());
// Here you can delete the session
status = SimpleHttp.doDelete(getAccountUrl("session?id=" + sessionId), client).acceptJson().auth(tokenUtil.getToken()).asStatus();
status = SimpleHttp.doDelete(getAccountUrl("session?id=" + sessionId), httpClient).acceptJson().auth(tokenUtil.getToken()).asStatus();
assertEquals(200, status);
sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
sessions = SimpleHttp.doGet(getAccountUrl("sessions"), httpClient).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
assertEquals(1, sessions.size());
}
private String getAccountUrl(String resource) {
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account" + (resource != null ? "/" + resource : "");
}
// Check if the feature really works
private void checkIfFeatureWorks(boolean shouldWorks) {
try {
List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken())
.asJson(new TypeReference<List<SessionRepresentation>>() {
});
assertEquals(1, sessions.size());
if (!shouldWorks)
fail("Feature is available, but this moment should be disabled");
} catch (Exception e) {
if (shouldWorks) {
e.printStackTrace();
fail("Feature is not available");
}
}
}
}

View file

@ -0,0 +1,572 @@
/*
* 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.account;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.Test;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AccountRoles;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PermissionTicketRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.resources.account.resources.AbstractResourceService;
import org.keycloak.services.resources.account.resources.AbstractResourceService.Permission;
import org.keycloak.services.resources.account.resources.AbstractResourceService.Resource;
import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ResourcesRestServiceTest extends AbstractRestServiceTest {
private AuthzClient authzClient;
private List<String> userNames = new ArrayList<>(Arrays.asList("alice", "jdoe", "bob"));
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
super.configureTestRealm(testRealm);
RealmRepresentation realmRepresentation = testRealm;
realmRepresentation.setUserManagedAccessAllowed(true);
testRealm.getUsers().add(createUser("alice", "password"));
testRealm.getUsers().add(createUser("jdoe", "password"));
testRealm.getUsers().add(createUser("bob", "password"));
ClientRepresentation client = ClientBuilder.create()
.clientId("my-resource-server")
.authorizationServicesEnabled(true)
.serviceAccountsEnabled(true)
.secret("secret")
.name("My Resource Server")
.baseUrl("http://resourceserver.com")
.directAccessGrants().build();
testRealm.getClients().add(client);
}
@Override
public void before() {
super.before();
ClientResource resourceServer = getResourceServer();
authzClient = createAuthzClient(resourceServer.toRepresentation());
AuthorizationResource authorization = resourceServer.authorization();
for (int i = 0; i < 30; i++) {
ResourceRepresentation resource = new ResourceRepresentation();
resource.setOwnerManagedAccess(true);
try {
resource.setOwner(
JsonSerialization.readValue(new JWSInput(tokenUtil.getToken()).getContent(), AccessToken.class)
.getSubject());
} catch (Exception cause) {
throw new RuntimeException("Failed to parse access token", cause);
}
resource.setName("Resource " + i);
resource.setDisplayName("Display Name " + i);
resource.setIconUri("Icon Uri " + i);
resource.addScope("Scope A", "Scope B", "Scope C", "Scope D");
resource.setUri("http://resourceServer.com/resources/" + i);
try (Response response1 = authorization.resources().create(resource)) {
resource.setId(response1.readEntity(ResourceRepresentation.class).getId());
}
for (String scope : Arrays.asList("Scope A", "Scope B")) {
PermissionTicketRepresentation ticket = new PermissionTicketRepresentation();
ticket.setGranted(true);
ticket.setOwner(resource.getOwner().getId());
ticket.setRequesterName(userNames.get(i % userNames.size()));
ticket.setResource(resource.getId());
ticket.setScopeName(scope);
authzClient.protection("test-user@localhost", "password").permission().create(ticket);
}
}
}
private ClientResource getResourceServer() {
ClientsResource clients = testRealm().clients();
return clients.get(clients.findByClientId("my-resource-server").get(0).getId());
}
@Override
public void after() {
super.after();
ClientResource resourceServer = getResourceServer();
ClientRepresentation representation = resourceServer.toRepresentation();
representation.setAuthorizationServicesEnabled(false);
resourceServer.update(representation);
representation.setAuthorizationServicesEnabled(true);
resourceServer.update(representation);
}
@Test
public void testGetMyResources() {
List<Resource> resources = getMyResources();
assertEquals(30, resources.size());
for (int i = 0; i < 30; i++) {
String uri = "http://resourceServer.com/resources/" + i;
Resource resource = resources.stream()
.filter(rep -> rep.getUris().stream().anyMatch(resourceUri -> resourceUri.equals(uri))).findAny()
.get();
assertNotNull(resource.getId());
assertEquals("Resource " + i, resource.getName());
assertEquals("Display Name " + i, resource.getDisplayName());
assertEquals("Icon Uri " + i, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
}
}
@Test
public void testGetSharedWithMe() {
for (String userName : userNames) {
List<AbstractResourceService.ResourcePermission> resources = getSharedWithMe(userName);
assertEquals(10, resources.size());
for (AbstractResourceService.ResourcePermission resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(2, resource.getScopes().size());
}
}
}
@Test
public void testGetSharedWithOthers() {
List<AbstractResourceService.ResourcePermission> resources = doGet("/shared-with-others",
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {
});
assertEquals(30, resources.size());
for (AbstractResourceService.ResourcePermission resource : resources) {
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(1, resource.getPermissions().size());
Permission user = resource.getPermissions().iterator().next();
assertTrue(userNames.contains(user.getUsername()));
assertEquals(2, user.getScopes().size());
}
}
@Test
public void testGetResource() {
Resource resource = doGet("/" + getMyResources().get(0).getId(), Resource.class);
String uri = resource.getUri();
int id = Integer.parseInt(uri.substring(uri.lastIndexOf('/') + 1));
assertNotNull(resource.getId());
assertEquals("Resource " + id, resource.getName());
assertEquals("Display Name " + id, resource.getDisplayName());
assertEquals("Icon Uri " + id, resource.getIconUri());
assertEquals("my-resource-server", resource.getClient().getClientId());
assertEquals("My Resource Server", resource.getClient().getName());
assertEquals("http://resourceserver.com", resource.getClient().getBaseUrl());
assertEquals(4, resource.getScopes().size());
OAuth2ErrorRepresentation response = doGet("/invalid_resource", OAuth2ErrorRepresentation.class);
assertEquals("resource_not_found", response.getError());
response = doGet("/" + getMyResources().get(0).getId(), authzClient.obtainAccessToken("jdoe", "password").getToken(), OAuth2ErrorRepresentation.class);
assertEquals("invalid_resource", response.getError());
}
@Test
public void testGetPermissions() throws Exception {
Resource resource = getMyResources().get(0);
List<Permission> shares = doGet("/" + resource.getId() + "/permissions", new TypeReference<List<Permission>>() {});
assertEquals(1, shares.size());
Permission firstShare = shares.get(0);
List<Permission> permissions = new ArrayList<>();
assertTrue(userNames.contains(firstShare.getUsername()));
assertEquals(2, firstShare.getScopes().size());
List<String> users = new ArrayList<>(userNames);
users.remove(firstShare.getUsername());
for (String userName : users) {
Permission permission = new Permission();
permission.setUsername(userName);
permission.addScope("Scope D");
permissions.add(permission);
}
SimpleHttp.doPut(getAccountUrl("resources/" + resource.getId() + "/permissions"), httpClient)
.auth(tokenUtil.getToken())
.json(permissions).asResponse();
shares = doGet("/" + resource.getId() + "/permissions", new TypeReference<List<Permission>>() {});
assertEquals(3, shares.size());
for (Permission user : shares) {
assertTrue(userNames.contains(user.getUsername()));
if (firstShare.getUsername().equals(user.getUsername())) {
assertEquals(2, user.getScopes().size());
} else {
assertEquals(1, user.getScopes().size());
}
}
}
@Test
public void testShareResource() throws Exception {
List<String> users = Arrays.asList("jdoe", "alice");
List<Permission> permissions = new ArrayList<>();
AbstractResourceService.ResourcePermission sharedResource = null;
for (String user : users) {
sharedResource = getSharedWithMe(user).get(0);
assertNotNull(sharedResource);
assertEquals(2, sharedResource.getScopes().size());
}
permissions.add(new Permission(users.get(0), "Scope C", "Scope D"));
permissions.add(new Permission(users.get(users.size() - 1), "Scope A", "Scope B", "Scope C", "Scope D"));
String resourceId = sharedResource.getId();
SimpleHttp.Response response = SimpleHttp.doPut(getAccountUrl("resources/" + resourceId + "/permissions"), httpClient)
.auth(tokenUtil.getToken())
.json(permissions).asResponse();
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
for (String user : users) {
sharedResource = getSharedWithMe(user).stream()
.filter(resource1 -> resource1.getId().equals(resourceId)).findAny().orElse(null);
assertNotNull(sharedResource);
if (user.equals(users.get(users.size() - 1))) {
assertEquals(4, sharedResource.getScopes().size());
} else {
assertEquals(2, sharedResource.getScopes().size());
}
}
}
@Test
public void failShareResourceInvalidPermissions() throws Exception {
List<Permission> permissions = new ArrayList<>();
SimpleHttp.Response response = SimpleHttp.doPut(getAccountUrl("resources/" + getMyResources().get(0).getId() + "/permissions"), httpClient)
.auth(tokenUtil.getToken())
.json(permissions).asResponse();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
}
@Test
public void testRevokePermission() throws Exception {
List<String> users = Arrays.asList("jdoe", "alice");
List<Permission> permissions = new ArrayList<>();
AbstractResourceService.ResourcePermission sharedResource = null;
for (String user : users) {
sharedResource = getSharedWithMe(user).get(0);
assertNotNull(sharedResource);
assertEquals(2, sharedResource.getScopes().size());
}
permissions.add(new Permission(users.get(0), "Scope C"));
permissions.add(new Permission(users.get(users.size() - 1), "Scope B", "Scope D"));
String resourceId = sharedResource.getId();
SimpleHttp.Response response = SimpleHttp.doPut(getAccountUrl("resources/" + resourceId + "/permissions"), httpClient)
.auth(tokenUtil.getToken())
.json(permissions).asResponse();
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
for (String user : users) {
sharedResource = getSharedWithMe(user).stream()
.filter(resource1 -> resource1.getId().equals(resourceId)).findAny().orElse(null);
assertNotNull(sharedResource);
if (user.equals(users.get(users.size() - 1))) {
assertEquals(2, sharedResource.getScopes().size());
} else {
assertEquals(1, sharedResource.getScopes().size());
}
}
}
@Test
public void testGetPermissionRequests() {
Resource resource = getMyResources().get(0);
List<Permission> requests = doGet("/" + resource.getId() + "/permissions/requests",
new TypeReference<List<Permission>>() {});
assertTrue(requests.isEmpty());
for (String userName : userNames) {
List<String> scopes = new ArrayList<>();
if ("bob".equals(userName)) {
scopes.add("Scope D");
} else if ("alice".equals(userName)) {
scopes.add("Scope C");
} else if ("jdoe".equals(userName)) {
scopes.add("Scope C");
scopes.add("Scope D");
}
for (String scope : scopes) {
PermissionTicketRepresentation ticket = new PermissionTicketRepresentation();
ticket.setGranted(false);
ticket.setOwner("test-user@localhost");
ticket.setRequesterName(userName);
ticket.setResource(resource.getId());
ticket.setScopeName(scope);
authzClient.protection("test-user@localhost", "password").permission().create(ticket);
}
}
requests = doGet("/" + resource.getId() + "/permissions/requests",
new TypeReference<List<Permission>>() {});
assertEquals(3, requests.size());
Iterator<Permission> iterator = requests.iterator();
while (iterator.hasNext()) {
Permission permission = iterator.next();
String username = permission.getUsername();
List<String> scopes = permission.getScopes();
if ("bob".equals(username)) {
assertEquals(1, scopes.size());
assertTrue(scopes.contains("Scope D"));
iterator.remove();
} else if ("alice".equals(username)) {
assertEquals(1, scopes.size());
assertTrue(scopes.contains("Scope C"));
iterator.remove();
} else if ("jdoe".equals(username)) {
assertEquals(2, scopes.size());
assertTrue(scopes.contains("Scope C"));
assertTrue(scopes.contains("Scope D"));
iterator.remove();
}
}
assertTrue(requests.isEmpty());
}
@Test
public void testApprovePermissionRequest() throws IOException {
Resource resource = getMyResources().get(0);
List<Permission> requests = doGet("/" + resource.getId() + "/permissions/requests",
new TypeReference<List<Permission>>() {});
assertTrue(requests.isEmpty());
for (String userName : userNames) {
List<String> scopes = new ArrayList<>();
if ("bob".equals(userName)) {
scopes.add("Scope D");
} else if ("alice".equals(userName)) {
scopes.add("Scope C");
} else if ("jdoe".equals(userName)) {
scopes.add("Scope C");
scopes.add("Scope D");
}
for (String scope : scopes) {
PermissionTicketRepresentation ticket = new PermissionTicketRepresentation();
ticket.setGranted(false);
ticket.setOwner("test-user@localhost");
ticket.setRequesterName(userName);
ticket.setResource(resource.getId());
ticket.setScopeName(scope);
authzClient.protection("test-user@localhost", "password").permission().create(ticket);
}
}
requests = doGet("/" + resource.getId() + "/permissions/requests",
new TypeReference<List<Permission>>() {});
assertEquals(3, requests.size());
Iterator<Permission> iterator = requests.iterator();
while (iterator.hasNext()) {
Permission permission = iterator.next();
String username = permission.getUsername();
List<String> scopes = permission.getScopes();
if ("bob".equals(username)) {
scopes.clear();
} else if ("jdoe".equals(username)) {
scopes.remove("Scope C");
}
}
SimpleHttp.doPut(getAccountUrl("resources/" + resource.getId() + "/permissions"), httpClient)
.auth(tokenUtil.getToken())
.json(requests).asResponse();
requests = doGet("/" + resource.getId() + "/permissions/requests",
new TypeReference<List<Permission>>() {});
assertTrue(requests.isEmpty());
for (String user : Arrays.asList("alice", "jdoe")) {
AbstractResourceService.ResourcePermission sharedResource = getSharedWithMe(user).stream()
.filter(resource1 -> resource1.getId().equals(resource.getId())).findAny().orElse(null);
assertNotNull(sharedResource);
Set<ScopeRepresentation> scopes = sharedResource.getScopes();
if ("alice".equals(user)) {
assertEquals(1, scopes.size());
assertTrue(scopes.stream().anyMatch(scope -> "Scope C".equals(scope.getName())));
} else if ("jdoe".equals(user)) {
assertEquals(1, scopes.size());
assertTrue(scopes.stream().anyMatch(scope -> "Scope D".equals(scope.getName())));
}
}
}
private List<AbstractResourceService.ResourcePermission> getSharedWithMe(String userName) {
return doGet("/shared-with-me", authzClient.obtainAccessToken(userName, "password").getToken(),
new TypeReference<List<AbstractResourceService.ResourcePermission>>() {});
}
private <R> R doGet(String resource, TypeReference<R> typeReference) {
return doGet(resource, tokenUtil.getToken(), typeReference);
}
private <R> R doGet(String resource, Class<R> type) {
return doGet(resource, tokenUtil.getToken(), type);
}
private <R> R doGet(String resource, String token, TypeReference<R> typeReference) {
try {
return get(resource, token).asJson(typeReference);
} catch (IOException cause) {
throw new RuntimeException("Failed to fetch resource", cause);
}
}
private <R> R doGet(String resource, String token, Class<R> type) {
try {
return get(resource, token).asJson(type);
} catch (IOException cause) {
throw new RuntimeException("Failed to fetch resource", cause);
}
}
private SimpleHttp get(String resource, String token) {
return SimpleHttp.doGet(getAccountUrl("resources" + resource), httpClient).auth(token);
}
private AuthzClient createAuthzClient(ClientRepresentation client) {
Map<String, Object> credentials = new HashMap<>();
credentials.put("secret", "secret");
return AuthzClient
.create(new Configuration(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth",
testRealm().toRepresentation().getRealm(), client.getClientId(),
credentials, httpClient));
}
private UserRepresentation createUser(String userName, String password) {
return UserBuilder.create()
.username(userName)
.enabled(true)
.password(password)
.role("account", AccountRoles.MANAGE_ACCOUNT)
.build();
}
private List<Resource> getMyResources() {
return doGet("", new TypeReference<List<Resource>>() {});
}
}