/*
* 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.surfnet.oaaas.model.Client;
import org.surfnet.oaaas.model.ResourceServer;
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.Collections;
/**
* JAX-RS Resource for CRUD operations on Clients. (clients in OAuth 2 context).
*/
@Named
@Path("/resourceServer/{resourceServerId}/client")
@Produces(MediaType.APPLICATION_JSON)
public class ClientResource extends AbstractResource {
private static final Logger LOG = LoggerFactory.getLogger(ClientResource.class);
private static final String FILTERED_CLIENT_ID_CHARS = "[^a-z0-9_\\x2D]";
@Inject
private ClientRepository clientRepository;
@Inject
private ResourceServerRepository resourceServerRepository;
/**
* Get a list of all clients linked to the given resourceServer.
*/
@GET
public Response getAll(@Context HttpServletRequest request,
@PathParam("resourceServerId") Long resourceServerId) {
Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ));
if (validateScopeResponse != null) {
return validateScopeResponse;
}
ResourceServer resourceServer = getResourceServer(request, resourceServerId);
Iterable<Client> clients = clientRepository.findByResourceServer(resourceServer);
return response(addAll(clients.iterator()));
}
/**
* Get a specific Client.
*/
@GET
@Path("/{clientId}")
public Response getById(@Context HttpServletRequest request,
@PathParam("resourceServerId") Long resourceServerId,
@PathParam("clientId") Long id) {
Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_READ));
if (validateScopeResponse != null) {
return validateScopeResponse;
}
Client client = getClientByResourceServer(request, id, resourceServerId);
return response(client);
}
/**
* Save a new client.
*/
@PUT
public Response put(@Context HttpServletRequest request,
@PathParam("resourceServerId") Long resourceServerId, Client client) {
Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE));
if (validateScopeResponse != null) {
return validateScopeResponse;
}
ResourceServer resourceServer = getResourceServer(request, resourceServerId);
client.setResourceServer(resourceServer);
client.setClientId(generateClientId(client));
client.setSecret(client.isAllowedImplicitGrant() ? null : generateSecret());
Client clientSaved;
try {
clientSaved = clientRepository.save(client);
} catch (RuntimeException e) {
return buildErrorResponse(e);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Saved client: {}", clientSaved);
}
final URI uri = UriBuilder.fromPath("{clientId}.json").build(clientSaved.getId());
return Response.created(uri).entity(clientSaved).build();
}
protected String generateSecret() {
return super.generateRandom();
}
/**
* Delete a given client.
*/
@DELETE
@Path("/{clientId}")
public Response delete(@Context HttpServletRequest request,
@PathParam("clientId") Long id,
@PathParam("resourceServerId") Long resourceServerId) {
Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE));
if (validateScopeResponse != null) {
return validateScopeResponse;
}
Client client = getClientByResourceServer(request, id, resourceServerId);
if (client == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
if (LOG.isDebugEnabled()) {
LOG.debug("Deleting client: {}", client);
}
clientRepository.delete(id);
return Response.noContent().build();
}
/**
* Update an existing client.
*/
@POST
@Path("/{clientId}")
public Response post(@Valid Client newOne, @PathParam("clientId") Long id,
@Context HttpServletRequest request,
@PathParam("resourceServerId") Long resourceServerId
) {
Response validateScopeResponse = validateScope(request, Collections.singletonList(AbstractResource.SCOPE_WRITE));
if (validateScopeResponse != null) {
return validateScopeResponse;
}
ResourceServer resourceServer = getResourceServer(request, resourceServerId);
final Client clientFromStore = clientRepository.findByIdAndResourceServer(id, resourceServer);
if (clientFromStore == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
// Copy over read-only fields
newOne.setResourceServer(resourceServer);
newOne.setClientId(clientFromStore.getClientId());
newOne.setSecret(newOne.isAllowedImplicitGrant() ? null : clientFromStore.getSecret());
Client savedInstance;
try {
savedInstance = clientRepository.save(newOne);
} catch (RuntimeException e) {
return buildErrorResponse(e);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Saving client: {}", savedInstance);
}
return Response.ok(savedInstance).build();
}
/**
* Method that generates a unique client id, taking into account existing clientIds in the backend.
*
* @param client the client for whom to generate an id.
* @return the generated id. Callers are responsible themselves for actually calling {@link Client#setClientId(String)}
*/
protected String generateClientId(Client client) {
String clientId = sanitizeClientName(client.getName());
if (clientRepository.findByClientId(clientId) != null) {
String baseClientId = clientId;
/* if one with such name exists already, the next one would actually be number 2. Therefore,
* start counting with 2.
*/
int i = 2;
do {
clientId = baseClientId + (i++);
} while (clientRepository.findByClientId(clientId) != null);
}
return clientId;
}
protected String sanitizeClientName(String name) {
return name.toLowerCase().replaceAll(" ", "-").replaceAll(FILTERED_CLIENT_ID_CHARS, "");
}
private Client getClientByResourceServer(HttpServletRequest request, Long clientId, Long resourceServerId) {
ResourceServer resourceServer = getResourceServer(request, resourceServerId);
return clientRepository.findByIdAndResourceServer(clientId, resourceServer);
}
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);
}
return resourceServer;
}
}