Commit 1366e2c0 authored by Kateryna Kliushnyk's avatar Kateryna Kliushnyk Committed by Matija Obreza

Tokens management finished

Conflicts:
	crophub_oauth_client/src/main/java/org/crophub/auth/client/CropHubClient.java
	crophub_server_oauth/src/main/java/org/crophub/auth/server/utils/ServiceEndpoints.java
	crophub_server_oauth/src/main/resources/spring/auth-servlet.xml
	crophub_server_oauth/src/main/resources/spring/spring-security.xml
parent e06513fa
package org.crophub.auth.server.controller;
import org.crophub.auth.server.exception.AuthorizationException;
import org.crophub.auth.server.exception.NoSuchTokenException;
import org.crophub.auth.server.message.SimpleMessage;
import org.crophub.auth.server.utils.ServiceEndpoints;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping(value = "/api/v0")
public class TokenController extends BaseController {
@Autowired
private ConsumerTokenServices tokenServices;
private PasswordEncoder encoder = new StandardPasswordEncoder();
@RequestMapping(
value = ServiceEndpoints.LIST_USER_TOKENS,
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE}
)
@ResponseBody
public
Collection<OAuth2AccessToken> listTokensForUser(@PathVariable String username) throws Exception {
if (isAuthenticated()) {
OAuth2Authentication principal = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
checkResourceOwner(username, principal);
return enhance(tokenServices.findTokensByUserName(username));
} else {
throw new AuthorizationException(EXCEPTION_NOT_AUTHORIZED);
}
}
@RequestMapping(
value = ServiceEndpoints.REVOKE_USER_TOKEN,
method = RequestMethod.DELETE,
produces = {MediaType.APPLICATION_JSON_VALUE}
)
@ResponseBody
public SimpleMessage revokeUserToken(@PathVariable String username,
@PathVariable String token) throws Exception {
if (isAuthenticated()){
OAuth2Authentication principal = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
checkResourceOwner(username, principal);
String tokenValue = getTokenValue(tokenServices.findTokensByUserName(username), token);
if (tokenValue != null && tokenServices.revokeToken(tokenValue)) {
return new SimpleMessage("ok", "user token revoked");
}
throw new NoSuchTokenException("Token not found");
} else {
throw new AuthorizationException(EXCEPTION_NOT_AUTHORIZED);
}
}
@RequestMapping(
value = ServiceEndpoints.LIST_CLIENT_TOKEN,
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE}
)
@ResponseBody
public Collection<OAuth2AccessToken> listTokensForClient(@PathVariable String client)
throws Exception {
if (isAuthenticated()){
OAuth2Authentication principal = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
checkClient(client, principal);
return enhance(tokenServices.findTokensByClientId(client));
} else {
throw new AuthorizationException(EXCEPTION_NOT_AUTHORIZED);
}
}
@RequestMapping(
value = ServiceEndpoints.REVOKE_CLIENT_TOKEN,
method = RequestMethod.DELETE,
produces = {MediaType.APPLICATION_JSON_VALUE}
)
@ResponseBody
public SimpleMessage revokeClientToken(@PathVariable String client, @PathVariable String token)
throws Exception {
if (isAuthenticated()){
OAuth2Authentication principal = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
checkClient(client, principal);
String tokenValue = getTokenValue(tokenServices.findTokensByClientId(client), token);
if (tokenValue != null && tokenServices.revokeToken(tokenValue)) {
return new SimpleMessage("ok", "client token revoked");
}
throw new NoSuchTokenException("Token not found");
} else {
throw new AuthorizationException(EXCEPTION_NOT_AUTHORIZED);
}
}
@ExceptionHandler(NoSuchTokenException.class)
public ResponseEntity<Void> handleNoSuchToken(NoSuchTokenException e) {
return new ResponseEntity<Void>(HttpStatus.NOT_FOUND);
}
private String getTokenValue(Collection<OAuth2AccessToken> tokens, String hash) {
for (OAuth2AccessToken token : tokens) {
try {
return token.getValue();
} catch (Exception e) {
// it doesn't match
}
}
return null;
}
private Collection<OAuth2AccessToken> enhance(Collection<OAuth2AccessToken> tokens) {
Collection<OAuth2AccessToken> result = new ArrayList<OAuth2AccessToken>();
for (OAuth2AccessToken prototype : tokens) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(prototype);
Map<String, Object> map = new HashMap<String, Object>(token.getAdditionalInformation());
// The token doesn't have an ID in the token service, but we need one for the endpoint, so add one here
map.put(token.getTokenType(), encoder.encode(token.getValue()));
try {
String clientId = tokenServices.getClientId(token.getValue());
if (clientId != null) {
map.put("client_id", clientId);
}
} catch (InvalidTokenException e) {
// Ignore defensively in case of bugs in token services
}
token.setAdditionalInformation(map);
result.add(token);
}
return result;
}
private void checkResourceOwner(String user, Principal principal) {
if (principal instanceof OAuth2Authentication) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
if (!authentication.isClientOnly() && !user.equals(principal.getName())) {
throw new AccessDeniedException(String.format("User '%s' cannot obtain tokens for user '%s'",
principal.getName(), user));
}
} else if (!user.equals(principal.getName())) {
throw new AccessDeniedException(String.format("User '%s' cannot obtain tokens for user '%s'",
principal.getName(), user));
}
}
private void checkClient(String client, Principal principal) {
if (principal instanceof OAuth2Authentication) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
if (!authentication.isClientOnly() || !client.equals(principal.getName()) && !isAdmin(principal)) {
throw new AccessDeniedException(String.format("Client '%s' cannot obtain tokens for client '%s'",
principal.getName(), client));
}
}
}
private boolean isAdmin(Principal principal) {
return AuthorityUtils.authorityListToSet(((Authentication) principal).getAuthorities()).contains("USER_ADMIN");
}
/**
* @param tokenServices the consumerTokenServices to set
*/
public void setTokenServices(ConsumerTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
}
package org.crophub.auth.server.exception;
public class NoSuchTokenException extends RuntimeException {
public NoSuchTokenException(String message) {
super(message);
}
}
\ No newline at end of file
package org.crophub.auth.server.message;
import java.io.Serializable;
public class SimpleMessage implements Serializable {
private String status;
private String message;
@SuppressWarnings("unused")
private SimpleMessage() {
}
public SimpleMessage(String status, String message) {
this.status = status;
this.message = message;
}
public String getStatus() {
return status;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return "{\"status\"=\"" + status + "\",\"message\"=\"" + message + "\"}";
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof SimpleMessage && toString().equals(obj.toString());
}
}
\ No newline at end of file
package org.crophub.auth.server.utils;
public interface ServiceEndpoints {
/*===============================================================*/
/*==================== Service endpoint /me =====================*/
/*===============================================================*/
//Retrieve current User profile
String ME = "/me";
//List Organizations that current User belongs to
String MY_ORGANIZATIONS = "/me/organizations";
//List DataSets owned by current User
String MY_DATA_SETS = "/me/datasets";
//List Traits owned by current User
String MY_TRAITS = "/me/traits";
/*===============================================================*/
/*=============== Service endpoint /organizations ===============*/
/*===============================================================*/
//Lists all Traits associated with Organization $O
String ORGANIZATION_TRAITS = "/organizations/{organizationId}/traits";
//Lists all DataSets associated with Organization $O
String ORGANIZATION_DATA_SETS = "/organizations/{organizationId}/datasets";
//List all Licenses associated with Organization $O
String ORGANIZATION_LICENSES = "/organizations/{organizationId}/licenses";
//Issue a request for current User to join Organization $O
String JOIN_ORGANIZATION = "/organizations/{organizationId}/join";
/*===============================================================*/
/*=============== Service endpoint /traits ======================*/
/*===============================================================*/
//Lists all Traits available to Current User
String TRAITS = "/traits";
//Remove Trait
String REMOVE_TRAIT = "/traits/{traitId}";
/*===============================================================*/
/*=============== Service endpoint /licenses ====================*/
/*===============================================================*/
//Lists all Licenses available to Current User
String LICENSES = "/licenses";
//Remove License {licenseId}
String REMOVE_LICENSE = "/licenses/{licenseId}";
/*===============================================================*/
/*=============== Service endpoint /datasets ====================*/
/*===============================================================*/
//List available DataSets
String DATASETS = "/datasets";
//Remove data set {dataSetId}
String REMOVE_DATASET = "/datasets/{dataSetId}";
//Get all Entries of DataSet {dataSetId}
String DATASET_ENTRIES = "/datasets/{dataSetId}/data";
//Update Entry {dataEntryId} in DataSet {dataSetId}
String UPDATE_DATASET_ENTRY = "/datasets/{dataSetId}/data/{dataEntryId}";
//Delete Entry {dataEntryId} from DataSet {dataSetId}
String REMOVE_DATASET_ENTRY = "/datasets/{dataSetId}/data/{dataEntryId}";
/*===============================================================*/
/*=============== Service endpoint /tokens ====================*/
/*===============================================================*/
//List available Tokens
String LIST_USER_TOKENS = "/users/{username}/list/tokens";
String REVOKE_USER_TOKEN = "/users/{username}/tokens/revoke/{token}";
String LIST_CLIENT_TOKEN = "/clients/{client}/list/tokens";
String REVOKE_CLIENT_TOKEN = "/clients/{client}/tokens/revoke/{token}";
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment