package com.dbg.cloud.acheron.plugins.oauth2.endpoints;
import com.dbg.cloud.acheron.adminendpoints.AdminEndpoint;
import com.dbg.cloud.acheron.consumers.Consumer;
import com.dbg.cloud.acheron.consumers.service.ConsumerService;
import com.dbg.cloud.acheron.plugins.oauth2.OAuth2ServerProvider;
import com.dbg.cloud.acheron.plugins.oauth2.authserver.base.OAuth2AuthorisationServer;
import com.dbg.cloud.acheron.plugins.oauth2.authserver.base.management.client.creation.ClientCreationOperation;
import com.dbg.cloud.acheron.plugins.oauth2.authserver.base.management.client.creation.ClientCreationResult;
import com.dbg.cloud.acheron.plugins.oauth2.authserver.base.management.client.deletion.ClientDeletionOperation;
import com.dbg.cloud.acheron.plugins.oauth2.authserver.base.management.client.deletion.ClientDeletionResult;
import com.dbg.cloud.acheron.plugins.oauth2.store.OAuth2Client;
import com.dbg.cloud.acheron.plugins.oauth2.store.OAuth2Store;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/admin")
@AllArgsConstructor
@Slf4j
final class OAuth2ClientController implements AdminEndpoint {
private final OAuth2Store oAuth2Store;
private final ConsumerService consumerService;
private final OAuth2ServerProvider oAuth2ServerProvider;
@RequestMapping(value = "/consumers/{consumerId}/oauth2-clients", method = RequestMethod.GET)
@JsonView(value = View.Read.class)
public List<OAuth2ClientTO> readOAuth2ClientsOfConsumer(final @PathVariable String consumerId) {
final UUID uuidConsumerId = parseUUID(consumerId).orElseThrow(() -> new BadRequestException());
final List<OAuth2Client> clientList = oAuth2Store.findByConsumer(uuidConsumerId);
return clientList.stream().map(client -> new OAuth2ClientTO(client)).collect(Collectors.toList());
}
@RequestMapping(value = "/consumers/{consumerId}/oauth2-clients/{id}", method = RequestMethod.GET)
@JsonView(value = View.Read.class)
public ResponseEntity<?> readOAuth2ClientOfConsumer(final @PathVariable String consumerId,
final @PathVariable String id) {
final UUID uuidConsumerId = parseUUID(consumerId).orElseThrow(() -> new ConsumerNotFoundException(consumerId));
final UUID uuid = parseUUID(id).orElseThrow(() -> new OAuth2ClientNotFoundException(id));
final Optional<OAuth2Client> optionalClient = oAuth2Store.findById(uuid);
final OAuth2Client client = optionalClient.orElseThrow(() -> new OAuth2ClientNotFoundException(id));
if (!uuidConsumerId.equals(client.getConsumerId())) {
throw new ConsumerNotFoundException(consumerId);
}
return ResponseEntity.ok(new OAuth2ClientTO(client));
}
@RequestMapping(value = "/consumers/{consumerId}/oauth2-clients", method = RequestMethod.POST)
@JsonView(value = View.ReadRegister.class)
public ResponseEntity<?> addOAuth2ClientToConsumer(final @PathVariable String consumerId,
final @JsonView(View.Register.class) @RequestBody
OAuth2ClientTO oauth2Client) {
if (!validateRegistration(oauth2Client)) {
return ResponseEntity.badRequest().build();
}
final UUID uuidConsumerId = parseUUID(consumerId).orElseThrow(() -> new ConsumerNotFoundException(consumerId));
final Consumer consumer = consumerService.getConsumer(uuidConsumerId).orElseThrow(
() -> new ConsumerNotFoundException(consumerId));
// Create in Authorisation Server
// TODO Change hardcoded realm when the concept of realms is clear
final OAuth2AuthorisationServer oauth2Server = oAuth2ServerProvider.authorisationServerOfRealm("realm1");
Optional<ClientCreationResult> optionalResult = Optional.empty();
try {
optionalResult = Optional.ofNullable(
oauth2Server.clientCreationSpec(oauth2Server.authenticationSpec().operation())
.operationForClient(oauth2Client).result());
} catch (ClientCreationOperation.TechnicalException e) {
log.error("Error occurred while trying to create client", e);
}
final ResponseEntity<?> response;
if (optionalResult.isPresent()) {
final ClientCreationResult result = optionalResult.get();
if (result.clientId() != null && !result.clientId().trim().isEmpty() &&
result.clientSecret() != null && !result.clientSecret().trim().isEmpty()) {
// Store Client in DB
final OAuth2Client client = oAuth2Store.add(new OAuth2Client.ForCreation(result.clientId(),
consumer.getId(), consumer.getName(), consumer.getCreatedAt()));
// FIXME Handle storage failure, i.e. call auth server to delete client
response = ResponseEntity
.status(HttpStatus.CREATED)
.body(new OAuth2ClientTO(client, result.clientSecret()));
} else {
response = ResponseEntity.badRequest().build();
}
} else {
response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return response;
}
@RequestMapping(value = "/consumers/{consumerId}/oauth2-clients/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteOAuth2ClientOfConsumer(final @PathVariable String consumerId,
final @PathVariable String id) {
final ResponseEntity<?> response;
final UUID uuidConsumerId = parseUUID(consumerId).orElseThrow(() -> new ConsumerNotFoundException(consumerId));
final UUID uuid = parseUUID(id).orElseThrow(() -> new OAuth2ClientNotFoundException(id));
// Read oauth2 client from DB to get client id
final OAuth2Client oAuth2Client = oAuth2Store.findById(uuid).orElseThrow(
() -> new OAuth2ClientNotFoundException(id));
if (uuidConsumerId.equals(oAuth2Client.getConsumerId())) {
response = delete(oAuth2Client);
} else {
throw new OAuth2ClientNotFoundException(id);
}
return response;
}
@RequestMapping(value = "/plugins/oauth2/clients/{id}", method = RequestMethod.GET)
@JsonView(value = View.Read.class)
public ResponseEntity<?> readOAuth2Client(final @PathVariable String id) {
final UUID uuid = parseUUID(id).orElseThrow(() -> new OAuth2ClientNotFoundException(id));
final Optional<OAuth2Client> optionalClient = oAuth2Store.findById(uuid);
return ResponseEntity.ok(new OAuth2ClientTO(
optionalClient.orElseThrow(() -> new OAuth2ClientNotFoundException(id))));
}
@RequestMapping(value = "/plugins/oauth2/clients/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteOAuth2Client(final @PathVariable String id) {
final UUID uuid = parseUUID(id).orElseThrow(() -> new OAuth2ClientNotFoundException(id));
// Read oauth2 client from DB to get client id
final OAuth2Client oAuth2Client = oAuth2Store.findById(uuid).orElseThrow(
() -> new OAuth2ClientNotFoundException(id));
return delete(oAuth2Client);
}
private ResponseEntity<?> delete(final OAuth2Client oAuth2Client) {
final ResponseEntity<?> response;
// Delete in Authorisation Server
// TODO Change hardcoded realm when the concept of realms is clear
final OAuth2AuthorisationServer oauth2Server = oAuth2ServerProvider.authorisationServerOfRealm("realm1");
Optional<ClientDeletionResult> optionalResult = Optional.empty();
try {
optionalResult = Optional.ofNullable(
oauth2Server.clientDeletionSpec(oauth2Server.authenticationSpec().operation())
.operationForClient(oAuth2Client.getClientId())
.result());
} catch (ClientDeletionOperation.TechnicalException e) {
log.error("Error occurred while trying to delete client", e);
}
if (optionalResult.isPresent()) {
final ClientDeletionResult result = optionalResult.get();
if (result.isOk()) {
// Delete in DB
oAuth2Store.deleteById(oAuth2Client.getId());
// FIXME Handle storage failure, i.e. call auth server to delete client
response = ResponseEntity.noContent().build();
} else {
response = ResponseEntity.badRequest().build();
}
} else {
response = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return response;
}
private boolean validateRegistration(final OAuth2ClientTO oauth2Client) {
// This will be revisited when it makes sense
return true;
}
private Optional<UUID> parseUUID(final String candidateUUID) {
UUID uuid = null;
try {
uuid = UUID.fromString(candidateUUID);
} catch (Exception e) {
log.info("Passed id is not a UUID {}", candidateUUID);
}
return Optional.ofNullable(uuid);
}
@ResponseStatus(HttpStatus.NOT_FOUND)
class ConsumerNotFoundException extends RuntimeException {
public ConsumerNotFoundException(final String consumerId) {
super("Could not find consumer '" + consumerId + "'");
}
}
@ResponseStatus(HttpStatus.NOT_FOUND)
class OAuth2ClientNotFoundException extends RuntimeException {
public OAuth2ClientNotFoundException(final String clientId) {
super("Could not find oauth2 client '" + clientId + "'");
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
class BadRequestException extends RuntimeException {
}
}