updated error handling on introspection and revocation endpoints
parent
35cb14a73f
commit
98fff8fe99
|
@ -31,6 +31,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
@ -58,58 +59,26 @@ public class IntrospectionEndpoint {
|
|||
this.tokenServices = tokenServices;
|
||||
}
|
||||
|
||||
@ExceptionHandler(InvalidTokenException.class)
|
||||
public ModelAndView tokenNotFound(InvalidTokenException ex) {
|
||||
Map<String,Boolean> e = ImmutableMap.of("valid", Boolean.FALSE);
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("entity", e);
|
||||
|
||||
logger.error("InvalidTokenException: " + ex.getStackTrace().toString());
|
||||
|
||||
model.put("code", HttpStatus.BAD_REQUEST);
|
||||
|
||||
return new ModelAndView("jsonEntityView", model);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_CLIENT')")
|
||||
@RequestMapping("/introspect")
|
||||
public ModelAndView verify(@RequestParam("token") String tokenValue, Principal p, ModelAndView modelAndView) {
|
||||
|
||||
/*
|
||||
if (p != null && p instanceof OAuth2Authentication) {
|
||||
OAuth2Authentication auth = (OAuth2Authentication)p;
|
||||
|
||||
if (auth.getDetails() != null && auth.getDetails() instanceof OAuth2AuthenticationDetails) {
|
||||
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)auth.getDetails();
|
||||
|
||||
String tokenValue = details.getTokenValue();
|
||||
|
||||
OAuth2AccessTokenEntity token = tokenServices.readAccessToken(tokenValue);
|
||||
|
||||
if (token != null) {
|
||||
// if it's a valid token, we'll print out the scope and expiration
|
||||
modelAndView.setViewName("tokenIntrospection");
|
||||
modelAndView.addObject("entity", token);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
public String verify(@RequestParam("token") String tokenValue, Principal p, Model model) {
|
||||
|
||||
if (Strings.isNullOrEmpty(tokenValue)) {
|
||||
logger.error("Verify failed; token value is null");
|
||||
modelAndView.addObject("code", HttpStatus.BAD_REQUEST);
|
||||
modelAndView.setViewName("httpCodeView");
|
||||
return modelAndView;
|
||||
Map<String,Boolean> entity = ImmutableMap.of("valid", Boolean.FALSE);
|
||||
model.addAttribute("entity", entity);
|
||||
return "jsonEntityView";
|
||||
}
|
||||
|
||||
OAuth2AccessTokenEntity token = null;
|
||||
|
||||
try {
|
||||
token = tokenServices.readAccessToken(tokenValue);
|
||||
} catch (AuthenticationException e) {
|
||||
} catch (InvalidTokenException e) {
|
||||
logger.error("Verify failed; AuthenticationException: " + e.getStackTrace().toString());
|
||||
modelAndView.addObject("code", HttpStatus.FORBIDDEN);
|
||||
modelAndView.setViewName("httpCodeView");
|
||||
return modelAndView;
|
||||
Map<String,Boolean> entity = ImmutableMap.of("valid", Boolean.FALSE);
|
||||
model.addAttribute("entity", entity);
|
||||
return "jsonEntityView";
|
||||
}
|
||||
|
||||
ClientDetailsEntity tokenClient = token.getClient();
|
||||
|
@ -124,27 +93,23 @@ public class IntrospectionEndpoint {
|
|||
if (authClient.equals(tokenClient) || authClient.getScope().containsAll(token.getScope())) {
|
||||
|
||||
// if it's a valid token, we'll print out information on it
|
||||
modelAndView.setViewName("tokenIntrospection");
|
||||
modelAndView.addObject("entity", token);
|
||||
return modelAndView;
|
||||
model.addAttribute("entity", token);
|
||||
return "tokenIntrospection";
|
||||
} else {
|
||||
logger.error("Verify failed; client tried to introspect a token of an incorrect scope");
|
||||
modelAndView.addObject("code", HttpStatus.BAD_REQUEST);
|
||||
modelAndView.setViewName("httpCodeView");
|
||||
return modelAndView;
|
||||
model.addAttribute("code", HttpStatus.FORBIDDEN);
|
||||
return "httpCodeView";
|
||||
}
|
||||
} else {
|
||||
logger.error("Verify failed; client " + clientId + " is not allowed to call introspection endpoint");
|
||||
modelAndView.addObject("code", HttpStatus.BAD_REQUEST);
|
||||
modelAndView.setViewName("httpCodeView");
|
||||
return modelAndView;
|
||||
model.addAttribute("code", HttpStatus.FORBIDDEN);
|
||||
return "httpCodeView";
|
||||
}
|
||||
} else {
|
||||
//TODO: Log error client not found
|
||||
// This is a bad error -- I think it means we have a token outstanding that doesn't map to a client?
|
||||
logger.error("Verify failed; client " + clientId + " not found.");
|
||||
modelAndView.addObject("code", HttpStatus.NOT_FOUND);
|
||||
modelAndView.setViewName("httpCodeView");
|
||||
return modelAndView;
|
||||
model.addAttribute("code", HttpStatus.NOT_FOUND);
|
||||
return "httpCodeView";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
@ -45,75 +47,66 @@ public class RevocationEndpoint {
|
|||
|
||||
}
|
||||
|
||||
public RevocationEndpoint(OAuth2TokenEntityService tokenServices) {
|
||||
this.tokenServices = tokenServices;
|
||||
}
|
||||
|
||||
// TODO
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
|
||||
@RequestMapping("/revoke")
|
||||
public ModelAndView revoke(@RequestParam("token") String tokenValue, Principal principal,
|
||||
ModelAndView modelAndView) {
|
||||
public String revoke(@RequestParam("token") String tokenValue, Principal principal, Model model) {
|
||||
|
||||
// This is the token as passed in from OAuth (in case we need it some day)
|
||||
//OAuth2AccessTokenEntity tok = tokenServices.getAccessToken((OAuth2Authentication) principal);
|
||||
|
||||
OAuth2RefreshTokenEntity refreshToken = null;
|
||||
OAuth2AccessTokenEntity accessToken = null;
|
||||
try {
|
||||
refreshToken = tokenServices.getRefreshToken(tokenValue);
|
||||
} catch (InvalidTokenException e) {
|
||||
// it's OK if either of these tokens are bad
|
||||
//TODO: Error Handling
|
||||
}
|
||||
|
||||
try {
|
||||
accessToken = tokenServices.readAccessToken(tokenValue);
|
||||
} catch (InvalidTokenException e) {
|
||||
// it's OK if either of these tokens are bad
|
||||
//TODO: Error Handling
|
||||
} catch (AuthenticationException e) {
|
||||
//TODO: Error Handling
|
||||
}
|
||||
|
||||
if (refreshToken == null && accessToken == null) {
|
||||
//TODO: Error Handling
|
||||
// TODO: this should throw a 400 with a JSON error code
|
||||
throw new InvalidTokenException("Invalid OAuth token: " + tokenValue);
|
||||
}
|
||||
|
||||
AuthorizationRequest clientAuth = null;
|
||||
if (principal instanceof OAuth2Authentication) {
|
||||
//TODO what is this variable for? It is unused. is it just a validation check?
|
||||
OAuth2AccessTokenEntity tok = tokenServices.getAccessToken((OAuth2Authentication) principal);
|
||||
// if the client is acting on its own behalf (the common case), pull out the client authorization request
|
||||
clientAuth = ((OAuth2Authentication) principal).getAuthorizationRequest();
|
||||
}
|
||||
|
||||
// we've got a client acting on its own behalf, not an admin
|
||||
//ClientAuthentication clientAuth = (ClientAuthenticationToken) ((OAuth2Authentication) auth).getClientAuthentication();
|
||||
AuthorizationRequest clientAuth = ((OAuth2Authentication) principal).getAuthorizationRequest();
|
||||
|
||||
if (refreshToken != null) {
|
||||
if (!refreshToken.getClient().getClientId().equals(clientAuth.getClientId())) {
|
||||
// trying to revoke a token we don't own, fail
|
||||
// TODO: this should throw a 403
|
||||
//TODO: Error Handling
|
||||
throw new PermissionDeniedException("Client tried to revoke a token it doesn't own");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// check and handle access tokens first
|
||||
|
||||
OAuth2AccessTokenEntity accessToken = tokenServices.readAccessToken(tokenValue);
|
||||
if (clientAuth != null) {
|
||||
// client acting on its own, make sure it owns the token
|
||||
if (!accessToken.getClient().getClientId().equals(clientAuth.getClientId())) {
|
||||
// trying to revoke a token we don't own, fail
|
||||
// TODO: this should throw a 403
|
||||
//TODO: Error Handling
|
||||
throw new PermissionDeniedException("Client tried to revoke a token it doesn't own");
|
||||
// trying to revoke a token we don't own, throw a 403
|
||||
model.addAttribute("code", HttpStatus.FORBIDDEN);
|
||||
return "httpCodeView";
|
||||
}
|
||||
}
|
||||
|
||||
// if we got this far, we're allowed to do this
|
||||
tokenServices.revokeAccessToken(accessToken);
|
||||
model.addAttribute("code", HttpStatus.OK);
|
||||
return "httpCodeView";
|
||||
|
||||
} catch (InvalidTokenException e) {
|
||||
|
||||
// access token wasn't found, check the refresh token
|
||||
|
||||
try {
|
||||
OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(tokenValue);
|
||||
if (clientAuth != null) {
|
||||
// client acting on its own, make sure it owns the token
|
||||
if (!refreshToken.getClient().getClientId().equals(clientAuth.getClientId())) {
|
||||
// trying to revoke a token we don't own, throw a 403
|
||||
model.addAttribute("code", HttpStatus.FORBIDDEN);
|
||||
return "httpCodeView";
|
||||
}
|
||||
}
|
||||
|
||||
// if we got this far, we're allowed to do this
|
||||
tokenServices.revokeRefreshToken(refreshToken);
|
||||
model.addAttribute("code", HttpStatus.OK);
|
||||
return "httpCodeView";
|
||||
|
||||
} catch (InvalidTokenException e1) {
|
||||
|
||||
// neither token type was found, simply say "OK" and be on our way.
|
||||
|
||||
model.addAttribute("code", HttpStatus.OK);
|
||||
return "httpCodeView";
|
||||
}
|
||||
}
|
||||
|
||||
// if we got this far, we're allowed to do this
|
||||
if (refreshToken != null) {
|
||||
tokenServices.revokeRefreshToken(refreshToken);
|
||||
} else {
|
||||
tokenServices.revokeAccessToken(accessToken);
|
||||
}
|
||||
|
||||
// TODO: throw a 200 back (no content?)
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue