package org.fluxtream.mvc.controllers; import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; import org.apache.oltu.oauth2.as.request.OAuthTokenRequest; import org.apache.oltu.oauth2.as.response.OAuthASResponse; import org.apache.oltu.oauth2.common.error.OAuthError; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.common.message.types.GrantType; import org.apache.oltu.oauth2.common.message.types.ResponseType; import org.apache.oltu.oauth2.common.message.types.TokenType; import org.fluxtream.core.auth.AuthHelper; import org.fluxtream.core.domain.Guest; import org.fluxtream.core.domain.oauth2.Application; import org.fluxtream.core.domain.oauth2.AuthorizationCode; import org.fluxtream.core.domain.oauth2.AuthorizationCodeResponse; import org.fluxtream.core.domain.oauth2.AuthorizationToken; import org.fluxtream.core.services.OAuth2MgmtService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; /** * User: candide * Date: 08/04/14 * Time: 14:26 */ @Controller @RequestMapping("/auth/oauth2") public class OAuth2ProviderController { @Autowired OAuth2MgmtService oAuth2MgmtService; @RequestMapping( value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST}) public @ResponseBody String receiveAuthorizationCodeRequest(final HttpServletRequest request, final HttpServletResponse response) throws IOException, OAuthSystemException { // Create the OAuth request from the HTTP request. OAuthAuthzRequest oauthRequest; try { oauthRequest = new OAuthAuthzRequest(request); } // The request does not conform to the RFC, so we return a HTTP 400 // with a reason. catch (OAuthProblemException e) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Validate that the user is requesting a "code" response type, which // is the only response type we accept. try { if (!ResponseType.CODE.toString().equals(oauthRequest.getResponseType())) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.CodeResponse.UNSUPPORTED_RESPONSE_TYPE) .setErrorDescription("The response type must be '" + ResponseType.CODE.toString() + "' but was instead: " + oauthRequest.getResponseType()) .setState(oauthRequest.getState()) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } } catch (IllegalArgumentException e) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.CodeResponse.UNSUPPORTED_RESPONSE_TYPE) .setErrorDescription("The response type is unknown: " + oauthRequest.getResponseType()) .setState(oauthRequest.getState()) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Make sure a redirect URI was given. if (oauthRequest.getRedirectURI() == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.CodeResponse.INVALID_REQUEST) .setErrorDescription("A redirect URI must be given.") .setState(oauthRequest.getState()) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Attempt to get the third-party. Application application = oAuth2MgmtService.getApplicationForClientId(oauthRequest.getClientId()); // If the third-party is unknown, reject the request. if (application == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST).setError (OAuthError.CodeResponse.INVALID_REQUEST).setErrorDescription( "The client ID is unknown: " + oauthRequest.getClientId() ).setState(oauthRequest.getState()).buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Create the temporary code to be granted or rejected by the user. AuthorizationCode code = oAuth2MgmtService.issueAuthorizationCode(application.getId(), oauthRequest.getScopes(), oauthRequest.getState()); // Set the redirect. response.sendRedirect(OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND) .setCode(code.code) .location("Authorize.html") //.setScope(scopeBuilder.toString()) .setParam("name", application.name) .setParam("description", application.description) .setParam("redirectUri", oauthRequest.getRedirectURI()) .buildQueryMessage().getLocationUri() ); // Since we are redirecting the user, we don't need to return anything. return null; } @RequestMapping(value = "Authorize.html") public ModelAndView getAuthorizeForm(@RequestParam(value="code",required=true) String code, @RequestParam(value="name",required=true) String name, @RequestParam(value="description",required=true) String description, @RequestParam(value="redirectUri",required=true) String redirectUri) { final ModelAndView mav = new ModelAndView("oauth2/Authorize"); mav.addObject("code", code); mav.addObject("name", name); mav.addObject("description", description); mav.addObject("redirectUri", redirectUri); return mav; } @RequestMapping(value = "/authorization", method = RequestMethod.POST) public void authenticateAuthorizationCodeRequest( @RequestParam(value = "granted", required = true) final boolean granted, @RequestParam(value = "redirectUri", required = true) final String redirectUri, @RequestParam(value = "code", required = true) final String code, final HttpServletRequest request, final HttpServletResponse response) throws IOException, OAuthSystemException { // Get the user. If the user's credentials are invalid for whatever // reason, an exception will be thrown and the page will echo back the // reason. Guest guest = AuthHelper.getGuest(); // Get the authorization code. AuthorizationCode authCode = oAuth2MgmtService.getCode(code); // If the code is unknown, we cannot redirect back to the third-party // because we don't know who they are. if (authCode == null) { throw new RuntimeException("The authorization code is unknown."); } // Verify that the code has not yet expired. if (System.currentTimeMillis() > authCode.expirationTime) { response.sendRedirect(OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.CodeResponse.ACCESS_DENIED) .setErrorDescription("The code has expired.") .location(redirectUri).setState(authCode.state).buildQueryMessage() .getLocationUri() ); return; } // Get the response if it already exists. AuthorizationCodeResponse codeResponse = oAuth2MgmtService.getResponse(code); // If the response does not exist, attempt to create a new one and // save it. if (codeResponse == null) { // Create the new code. codeResponse = new AuthorizationCodeResponse(authCode, guest.getId(), granted); // Store it. oAuth2MgmtService.storeVerification(codeResponse); } // Make sure it is being verified by the same user. else if (!guest.getId().equals(codeResponse.guestId)) { response.sendRedirect(OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.CodeResponse.ACCESS_DENIED) .setErrorDescription("The code has already been verified by another user.") .location(redirectUri).setState(authCode.state) .buildQueryMessage() .getLocationUri() ); } // Make sure the same grant response is being made. else if (granted == codeResponse.granted) { response.sendRedirect(OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.CodeResponse.ACCESS_DENIED) .setErrorDescription("The user has re-submitted the same authorization code twice with competing grant values.") .location(redirectUri) .setState(authCode.state) .buildQueryMessage() .getLocationUri() ); } // Otherwise, this is simply a repeat of the same request as before, // and we can simply ignore it. // Redirect the user back to the third-party with the authorization // code and state. response.sendRedirect(OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_OK).location(redirectUri) .setCode(authCode.code) .setParam("state", authCode.state) .buildQueryMessage() .getLocationUri() ); } @RequestMapping(value = "/token", method = RequestMethod.POST) public @ResponseBody String createAuthorizationToken(final HttpServletRequest request, final HttpServletResponse response) throws OAuthSystemException, IOException { // Attempt to build an OAuth request from the HTTP request. OAuthTokenRequest oauthRequest; try { oauthRequest = new OAuthTokenRequest(request); } // If the HTTP request was not a valid OAuth token request, then we // have no other choice but to reject it as a bad request. catch (OAuthProblemException e) { // Build the OAuth response. OAuthResponse oauthResponse = OAuthResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e) .buildJSONMessage(); // Set the HTTP response status code from the OAuth response. response.setStatus(oauthResponse.getResponseStatus()); // Return the error message. return oauthResponse.getBody(); } // Attempt to get the client. Application application = oAuth2MgmtService.getApplicationForClientId(oauthRequest.getClientId()); // If the client is unknown, respond as such. if (application == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription("The client is unknown: " + oauthRequest.getClientId()) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Get the given client secret. String applicationSecret = oauthRequest.getClientSecret(); if (applicationSecret == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription("The client secret is required.") .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Make sure the client gave the right secret. else if (!applicationSecret.equals(application.sharedSecret)) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription("The client secret is incorrect.") .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Get the grant-type. GrantType grantType; String grantTypeString = oauthRequest.getGrantType(); if (GrantType.AUTHORIZATION_CODE.toString().equals(grantTypeString)) { grantType = GrantType.AUTHORIZATION_CODE; } else if (GrantType.CLIENT_CREDENTIALS.toString().equals(grantTypeString)) { grantType = GrantType.CLIENT_CREDENTIALS; } else if (GrantType.PASSWORD.toString().equals(grantTypeString)) { grantType = GrantType.PASSWORD; } else if (GrantType.REFRESH_TOKEN.toString().equals(grantTypeString)) { grantType = GrantType.REFRESH_TOKEN; } else { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_GRANT) .setErrorDescription("The grant type is unknown: " + grantTypeString) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Handle the different types of token requests. AuthorizationToken token; if (GrantType.AUTHORIZATION_CODE.equals(grantType)) { // Attempt to get the code. String codeString = oauthRequest.getCode(); if (codeString == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("An authorization code must be given to be exchanged for an authorization token.") .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Attempt to lookup the actual AuthorizationCode object. AuthorizationCode code = oAuth2MgmtService.getCode(codeString); // If the code doesn't exist, reject the request. if (code == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("The given authorization code is unknown: " + codeString) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Verify that the client asking for a token is the same as the one // that requested the code. if (code.applicationId != application.getId()) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("This client is not allowed to reference this code: " + codeString) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // If the code has expired, reject the request. if (System.currentTimeMillis() > code.expirationTime) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("The given authorization code has expired: " + codeString) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Use the code to lookup the response information and error out if // a user has not yet verified it. AuthorizationCodeResponse codeResponse = oAuth2MgmtService.getResponse(code.code); if (codeResponse == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("A user has not yet verified the code: " + codeString) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Determine if the user granted access and, if not, error out. if (!codeResponse.granted) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("The user denied the authorization: " + codeString) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Create a new token. token = new AuthorizationToken(codeResponse); } // Handle a third-party refreshing an existing token. else if (GrantType.REFRESH_TOKEN.equals(grantType)) { // Get the refresh token from the request. String refreshToken = oauthRequest.getRefreshToken(); if (refreshToken == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("A refresh token must be given to be exchanged for a new authorization token.") .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Use the refresh token to lookup the actual refresh token. AuthorizationToken currentToken = oAuth2MgmtService.getTokenFromRefreshToken(refreshToken); if (currentToken == null) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("The refresh token is unknown.") .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Verify that the client asking for a token is the same as the one // that was issued the refresh token. // This is probably a very serious offense and should probably // raise some serious red flags! if (!oAuth2MgmtService.getApplicationForToken(currentToken).getId().equals(application.getId())) { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_REQUEST) .setErrorDescription("This token does not belong to this client.") .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Create a new authorization token from the current one. token = new AuthorizationToken(currentToken); } // If the grant-type is unknown, then we do not yet understand how // the request is built and, therefore, can do nothing more than // reject it via an OmhException. else { // Create the OAuth response. OAuthResponse oauthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE) .setErrorDescription("The grant type must be one of '" + GrantType.AUTHORIZATION_CODE.toString() + "' or '" + GrantType.REFRESH_TOKEN.toString() + "': " + grantType.toString()) .buildJSONMessage(); // Set the status and return the error message. response.setStatus(oauthResponse.getResponseStatus()); return oauthResponse.getBody(); } // Store the new token. oAuth2MgmtService.storeToken(token); // Build the response. OAuthResponse oauthResponse = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(token.accessToken) .setExpiresIn(Long.valueOf(token.getExpirationIn() / 1000).toString()) .setRefreshToken(token.refreshToken) .setTokenType(TokenType.BEARER.toString()) .buildJSONMessage(); // Set the status. response.setStatus(oauthResponse.getResponseStatus()); // Set the content-type. response.setContentType("application/json"); // Add the headers. Map<String, String> headers = oauthResponse.getHeaders(); for (String headerKey : headers.keySet()) { response.addHeader(headerKey, headers.get(headerKey)); } // Return the body. return oauthResponse.getBody(); } }