/* * Copyright 2012 SURFnet bv, The Netherlands * * 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.surfnet.oaaas.resource.resourceserver; import org.apache.commons.collections.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.annotation.Transactional; import org.surfnet.oaaas.auth.AuthorizationServerFilter; import org.surfnet.oaaas.model.Client; import org.surfnet.oaaas.model.ResourceServer; import org.surfnet.oaaas.model.StatisticsResponse; import org.surfnet.oaaas.model.StatisticsResponse.ClientStat; import org.surfnet.oaaas.model.StatisticsResponse.ResourceServerStat; import org.surfnet.oaaas.model.VerifyTokenResponse; import org.surfnet.oaaas.repository.AccessTokenRepository; import org.surfnet.oaaas.repository.ClientRepository; import org.surfnet.oaaas.repository.ResourceServerRepository; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import java.net.URI; import java.util.*; import static org.apache.commons.collections.CollectionUtils.subtract; /** * JAX-RS Resource for resource servers. */ @Named @Path("/resourceServer") @Produces(MediaType.APPLICATION_JSON) @Transactional public class ResourceServerResource extends AbstractResource { private static final Logger LOG = LoggerFactory.getLogger(ResourceServerResource.class); @Inject private ResourceServerRepository resourceServerRepository; @Inject private AccessTokenRepository accessTokenRepository; @Inject private ClientRepository clientRepository; /** * Get all existing resource servers for the provided credentials (== owner) or in case of an adminPrincipal we return all resource servers. */ @GET public Response getAll(@Context HttpServletRequest request) { Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); if (validateScopeResponse != null) { return validateScopeResponse; } List<ResourceServer> resourceServers = getAllResourceServers(request); return Response.ok(resourceServers).build(); } /** * Get one resource server. */ @GET @Path("/{resourceServerId}") public Response getById(@Context HttpServletRequest request, @PathParam("resourceServerId") Long id) { Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ)); if (validateScopeResponse != null) { return validateScopeResponse; } return response(getResourceServer(request, id)); } /** * Get statistics */ @GET @Path("/stats") public Response stats(@Context HttpServletRequest request) { Iterable<ResourceServer> resourceServers = this.getAllResourceServers(request); List<ResourceServerStat> resourceServerStats = new ArrayList<StatisticsResponse.ResourceServerStat>(); for (ResourceServer resourceServer : resourceServers) { List<ClientStat> clientStats = new ArrayList<StatisticsResponse.ClientStat>(); Set<Client> clients = resourceServer.getClients(); for (Client client : clients) { clientStats.add(new StatisticsResponse.ClientStat(client.getName(), client.getDescription(), accessTokenRepository.countByUniqueResourceOwnerIdAndClientId(client.getId()))); } resourceServerStats.add(new StatisticsResponse.ResourceServerStat(resourceServer.getName(), resourceServer .getDescription(), clientStats)); } return Response.ok(new StatisticsResponse(resourceServerStats)).build(); } /** * Get the principal */ @GET @Path("/principal") public Response principal(@Context HttpServletRequest request) { VerifyTokenResponse verifyTokenResponse = (VerifyTokenResponse) request.getAttribute(AuthorizationServerFilter.VERIFY_TOKEN_RESPONSE); return Response.ok(verifyTokenResponse.getPrincipal()).build(); } /** * Save a new resource server. */ @PUT public Response put(@Context HttpServletRequest request, @Valid ResourceServer newOne) { Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); if (validateScopeResponse != null) { return validateScopeResponse; } String owner = getUserId(request); // Read only fields newOne.setKey(generateKey()); newOne.setSecret(generateSecret()); newOne.setOwner(owner); ResourceServer resourceServerSaved; try { //we run transactional modus, so any constraint violations only occur after the commit of the transaction (to late...) validate(newOne); resourceServerSaved = resourceServerRepository.save(newOne); } catch (Exception e) { return buildErrorResponse(e); } LOG.debug("New resourceServer has been saved: {}. ", resourceServerSaved); final URI uri = UriBuilder.fromPath("{resourceServerId}.json").build(resourceServerSaved.getId()); return Response.created(uri).entity(resourceServerSaved).build(); } /** * Delete an existing resource server. */ @DELETE @Path("/{resourceServerId}") public Response delete(@Context HttpServletRequest request, @PathParam("resourceServerId") Long id) { Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); if (validateScopeResponse != null) { return validateScopeResponse; } ResourceServer resourceServer = getResourceServer(request, id); if (resourceServer == null) { return Response.status(Response.Status.NOT_FOUND).build(); } LOG.debug("About to delete resourceServer {}", id); resourceServerRepository.delete(id); return Response.noContent().build(); } /** * Update an existing resource server. */ @POST @Path("/{resourceServerId}") public Response post(@Valid final ResourceServer resourceServer, @Context HttpServletRequest request, @PathParam("resourceServerId") Long id) { Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE)); if (validateScopeResponse != null) { return validateScopeResponse; } ResourceServer persistedResourceServer = getResourceServer(request, id); if (persistedResourceServer == null) { return Response.status(Response.Status.NOT_FOUND).build(); } // Copy over read-only fields resourceServer.setSecret(persistedResourceServer.getSecret()); resourceServer.setKey(persistedResourceServer.getKey()); resourceServer.setOwner(getUserId(request)); pruneClientScopes(resourceServer.getScopes(), persistedResourceServer.getScopes(), persistedResourceServer.getClients()); LOG.debug("About to update existing resourceServer {} with new properties: {}", persistedResourceServer, resourceServer); ResourceServer savedInstance; try { //we run transactional modus, so any constraint violations only occur after the commit of the transaction (to late...) validate(resourceServer); savedInstance = resourceServerRepository.save(resourceServer); } catch (Exception e) { return buildErrorResponse(e); } return Response.ok(savedInstance).build(); } /** * Delete all scopes from clients that are not valid anymore with the new * resource server * * @param newScopes the newly saved scopes * @param oldScopes the scopes from the existing resource server * @param clients the clients of the resource server */ @SuppressWarnings("unchecked") protected void pruneClientScopes(final List<String> newScopes, List<String> oldScopes, Set<Client> clients) { if (!newScopes.containsAll(oldScopes)) { subtract(oldScopes, newScopes); Collection<String> outdatedScopes = subtract(oldScopes, newScopes); LOG.info("Resource server has updated scopes. Will remove all outdated scopes from clients: {}", outdatedScopes); for (Client c : clients) { final List<String> clientScopes = c.getScopes(); if (CollectionUtils.containsAny(clientScopes, outdatedScopes)) { ArrayList<String> prunedScopes = new ArrayList<String>(subtract(clientScopes, outdatedScopes)); LOG.info("Client scopes of client {} were: {}. After pruning (because resourceServer has new scopes): {}", new Object[]{c.getClientId(), c.getScopes(), prunedScopes}); c.setScopes(prunedScopes); } } } } protected String generateKey() { return super.generateRandom(); } protected String generateSecret() { return super.generateRandom(); } private ResourceServer getResourceServer(HttpServletRequest request, Long id) { ResourceServer resourceServer; if (isAdminPrincipal(request)) { resourceServer = resourceServerRepository.findOne(id); } else { String owner = getUserId(request); resourceServer = resourceServerRepository.findByIdAndOwner(id, owner); } LOG.debug("About to return one resourceServer with id {}: {}", id, resourceServer); return resourceServer; } private List<ResourceServer> getAllResourceServers(HttpServletRequest request) { List<ResourceServer> resourceServers; if (isAdminPrincipal(request)) { resourceServers = addAll(resourceServerRepository.findAll().iterator()); LOG.debug("About to return all resource servers ({}) for adminPrincipal", resourceServers.size()); } else { String owner = getUserId(request); resourceServers = resourceServerRepository.findByOwner(owner); LOG.debug("About to return all resource servers ({}) for owner {}", resourceServers.size(), owner); } return resourceServers; } }