From 8b096c6edd9e2a2d8d5902799a5d41b283f08c29 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 07:13:40 -0600 Subject: [PATCH 01/11] refactor: switch from Apache OLTU to nimbusds for OAuth request flow for remote projects and galaxy --- build.gradle.kts | 4 +- .../security/IridaApiSecurityConfig.java | 7 - .../IridaOAuthProblemException.java | 9 ++ .../errors/IridaCustomExceptionHandler.java | 8 +- .../GalaxyRedirectionEndpointController.java | 49 ++++--- .../oauth/OltuAuthorizationController.java | 75 ++++++---- .../ria/web/oauth/RemoteAPIController.java | 4 +- .../irida/service/RemoteAPITokenService.java | 31 ++--- .../impl/RemoteAPITokenServiceImpl.java | 129 ++++++++++-------- .../OltuAuthorizationControllerTest.java | 76 ++++++++--- .../web/oauth/RemoteAPIControllerTest.java | 3 +- .../unit/RemoteAPITokenServiceImplTest.java | 57 ++++---- 12 files changed, 259 insertions(+), 193 deletions(-) create mode 100644 src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java diff --git a/build.gradle.kts b/build.gradle.kts index 0e8305951a3..e1b573f3bab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -123,9 +123,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.security:spring-security-oauth2-authorization-server:0.3.1") implementation("org.springframework.security:spring-security-oauth2-resource-server:5.7.3") - implementation("org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.0") { - exclude(group = "org.slf4j") - } + implementation("com.nimbusds:oauth2-oidc-sdk:10.1") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.data:spring-data-envers") { exclude(group = "org.slf4j") diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/config/security/IridaApiSecurityConfig.java b/src/main/java/ca/corefacility/bioinformatics/irida/config/security/IridaApiSecurityConfig.java index 79727411cf2..5a16cfa6202 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/config/security/IridaApiSecurityConfig.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/config/security/IridaApiSecurityConfig.java @@ -2,8 +2,6 @@ import java.util.List; -import org.apache.oltu.oauth2.client.OAuthClient; -import org.apache.oltu.oauth2.client.URLConnectionClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -131,11 +129,6 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Bean - public OAuthClient oAuthClient() { - return new OAuthClient(new URLConnectionClient()); - } - /** * Default {@link DefaultWebSecurityExpressionHandler}. This is used by Thymeleaf's Spring Security plugin, and * isn't actually used anywhere in the back-end, but it needs to be in the back-end configuration classes because diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java b/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java new file mode 100644 index 00000000000..ca800e9e40c --- /dev/null +++ b/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java @@ -0,0 +1,9 @@ +package ca.corefacility.bioinformatics.irida.exceptions; + +public class IridaOAuthProblemException extends RuntimeException { + + public IridaOAuthProblemException(String message) { + super(message); + } + +} diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java index dd1ed43f888..9a6c333d80e 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java @@ -7,7 +7,6 @@ import javax.servlet.http.HttpServletResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -23,6 +22,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import ca.corefacility.bioinformatics.irida.exceptions.EntityNotFoundException; +import ca.corefacility.bioinformatics.irida.exceptions.IridaOAuthProblemException; import ca.corefacility.bioinformatics.irida.exceptions.StorageException; import ca.corefacility.bioinformatics.irida.service.EmailController; @@ -76,14 +76,14 @@ public void handleAccessDeniedException(AccessDeniedException ex, HttpServletRes } /** - * Catch an {@link OAuthProblemException} and return an http 500 error + * Catch an {@link IridaOAuthProblemException} and return an http 500 error * * @param ex the caught {@link OAuthProblemException} * @return A {@link ModelAndView} containing the name of the oauth error view */ - @ExceptionHandler(OAuthProblemException.class) + @ExceptionHandler(IridaOAuthProblemException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ModelAndView handleOAuthProblemException(OAuthProblemException ex) { + public ModelAndView handleOAuthProblemException(IridaOAuthProblemException ex) { logger.error("OAuth exception: " + ex.getMessage(), ex); ModelAndView modelAndView = new ModelAndView(OAUTH_ERROR_PAGE); diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java index 70135e877db..bc548bf8efc 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java @@ -3,19 +3,20 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; +import com.nimbusds.oauth2.sdk.ParseException; +import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; +import com.nimbusds.openid.connect.sdk.AuthenticationResponse; +import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; + /** * Controller for handling OAuth2 authorization codes for the Galaxy exporter - * - * - * */ @Controller @@ -23,39 +24,47 @@ public class GalaxyRedirectionEndpointController { private static final Logger logger = LoggerFactory.getLogger(GalaxyRedirectionEndpointController.class); + public static final String GALAXY_OAUTH_REDIRECT = "/galaxy/auth_code"; + /** * Receive the OAuth2 authorization code from IRIDA and pass it on to the client-side code - * @param model - * the model to write to - * @param request - * the incoming request - * @param session - * the user's session + * + * @param model the model to write to + * @param request the incoming request + * @param session the user's session * @return a template that will pass on the authorization code * @throws OAuthProblemException if a valid OAuth authorization response cannot be created * @throws IllegalStateException if the callback URL is removed from an invalid session + * @throws ParseException */ - @RequestMapping("galaxy/auth_code") - public String passAuthCode(Model model, HttpServletRequest request, HttpSession session - ) throws OAuthProblemException, IllegalStateException { + @RequestMapping(GALAXY_OAUTH_REDIRECT) + public String passAuthCode(Model model, HttpServletRequest request, HttpSession session) + throws IllegalStateException, ParseException { logger.debug("Parsing auth code from HttpServletRequest"); + // Get the OAuth2 authorization code - OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request); - String code = oar.getCode(); + AuthenticationResponse authResponse = AuthenticationResponseParser + .parse(new ServletServerHttpRequest(request).getURI()); + + if (authResponse instanceof AuthenticationErrorResponse) { + logger.trace("Unexpected authentication response"); + } + + String code = authResponse.toSuccessResponse().getAuthorizationCode().getValue(); model.addAttribute("auth_code", code); - + session.removeAttribute("galaxyExportToolCallbackURL"); - + return "templates/galaxy_auth_code.tmpl"; } /** - * Get the URL for the galaxy redirection location. This will be needed for the oauth flow to get its token. + * Get the URL for the galaxy redirection location. This will be needed for the oauth flow to get its token. * * @param baseURL The server's base URL * @return the URL of the galaxy oauth redirect location. */ public static String getGalaxyRedirect(String baseURL) { - return baseURL + "/galaxy/auth_code"; + return baseURL + GALAXY_OAUTH_REDIRECT; } } \ No newline at end of file diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java index ba8a11fa186..cb710d7711f 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java @@ -1,6 +1,8 @@ package ca.corefacility.bioinformatics.irida.ria.web.oauth; +import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -10,23 +12,27 @@ import javax.servlet.http.HttpSession; import javax.ws.rs.core.UriBuilder; -import org.apache.oltu.oauth2.client.request.OAuthClientRequest; -import org.apache.oltu.oauth2.client.response.OAuthAuthzResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.types.ResponseType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import ca.corefacility.bioinformatics.irida.exceptions.IridaOAuthProblemException; import ca.corefacility.bioinformatics.irida.model.RemoteAPI; import ca.corefacility.bioinformatics.irida.service.RemoteAPIService; import ca.corefacility.bioinformatics.irida.service.RemoteAPITokenService; +import com.nimbusds.oauth2.sdk.*; +import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.id.State; +import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; +import com.nimbusds.openid.connect.sdk.AuthenticationResponse; +import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; + /** * Controller for handling OAuth2 authorizations */ @@ -54,9 +60,8 @@ public OltuAuthorizationController(RemoteAPITokenService tokenService, RemoteAPI * @param remoteAPI The API we need to authenticate with * @param redirect The location to redirect back to after authentication is complete * @return A ModelAndView beginning the authentication procedure - * @throws OAuthSystemException if we can't read from the authorization server. */ - public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redirect) throws OAuthSystemException { + public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redirect) { // get the URI for the remote service we'll be requesting from String serviceURI = remoteAPI.getServiceURI(); @@ -67,7 +72,7 @@ public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redi logger.debug("Redirect after authentication: " + redirect); // build a redirect URI to redirect to after auth flow is completed - String tokenRedirect = buildRedirectURI(); + URI tokenRedirect = buildRedirectURI(); // build state object which is used to extract the authCode to the correct remoteAPI String stateUuid = UUID.randomUUID().toString(); @@ -76,17 +81,15 @@ public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redi stateMap.put("redirect", redirect); session.setAttribute(stateUuid, stateMap); - // build the redirect query to request an authorization code from the - // remote API - OAuthClientRequest request = OAuthClientRequest.authorizationLocation(serviceAuthLocation.toString()) - .setClientId(remoteAPI.getClientId()) - .setRedirectURI(tokenRedirect) - .setResponseType(ResponseType.CODE.toString()) - .setScope("read") - .setState(stateUuid) - .buildQueryMessage(); - - String locURI = request.getLocationUri(); + // build the redirect query to request an authorization code from the remote API + AuthorizationRequest request = new AuthorizationRequest.Builder(new ResponseType(ResponseType.Value.CODE), + new ClientID(remoteAPI.getClientId())).scope(new Scope("read")) + .state(new State(stateUuid)) + .redirectionURI(tokenRedirect) + .endpointURI(serviceAuthLocation) + .build(); + + String locURI = request.toURI().toString(); logger.trace("Authorization request location: " + locURI); return "redirect:" + locURI; @@ -99,19 +102,31 @@ public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redi * @param response The response to redirect * @param state The state param which contains a map including apiId and redirect * @return A ModelAndView redirecting back to the resource that was requested - * @throws OAuthSystemException if we can't get an access token for the current request. - * @throws OAuthProblemException if we can't get a response from the authorization server + * @throws URISyntaxException + * @throws IOException */ @RequestMapping(TOKEN_ENDPOINT) public String getTokenFromAuthCode(HttpServletRequest request, HttpServletResponse response, - @RequestParam("state") String state) throws OAuthSystemException, OAuthProblemException { + @RequestParam("state") String state) throws ParseException { + HttpSession session = request.getSession(); // Get the OAuth2 auth code - OAuthAuthzResponse oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request); - String code = oar.getCode(); - logger.trace("Received auth code: " + code); + AuthenticationResponse authResponse = AuthenticationResponseParser + .parse(new ServletServerHttpRequest(request).getURI()); - HttpSession session = request.getSession(); + // Check the state + if (session.getAttribute(authResponse.getState().toString()) == null) { + logger.trace("State not present in session"); + throw new IridaOAuthProblemException("hello"); + // raise exception? + } + + if (authResponse instanceof AuthenticationErrorResponse) { + logger.trace("Unexpected authentication response"); + } + + AuthorizationCode code = authResponse.toSuccessResponse().getAuthorizationCode(); + logger.trace("Received auth code: " + code.getValue()); Map stateMap = (Map) session.getAttribute(state); @@ -119,7 +134,7 @@ public String getTokenFromAuthCode(HttpServletRequest request, HttpServletRespon String redirect = stateMap.get("redirect"); // Build the redirect URI to request a token from - String tokenRedirect = buildRedirectURI(); + URI tokenRedirect = buildRedirectURI(); // Read the RemoteAPI from the RemoteAPIService and get the base URI RemoteAPI remoteAPI = remoteAPIService.read(apiId); @@ -135,11 +150,11 @@ public String getTokenFromAuthCode(HttpServletRequest request, HttpServletRespon * * @return */ - private String buildRedirectURI() { + private URI buildRedirectURI() { - URI build = UriBuilder.fromUri(serverBase).path(TOKEN_ENDPOINT).build(); + URI redirectURI = UriBuilder.fromUri(serverBase).path(TOKEN_ENDPOINT).build(); - return build.toString(); + return redirectURI; } /** diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/RemoteAPIController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/RemoteAPIController.java index 2403e36bb6e..fd0354a570e 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/RemoteAPIController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/RemoteAPIController.java @@ -5,7 +5,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -87,10 +86,9 @@ public String connectToAPI(@PathVariable Long apiId, Model model) { * @param request The incoming request method * @param ex The thrown exception * @return A redirect to the {@link OltuAuthorizationController}'s authentication - * @throws OAuthSystemException if the request cannot be authenticated. */ @ExceptionHandler(IridaOAuthException.class) - public String handleOAuthException(HttpServletRequest request, IridaOAuthException ex) throws OAuthSystemException { + public String handleOAuthException(HttpServletRequest request, IridaOAuthException ex) { logger.debug("Caught IridaOAuthException. Beginning OAuth2 authentication token flow."); String requestURI = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); HttpSession session = request.getSession(); diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java index 6578e42dcbd..42d7c315c42 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java @@ -1,12 +1,14 @@ package ca.corefacility.bioinformatics.irida.service; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import java.net.URI; import ca.corefacility.bioinformatics.irida.exceptions.EntityNotFoundException; import ca.corefacility.bioinformatics.irida.model.RemoteAPI; import ca.corefacility.bioinformatics.irida.model.RemoteAPIToken; +import com.nimbusds.oauth2.sdk.AuthorizationCode; +import com.nimbusds.oauth2.sdk.ParseException; + /** * Service for saving and reading tokens for Remote APIs */ @@ -14,8 +16,7 @@ public interface RemoteAPITokenService { /** * Add a token to the store for a given service * - * @param token - * The token to create + * @param token The token to create * @return the created token */ public RemoteAPIToken create(RemoteAPIToken token); @@ -23,21 +24,17 @@ public interface RemoteAPITokenService { /** * Get a token for a given service * - * @param remoteAPI - * The {@link RemoteAPI} of the service root + * @param remoteAPI The {@link RemoteAPI} of the service root * @return A String OAuth2 token - * @throws EntityNotFoundException - * if the token could not be found + * @throws EntityNotFoundException if the token could not be found */ public RemoteAPIToken getToken(RemoteAPI remoteAPI) throws EntityNotFoundException; /** * Delete a token for the logged in user and a given {@link RemoteAPI} * - * @param remoteAPI - * the {@link RemoteAPI} to delete a token for - * @throws EntityNotFoundException - * if the token could not be found + * @param remoteAPI the {@link RemoteAPI} to delete a token for + * @throws EntityNotFoundException if the token could not be found */ public void delete(RemoteAPI remoteAPI) throws EntityNotFoundException; @@ -51,15 +48,13 @@ public interface RemoteAPITokenService { * @throws OAuthSystemException If ther's a problem building the token request * @throws OAuthProblemException If there's a problem with the token request */ - public RemoteAPIToken createTokenFromAuthCode(String authcode, RemoteAPI remoteAPI, String tokenRedirect) - throws OAuthSystemException, OAuthProblemException; + public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) + throws ParseException; /** - * Update a given {@link RemoteAPI}'s OAuth token by refresh token if - * available + * Update a given {@link RemoteAPI}'s OAuth token by refresh token if available * - * @param api - * the API to update + * @param api the API to update * @return the most recent token if available */ public RemoteAPIToken updateTokenFromRefreshToken(RemoteAPI api); diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java index d9d0dc7acd9..a893a8c30e8 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java @@ -1,16 +1,11 @@ package ca.corefacility.bioinformatics.irida.service.impl; +import java.io.IOException; import java.net.URI; import java.util.Date; import javax.ws.rs.core.UriBuilder; -import org.apache.oltu.oauth2.client.OAuthClient; -import org.apache.oltu.oauth2.client.request.OAuthClientRequest; -import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; -import org.apache.oltu.oauth2.common.message.types.GrantType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; import ca.corefacility.bioinformatics.irida.exceptions.EntityNotFoundException; +import ca.corefacility.bioinformatics.irida.exceptions.IridaOAuthProblemException; import ca.corefacility.bioinformatics.irida.model.RemoteAPI; import ca.corefacility.bioinformatics.irida.model.RemoteAPIToken; import ca.corefacility.bioinformatics.irida.model.user.User; @@ -29,6 +25,14 @@ import ca.corefacility.bioinformatics.irida.repositories.user.UserRepository; import ca.corefacility.bioinformatics.irida.service.RemoteAPITokenService; +import com.nimbusds.oauth2.sdk.*; +import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; +import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; +import com.nimbusds.oauth2.sdk.auth.Secret; +import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.token.AccessToken; +import com.nimbusds.oauth2.sdk.token.RefreshToken; + /** * Service implementation for storing and reading remote api tokens */ @@ -40,15 +44,12 @@ public class RemoteAPITokenServiceImpl implements RemoteAPITokenService { private RemoteApiTokenRepository tokenRepository; private UserRepository userRepository; - private final OAuthClient oauthClient; @Autowired - public RemoteAPITokenServiceImpl(RemoteApiTokenRepository tokenRepository, UserRepository userRepository, - OAuthClient oauthClient) { + public RemoteAPITokenServiceImpl(RemoteApiTokenRepository tokenRepository, UserRepository userRepository) { super(); this.tokenRepository = tokenRepository; this.userRepository = userRepository; - this.oauthClient = oauthClient; } /** @@ -99,33 +100,48 @@ public RemoteAPIToken updateTokenFromRefreshToken(RemoteAPI api) { try { token = getToken(api); - String refreshToken = token.getRefreshToken(); + String refreshTokenValue = token.getRefreshToken(); + + if (refreshTokenValue != null) { + RefreshToken refreshToken = new RefreshToken(refreshTokenValue); - if (refreshToken != null) { URI serviceTokenLocation = UriBuilder.fromUri(api.getServiceURI()).path("oauth").path("token").build(); - OAuthClientRequest tokenRequest = OAuthClientRequest.tokenLocation(serviceTokenLocation.toString()) - .setClientId(api.getClientId()) - .setClientSecret(api.getClientSecret()) - .setRefreshToken(refreshToken) - .setGrantType(GrantType.REFRESH_TOKEN) - .buildBodyMessage(); + ClientAuthentication clientAuth = new ClientSecretBasic(new ClientID(api.getClientId()), + new Secret(api.getClientSecret())); + + TokenRequest tokenRequest = new TokenRequest(serviceTokenLocation, clientAuth, + new RefreshTokenGrant(refreshToken)); + + TokenResponse tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send()); + + if (!tokenResponse.indicatesSuccess()) { + // We got an error response... + TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); + logger.error("Updating token by refresh token failed", errorResponse.getErrorObject().toString()); + } else { - OAuthJSONAccessTokenResponse accessToken = oauthClient.accessToken(tokenRequest); + AccessTokenResponse accessTokenResponse = tokenResponse.toSuccessResponse(); - token = buildTokenFromResponse(accessToken, api); + token = buildTokenFromResponse(accessTokenResponse, api); - delete(api); - token = create(token); + delete(api); + token = create(token); - logger.trace("Token for api " + api + " updated by refresh token."); + logger.trace("Token for api " + api + " updated by refresh token."); + + } } else { logger.trace("No refresh token for api " + api + ". Cannot update access token."); } } catch (EntityNotFoundException ex) { logger.debug("Token not found for api " + api + ". Cannot update access token."); - } catch (OAuthProblemException | OAuthSystemException ex) { - logger.error("Updating token by refresh token failed", ex.getMessage()); + } catch (ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } return token; @@ -138,44 +154,43 @@ public RemoteAPIToken updateTokenFromRefreshToken(RemoteAPI api) { * @param remoteAPI the remote api to get a token for * @param tokenRedirect a redirect url to get the token from * @return a new token - * @throws OAuthSystemException If building the token request fails - * @throws OAuthProblemException If the token request fails + * @throws IOException */ @Transactional - public RemoteAPIToken createTokenFromAuthCode(String authcode, RemoteAPI remoteAPI, String tokenRedirect) - throws OAuthSystemException, OAuthProblemException { + public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) + throws ParseException { String serviceURI = remoteAPI.getServiceURI(); // Build the token location for this service URI serviceTokenLocation = UriBuilder.fromUri(serviceURI).path("oauth").path("token").build(); logger.trace("Remote token location: " + serviceTokenLocation); + ClientAuthentication clientAuth = new ClientSecretBasic(new ClientID(remoteAPI.getClientId()), + new Secret(remoteAPI.getClientSecret())); + // Create the token request form the given auth code - OAuthClientRequest tokenRequest = OAuthClientRequest.tokenLocation(serviceTokenLocation.toString()) - .setClientId(remoteAPI.getClientId()) - .setClientSecret(remoteAPI.getClientSecret()) - .setRedirectURI(tokenRedirect) - .setCode(authcode) - .setGrantType(GrantType.AUTHORIZATION_CODE) - .buildBodyMessage(); + TokenRequest tokenRequest = new TokenRequest(serviceTokenLocation, clientAuth, + new AuthorizationCodeGrant(authcode, tokenRedirect)); // execute the request - OAuthJSONAccessTokenResponse accessTokenResponse = oauthClient.accessToken(tokenRequest); - - // read the response for the access token - String accessToken = accessTokenResponse.getAccessToken(); + TokenResponse tokenResponse; + try { + tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send()); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new IridaOAuthProblemException("message"); + } - // Handle Refresh Tokens - String refreshToken = accessTokenResponse.getRefreshToken(); + if (!tokenResponse.indicatesSuccess()) { + // We got an error response... + TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); + } - // check the token expiry - Long expiresIn = accessTokenResponse.getExpiresIn(); - Long currentTime = System.currentTimeMillis(); - Date expiry = new Date(currentTime + (expiresIn * ONE_SECOND_IN_MS)); - logger.trace("Token expiry: " + expiry); + AccessTokenResponse accessTokenResponse = tokenResponse.toSuccessResponse(); // create the OAuth2 token and store it - RemoteAPIToken token = new RemoteAPIToken(accessToken, refreshToken, remoteAPI, expiry); + RemoteAPIToken token = buildTokenFromResponse(accessTokenResponse, remoteAPI); return create(token); } @@ -216,21 +231,27 @@ protected RemoteAPIToken getOldTokenId(RemoteAPIToken apiToken) { return apiToken; } - private RemoteAPIToken buildTokenFromResponse(OAuthJSONAccessTokenResponse accessTokenResponse, - RemoteAPI remoteAPI) { + private RemoteAPIToken buildTokenFromResponse(AccessTokenResponse accessTokenResponse, RemoteAPI remoteAPI) { // read the response for the access token - String accessToken = accessTokenResponse.getAccessToken(); + AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); // Handle Refresh Tokens - String refreshToken = accessTokenResponse.getRefreshToken(); + RefreshToken refreshToken = accessTokenResponse.getTokens().getRefreshToken(); // check the token expiry - Long expiresIn = accessTokenResponse.getExpiresIn(); + Long expiresIn = accessToken.getLifetime(); Long currentTime = System.currentTimeMillis(); Date expiry = new Date(currentTime + (expiresIn * ONE_SECOND_IN_MS)); logger.trace("Token expiry: " + expiry); // create the OAuth2 token - return new RemoteAPIToken(accessToken, refreshToken, remoteAPI, expiry); + RemoteAPIToken token; + if (refreshToken != null) { + token = new RemoteAPIToken(accessToken.getValue(), refreshToken.getValue(), remoteAPI, expiry); + } else { + token = new RemoteAPIToken(accessToken.getValue(), remoteAPI, expiry); + } + + return token; } } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java index ba1f02afb4c..60f9127928b 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.HashMap; @@ -12,8 +13,6 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.apache.oltu.oauth2.common.exception.OAuthProblemException; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -23,6 +22,9 @@ import ca.corefacility.bioinformatics.irida.service.RemoteAPIService; import ca.corefacility.bioinformatics.irida.service.RemoteAPITokenService; +import com.nimbusds.oauth2.sdk.AuthorizationCode; +import com.nimbusds.oauth2.sdk.ParseException; + import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; @@ -47,7 +49,7 @@ public void setUp() { } @Test - public void testAuthenticate() throws IOException, OAuthSystemException, UnsupportedEncodingException { + public void testAuthenticate() throws IOException, UnsupportedEncodingException { RemoteAPI remoteAPI = new RemoteAPI("name", "http://uri", "a description", "id", "secret"); remoteAPI.setId(1L); String redirect = "http://base"; @@ -62,12 +64,11 @@ public void testAuthenticate() throws IOException, OAuthSystemException, Unsuppo } @Test - public void testGetTokenFromAuthCode() - throws IOException, OAuthSystemException, OAuthProblemException, URISyntaxException { + public void testGetTokenFromAuthCode() throws IOException, URISyntaxException, ParseException { Long apiId = 1L; RemoteAPI remoteAPI = new RemoteAPI("name", "http://remoteLocation", "a description", "id", "secret"); remoteAPI.setId(apiId); - String code = "code"; + AuthorizationCode code = new AuthorizationCode("code"); String redirect = "http://originalPage"; String stateUuid = UUID.randomUUID().toString(); @@ -81,30 +82,30 @@ public void testGetTokenFromAuthCode() HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); - Map requestParams = new HashMap<>(); - requestParams.put("code", new String[] { code }); + StringBuffer url = new StringBuffer(serverBase + OltuAuthorizationController.TOKEN_ENDPOINT); + String query = "code=" + code.getValue() + "&state=" + stateUuid; - when(request.getParameterMap()).thenReturn(requestParams); + when(request.getRequestURL()).thenReturn(url); + when(request.getQueryString()).thenReturn(query); when(request.getSession()).thenReturn(session); controller.getTokenFromAuthCode(request, response, stateUuid); verify(apiService).read(apiId); - ArgumentCaptor redirectArg = ArgumentCaptor.forClass(String.class); + ArgumentCaptor redirectArg = ArgumentCaptor.forClass(URI.class); verify(tokenService).createTokenFromAuthCode(eq(code), eq(remoteAPI), redirectArg.capture()); - String capturedRedirect = redirectArg.getValue(); + String capturedRedirect = redirectArg.getValue().toString(); assertTrue(capturedRedirect.contains(serverBase)); } @Test - public void testGetTokenFromAuthCodeExtraSlash() - throws IOException, OAuthSystemException, OAuthProblemException, URISyntaxException { + public void testGetTokenFromAuthCodeExtraSlash() throws IOException, URISyntaxException, ParseException { Long apiId = 1L; RemoteAPI remoteAPI = new RemoteAPI("name", "http://remoteLocation", "a description", "id", "secret"); remoteAPI.setId(apiId); - String code = "code"; + AuthorizationCode code = new AuthorizationCode("code"); String redirect = "http://originalPage"; String stateUuid = UUID.randomUUID().toString(); @@ -121,21 +122,58 @@ public void testGetTokenFromAuthCodeExtraSlash() HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); - Map requestParams = new HashMap<>(); - requestParams.put("code", new String[] { code }); + StringBuffer url = new StringBuffer(serverBase + "/" + OltuAuthorizationController.TOKEN_ENDPOINT); + String query = "code=" + code.getValue() + "&state=" + stateUuid; - when(request.getParameterMap()).thenReturn(requestParams); + when(request.getRequestURL()).thenReturn(url); + when(request.getQueryString()).thenReturn(query); when(request.getSession()).thenReturn(session); controller.getTokenFromAuthCode(request, response, stateUuid); verify(apiService).read(apiId); - ArgumentCaptor redirectArg = ArgumentCaptor.forClass(String.class); + ArgumentCaptor redirectArg = ArgumentCaptor.forClass(URI.class); verify(tokenService).createTokenFromAuthCode(eq(code), eq(remoteAPI), redirectArg.capture()); - String capturedRedirect = redirectArg.getValue(); + String capturedRedirect = redirectArg.getValue().toString(); assertTrue(capturedRedirect.contains(serverBase)); assertFalse(capturedRedirect.contains(serverBase + "//")); } + + @Test + public void testGetTokenFromAuthCodeError() throws IOException, URISyntaxException, ParseException { + Long apiId = 1L; + RemoteAPI remoteAPI = new RemoteAPI("name", "http://remoteLocation", "a description", "id", "secret"); + remoteAPI.setId(apiId); + AuthorizationCode code = new AuthorizationCode("code"); + String redirect = "http://originalPage"; + + String stateUuid = UUID.randomUUID().toString(); + Map stateMap = new HashMap(); + stateMap.put("apiId", apiId.toString()); + stateMap.put("redirect", redirect); + + when(session.getAttribute(stateUuid)).thenReturn(stateMap); + + when(apiService.read(apiId)).thenReturn(remoteAPI); + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + StringBuffer url = new StringBuffer(serverBase + OltuAuthorizationController.TOKEN_ENDPOINT); + + when(request.getRequestURL()).thenReturn(url); + when(request.getQueryString()).thenReturn(null); + when(request.getSession()).thenReturn(session); + + controller.getTokenFromAuthCode(request, response, stateUuid); + + verify(apiService).read(apiId); + + ArgumentCaptor redirectArg = ArgumentCaptor.forClass(URI.class); + verify(tokenService).createTokenFromAuthCode(eq(code), eq(remoteAPI), redirectArg.capture()); + + String capturedRedirect = redirectArg.getValue().toString(); + assertTrue(capturedRedirect.contains(serverBase)); + } } diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/RemoteAPIControllerTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/RemoteAPIControllerTest.java index bf3cd2d286b..e946daf426f 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/RemoteAPIControllerTest.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/RemoteAPIControllerTest.java @@ -4,7 +4,6 @@ import javax.servlet.http.HttpServletRequest; -import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.ui.ExtendedModelMap; @@ -64,7 +63,7 @@ public void testConnectToAPIActiveToken() { } @Test - public void testHandleOAuthException() throws IOException, OAuthSystemException { + public void testHandleOAuthException() throws IOException { HttpServletRequest request = mock(HttpServletRequest.class); String redirect = "http://request"; when(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)).thenReturn(redirect); diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/service/impl/unit/RemoteAPITokenServiceImplTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/service/impl/unit/RemoteAPITokenServiceImplTest.java index b2b227f7636..cc02e1c7695 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/service/impl/unit/RemoteAPITokenServiceImplTest.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/service/impl/unit/RemoteAPITokenServiceImplTest.java @@ -1,15 +1,7 @@ package ca.corefacility.bioinformatics.irida.service.impl.unit; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import java.util.Date; -import org.apache.oltu.oauth2.client.OAuthClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.TestingAuthenticationToken; @@ -24,9 +16,12 @@ import ca.corefacility.bioinformatics.irida.service.RemoteAPITokenService; import ca.corefacility.bioinformatics.irida.service.impl.RemoteAPITokenServiceImpl; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + /** * Unit tests class for {@link RemoteAPITokenServiceImpl} - * */ public class RemoteAPITokenServiceImplTest { private RemoteAPITokenService service; @@ -35,45 +30,42 @@ public class RemoteAPITokenServiceImplTest { private RemoteAPIToken remoteAPIToken; private RemoteAPI remoteAPI; private User user; - private OAuthClient oauthClient; - - + @BeforeEach - public void setUp(){ + public void setUp() { tokenRepository = mock(RemoteApiTokenRepository.class); userRepo = mock(UserRepository.class); - oauthClient = mock(OAuthClient.class); - service = new RemoteAPITokenServiceImpl(tokenRepository, userRepo, oauthClient); - + service = new RemoteAPITokenServiceImpl(tokenRepository, userRepo); + user = new User("tom", "an@email.com", "password1", "tom", "matthews", "123456789"); remoteAPI = new RemoteAPI("apiname", "http://nowhere", "a test api", "clientId", "clientSecret"); SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(user, null)); - remoteAPIToken = new RemoteAPIToken("token",remoteAPI,new Date()); + remoteAPIToken = new RemoteAPIToken("token", remoteAPI, new Date()); } - + @Test public void testAddToken() { when(userRepo.loadUserByUsername(user.getUsername())).thenReturn(user); - + service.create(remoteAPIToken); - + verify(tokenRepository).save(remoteAPIToken); - verify(userRepo,times(2)).loadUserByUsername(user.getUsername()); - verify(tokenRepository,times(0)).delete(remoteAPIToken); + verify(userRepo, times(2)).loadUserByUsername(user.getUsername()); + verify(tokenRepository, times(0)).delete(remoteAPIToken); } - + @Test public void testAddTokenExisting() { when(userRepo.loadUserByUsername(user.getUsername())).thenReturn(user); when(tokenRepository.readTokenForApiAndUser(remoteAPI, user)).thenReturn(remoteAPIToken); - + service.create(remoteAPIToken); - + verify(tokenRepository).save(remoteAPIToken); - verify(userRepo,times(2)).loadUserByUsername(user.getUsername()); + verify(userRepo, times(2)).loadUserByUsername(user.getUsername()); verify(tokenRepository).readTokenForApiAndUser(remoteAPI, user); } - + @Test public void testAddTokenNotLoggedIn() { SecurityContextHolder.clearContext(); @@ -87,24 +79,23 @@ public void testAddTokenNotLoggedIn() { public void testGetToken() { when(userRepo.loadUserByUsername(user.getUsername())).thenReturn(user); when(tokenRepository.readTokenForApiAndUser(remoteAPI, user)).thenReturn(remoteAPIToken); - + RemoteAPIToken token = service.getToken(remoteAPI); - + assertEquals(remoteAPIToken, token); - + verify(userRepo).loadUserByUsername(user.getUsername()); verify(tokenRepository).readTokenForApiAndUser(remoteAPI, user); } - + @Test public void testGetNotExisting() { when(userRepo.loadUserByUsername(user.getUsername())).thenReturn(user); when(tokenRepository.readTokenForApiAndUser(remoteAPI, user)).thenReturn(null); - + assertThrows(EntityNotFoundException.class, () -> { service.getToken(remoteAPI); }); } - } From 6340460d537db17c9bf3e6faa9fe1c01c9ede0b4 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 07:33:13 -0600 Subject: [PATCH 02/11] chore: fix newly added OltuAuthorizationController test --- .../oauth/OltuAuthorizationControllerTest.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java index 60f9127928b..d42951f3d10 100644 --- a/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java +++ b/src/test/java/ca/corefacility/bioinformatics/irida/ria/unit/web/oauth/OltuAuthorizationControllerTest.java @@ -25,8 +25,7 @@ import com.nimbusds.oauth2.sdk.AuthorizationCode; import com.nimbusds.oauth2.sdk.ParseException; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -163,17 +162,11 @@ public void testGetTokenFromAuthCodeError() throws IOException, URISyntaxExcepti StringBuffer url = new StringBuffer(serverBase + OltuAuthorizationController.TOKEN_ENDPOINT); when(request.getRequestURL()).thenReturn(url); - when(request.getQueryString()).thenReturn(null); + when(request.getQueryString()).thenReturn(""); when(request.getSession()).thenReturn(session); - controller.getTokenFromAuthCode(request, response, stateUuid); - - verify(apiService).read(apiId); - - ArgumentCaptor redirectArg = ArgumentCaptor.forClass(URI.class); - verify(tokenService).createTokenFromAuthCode(eq(code), eq(remoteAPI), redirectArg.capture()); - - String capturedRedirect = redirectArg.getValue().toString(); - assertTrue(capturedRedirect.contains(serverBase)); + assertThrows(ParseException.class, () -> { + controller.getTokenFromAuthCode(request, response, stateUuid); + }); } } From 9ce8ecd6da9adc053c9f951ec245fa6f67a5568d Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 09:39:10 -0600 Subject: [PATCH 03/11] chore: fix various javadoc errors --- .../irida/ria/web/errors/IridaCustomExceptionHandler.java | 2 +- .../ria/web/oauth/GalaxyRedirectionEndpointController.java | 2 -- .../irida/ria/web/oauth/OltuAuthorizationController.java | 6 +----- .../irida/service/impl/RemoteAPITokenServiceImpl.java | 3 ++- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java index 9a6c333d80e..f19548b2c75 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/errors/IridaCustomExceptionHandler.java @@ -78,7 +78,7 @@ public void handleAccessDeniedException(AccessDeniedException ex, HttpServletRes /** * Catch an {@link IridaOAuthProblemException} and return an http 500 error * - * @param ex the caught {@link OAuthProblemException} + * @param ex the caught {@link IridaOAuthProblemException} * @return A {@link ModelAndView} containing the name of the oauth error view */ @ExceptionHandler(IridaOAuthProblemException.class) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java index bc548bf8efc..3d1270bc0f5 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java @@ -10,7 +10,6 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; -import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; @@ -33,7 +32,6 @@ public class GalaxyRedirectionEndpointController { * @param request the incoming request * @param session the user's session * @return a template that will pass on the authorization code - * @throws OAuthProblemException if a valid OAuth authorization response cannot be created * @throws IllegalStateException if the callback URL is removed from an invalid session * @throws ParseException */ diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java index cb710d7711f..d699b30c19b 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java @@ -1,8 +1,6 @@ package ca.corefacility.bioinformatics.irida.ria.web.oauth; -import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -28,7 +26,6 @@ import com.nimbusds.oauth2.sdk.*; import com.nimbusds.oauth2.sdk.id.ClientID; -import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; @@ -102,8 +99,7 @@ public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redi * @param response The response to redirect * @param state The state param which contains a map including apiId and redirect * @return A ModelAndView redirecting back to the resource that was requested - * @throws URISyntaxException - * @throws IOException + * @throws ParseException */ @RequestMapping(TOKEN_ENDPOINT) public String getTokenFromAuthCode(HttpServletRequest request, HttpServletResponse response, diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java index a893a8c30e8..5f30dbd9529 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java @@ -6,6 +6,7 @@ import javax.ws.rs.core.UriBuilder; +import org.apache.oltu.oauth2.common.error.OAuthError.TokenResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -154,7 +155,7 @@ public RemoteAPIToken updateTokenFromRefreshToken(RemoteAPI api) { * @param remoteAPI the remote api to get a token for * @param tokenRedirect a redirect url to get the token from * @return a new token - * @throws IOException + * @throws ParseException */ @Transactional public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) From 5ef896daeee44a667b604b5de815429ae02c6b16 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 10:10:44 -0600 Subject: [PATCH 04/11] chore: fix missing imports --- .../irida/ria/web/oauth/GalaxyRedirectionEndpointController.java | 1 + .../irida/ria/web/oauth/OltuAuthorizationController.java | 1 + .../irida/service/impl/RemoteAPITokenServiceImpl.java | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java index 3d1270bc0f5..1ebfb56c98c 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java @@ -10,6 +10,7 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; +import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java index d699b30c19b..cbd72f22f8f 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java @@ -26,6 +26,7 @@ import com.nimbusds.oauth2.sdk.*; import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponse; import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser; diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java index 5f30dbd9529..69185327bd2 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java @@ -6,7 +6,6 @@ import javax.ws.rs.core.UriBuilder; -import org.apache.oltu.oauth2.common.error.OAuthError.TokenResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; From c899e77a6d0876724df9fd6f4caac4519c4039e7 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 10:50:02 -0600 Subject: [PATCH 05/11] chore: fix another javadoc issue --- .../bioinformatics/irida/service/RemoteAPITokenService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java index 42d7c315c42..2908bc274cb 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java @@ -45,8 +45,7 @@ public interface RemoteAPITokenService { * @param remoteAPI the remote api to get a token for * @param tokenRedirect a redirect url to get the token from * @return the newly created token - * @throws OAuthSystemException If ther's a problem building the token request - * @throws OAuthProblemException If there's a problem with the token request + * @throws ParseException If there's a problem with the token request */ public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) throws ParseException; From c669b09c381d9328fc7cc7e5efb3d5c35391f431 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 13:38:14 -0600 Subject: [PATCH 06/11] chore: update error handling for getting tokens --- .../oauth/OltuAuthorizationController.java | 20 +++++++++++-------- .../irida/service/RemoteAPITokenService.java | 6 ++++-- .../impl/RemoteAPITokenServiceImpl.java | 15 ++++---------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java index cbd72f22f8f..ecf443ecef2 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/OltuAuthorizationController.java @@ -100,26 +100,30 @@ public String authenticate(HttpSession session, RemoteAPI remoteAPI, String redi * @param response The response to redirect * @param state The state param which contains a map including apiId and redirect * @return A ModelAndView redirecting back to the resource that was requested + * @throws IridaOAuthProblemException * @throws ParseException */ @RequestMapping(TOKEN_ENDPOINT) public String getTokenFromAuthCode(HttpServletRequest request, HttpServletResponse response, - @RequestParam("state") String state) throws ParseException { + @RequestParam("state") String state) throws IridaOAuthProblemException, ParseException { HttpSession session = request.getSession(); // Get the OAuth2 auth code AuthenticationResponse authResponse = AuthenticationResponseParser .parse(new ServletServerHttpRequest(request).getURI()); - // Check the state - if (session.getAttribute(authResponse.getState().toString()) == null) { - logger.trace("State not present in session"); - throw new IridaOAuthProblemException("hello"); - // raise exception? - } - if (authResponse instanceof AuthenticationErrorResponse) { logger.trace("Unexpected authentication response"); + throw new IridaOAuthProblemException(authResponse.toErrorResponse().getErrorObject().toString()); + } + + // Verify existence of state + if (authResponse.getState() == null) { + logger.trace("Authentication response did not contain a state"); + throw new IridaOAuthProblemException("State missing from authentication response"); + } else if (session.getAttribute(authResponse.getState().toString()) == null) { + logger.trace("State not present in session"); + throw new IridaOAuthProblemException("State not present in session"); } AuthorizationCode code = authResponse.toSuccessResponse().getAuthorizationCode(); diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java index 2908bc274cb..6805b263000 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java @@ -3,6 +3,7 @@ import java.net.URI; import ca.corefacility.bioinformatics.irida.exceptions.EntityNotFoundException; +import ca.corefacility.bioinformatics.irida.exceptions.IridaOAuthProblemException; import ca.corefacility.bioinformatics.irida.model.RemoteAPI; import ca.corefacility.bioinformatics.irida.model.RemoteAPIToken; @@ -45,10 +46,11 @@ public interface RemoteAPITokenService { * @param remoteAPI the remote api to get a token for * @param tokenRedirect a redirect url to get the token from * @return the newly created token - * @throws ParseException If there's a problem with the token request + * @throws IridaOAuthProblemExcpetion If we could not successfully get a token + * @throws ParseException If there's a problem with the token request */ public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) - throws ParseException; + throws IridaOAuthProblemException, ParseException; /** * Update a given {@link RemoteAPI}'s OAuth token by refresh token if available diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java index 69185327bd2..d1b39b6f026 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java @@ -148,17 +148,11 @@ public RemoteAPIToken updateTokenFromRefreshToken(RemoteAPI api) { } /** - * Get a new token from the given auth code - * - * @param authcode the auth code to create a token for - * @param remoteAPI the remote api to get a token for - * @param tokenRedirect a redirect url to get the token from - * @return a new token - * @throws ParseException + * {@inheritDoc} */ @Transactional public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) - throws ParseException { + throws IridaOAuthProblemException, ParseException { String serviceURI = remoteAPI.getServiceURI(); // Build the token location for this service @@ -177,14 +171,13 @@ public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, Remote try { tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send()); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - throw new IridaOAuthProblemException("message"); + throw new IridaOAuthProblemException(e.getMessage()); } if (!tokenResponse.indicatesSuccess()) { // We got an error response... TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); + throw new IridaOAuthProblemException(errorResponse.getErrorObject().toString()); } AccessTokenResponse accessTokenResponse = tokenResponse.toSuccessResponse(); From 0d149ef751380acee4cf639376c71769f31df633 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 9 Dec 2022 13:45:06 -0600 Subject: [PATCH 07/11] chore: fix typo in javadoc --- .../bioinformatics/irida/service/RemoteAPITokenService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java index 6805b263000..f01a8b96a1d 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/RemoteAPITokenService.java @@ -46,7 +46,7 @@ public interface RemoteAPITokenService { * @param remoteAPI the remote api to get a token for * @param tokenRedirect a redirect url to get the token from * @return the newly created token - * @throws IridaOAuthProblemExcpetion If we could not successfully get a token + * @throws IridaOAuthProblemException If we could not successfully get a token * @throws ParseException If there's a problem with the token request */ public RemoteAPIToken createTokenFromAuthCode(AuthorizationCode authcode, RemoteAPI remoteAPI, URI tokenRedirect) From d4485865c7c614ae1e268501640478be2bf5bb39 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Tue, 17 Jan 2023 10:09:21 -0600 Subject: [PATCH 08/11] chore: correct indentation to use tabs --- .../irida/exceptions/IridaOAuthProblemException.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java b/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java index ca800e9e40c..b1086aa479e 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/exceptions/IridaOAuthProblemException.java @@ -2,8 +2,8 @@ public class IridaOAuthProblemException extends RuntimeException { - public IridaOAuthProblemException(String message) { - super(message); - } + public IridaOAuthProblemException(String message) { + super(message); + } } From aa9f85e8266978ea35344252243ae81fcfe2df64 Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 27 Jan 2023 09:41:52 -0600 Subject: [PATCH 09/11] chore: update logging message in GalaxyRedirectionEndpointController --- .../ria/web/oauth/GalaxyRedirectionEndpointController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java index 1ebfb56c98c..dcf34533119 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/ria/web/oauth/GalaxyRedirectionEndpointController.java @@ -46,7 +46,8 @@ public String passAuthCode(Model model, HttpServletRequest request, HttpSession .parse(new ServletServerHttpRequest(request).getURI()); if (authResponse instanceof AuthenticationErrorResponse) { - logger.trace("Unexpected authentication response"); + logger.trace("Unexpected authentication error response during Galaxy OAuth flow", + authResponse.toErrorResponse().getErrorObject().toString()); } String code = authResponse.toSuccessResponse().getAuthorizationCode().getValue(); From 9aad969ccf1bda6d7a1dedac0f1b83409d69f09a Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 27 Jan 2023 09:53:00 -0600 Subject: [PATCH 10/11] chore: refactored updateTokenFromRefreshToken to have smaller try catch blocks and more explicit logging. --- .../impl/RemoteAPITokenServiceImpl.java | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java index d1b39b6f026..7352e7bcb64 100644 --- a/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java +++ b/src/main/java/ca/corefacility/bioinformatics/irida/service/impl/RemoteAPITokenServiceImpl.java @@ -99,49 +99,53 @@ public RemoteAPIToken updateTokenFromRefreshToken(RemoteAPI api) { try { token = getToken(api); + } catch (EntityNotFoundException ex) { + logger.debug("Token not found for api " + api + ". Cannot update access token."); + return token; + } - String refreshTokenValue = token.getRefreshToken(); - - if (refreshTokenValue != null) { - RefreshToken refreshToken = new RefreshToken(refreshTokenValue); + String refreshTokenValue = token.getRefreshToken(); + if (refreshTokenValue == null) { + logger.trace("No refresh token for api " + api + ". Cannot update access token."); + return token; + } else { + RefreshToken refreshToken = new RefreshToken(refreshTokenValue); - URI serviceTokenLocation = UriBuilder.fromUri(api.getServiceURI()).path("oauth").path("token").build(); + URI serviceTokenLocation = UriBuilder.fromUri(api.getServiceURI()).path("oauth").path("token").build(); - ClientAuthentication clientAuth = new ClientSecretBasic(new ClientID(api.getClientId()), - new Secret(api.getClientSecret())); + ClientAuthentication clientAuth = new ClientSecretBasic(new ClientID(api.getClientId()), + new Secret(api.getClientSecret())); - TokenRequest tokenRequest = new TokenRequest(serviceTokenLocation, clientAuth, - new RefreshTokenGrant(refreshToken)); + TokenRequest tokenRequest = new TokenRequest(serviceTokenLocation, clientAuth, + new RefreshTokenGrant(refreshToken)); - TokenResponse tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send()); + TokenResponse tokenResponse; + try { + tokenResponse = TokenResponse.parse(tokenRequest.toHTTPRequest().send()); + } catch (ParseException e) { + logger.error("Updating token by refresh token failed", e); + return token; + } catch (IOException e) { + logger.error("Updating token by refresh token failed", e); + return token; + } - if (!tokenResponse.indicatesSuccess()) { - // We got an error response... - TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); - logger.error("Updating token by refresh token failed", errorResponse.getErrorObject().toString()); - } else { + if (!tokenResponse.indicatesSuccess()) { + // We got an error response... + TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); + logger.error("Updating token by refresh token failed", errorResponse.getErrorObject().toString()); + } else { - AccessTokenResponse accessTokenResponse = tokenResponse.toSuccessResponse(); + AccessTokenResponse accessTokenResponse = tokenResponse.toSuccessResponse(); - token = buildTokenFromResponse(accessTokenResponse, api); + token = buildTokenFromResponse(accessTokenResponse, api); - delete(api); - token = create(token); + delete(api); + token = create(token); - logger.trace("Token for api " + api + " updated by refresh token."); + logger.trace("Token for api " + api + " updated by refresh token."); - } - } else { - logger.trace("No refresh token for api " + api + ". Cannot update access token."); } - } catch (EntityNotFoundException ex) { - logger.debug("Token not found for api " + api + ". Cannot update access token."); - } catch (ParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); } return token; From 622b9a5742221494c1c6de2884a7b48d42d9957b Mon Sep 17 00:00:00 2001 From: Eric Enns Date: Fri, 27 Jan 2023 09:57:05 -0600 Subject: [PATCH 11/11] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 957f72aeb20..ff15c5656fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [Documentation]: Updated broken links in developer documentation. See [PR 1418](https://github.com/phac-nml/irida/pull/1418). * [Developer] Updated developer setup documentation, ignore java_pid*.hprof files, and added quality of life file `gradle.properties` * [UI]: Refreshed global search page to use Ant Design. See [PR 1409](https://github.com/phac-nml/irida/pull/1409) +* [Developer]: Replaced Apache OLTU with Nimbusds for performing OAuth2 authentication flow during syncing and Galaxy exporting. See [PR 1432](https://github.com/phac-nml/irida/pull/1432) ## [22.09.6] - 2022/12/21 * [UI]: Fixed bug on NCBI Export page preventing the export from occuring. See [PR 1439](https://github.com/phac-nml/irida/pull/1439)