All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.trajano.openidconnect.jaspic.OpenIdConnectAuthModule Maven / Gradle / Ivy

The newest version!
package net.trajano.openidconnect.jaspic;

import static net.trajano.openidconnect.core.OpenIdConnectKey.CLIENT_ID;
import static net.trajano.openidconnect.core.OpenIdConnectKey.CLIENT_SECRET;
import static net.trajano.openidconnect.core.OpenIdConnectKey.GRANT_TYPE;
import static net.trajano.openidconnect.core.OpenIdConnectKey.REDIRECT_URI;
import static net.trajano.openidconnect.core.OpenIdConnectKey.RESPONSE_MODE;
import static net.trajano.openidconnect.core.OpenIdConnectKey.SCOPE;
import static net.trajano.openidconnect.jaspic.internal.Utils.isHeadRequest;
import static net.trajano.openidconnect.jaspic.internal.Utils.isNullOrEmpty;
import static net.trajano.openidconnect.jaspic.internal.Utils.isRetrievalRequest;
import static net.trajano.openidconnect.jaspic.internal.Utils.validateIdToken;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.UserPrincipal;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.crypto.SecretKey;
import javax.json.JsonObject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import net.trajano.openidconnect.auth.AuthenticationRequest;
import net.trajano.openidconnect.auth.ResponseMode;
import net.trajano.openidconnect.auth.ResponseType;
import net.trajano.openidconnect.core.OpenIdConnectKey;
import net.trajano.openidconnect.core.OpenIdProviderConfiguration;
import net.trajano.openidconnect.crypto.Encoding;
import net.trajano.openidconnect.crypto.JsonWebAlgorithm;
import net.trajano.openidconnect.crypto.JsonWebTokenBuilder;
import net.trajano.openidconnect.crypto.JsonWebTokenProcessor;
import net.trajano.openidconnect.jaspic.internal.CipherUtil;
import net.trajano.openidconnect.jaspic.internal.Log;
import net.trajano.openidconnect.jaspic.internal.NullHostnameVerifier;
import net.trajano.openidconnect.jaspic.internal.NullX509TrustManager;
import net.trajano.openidconnect.jaspic.internal.TokenCookie;
import net.trajano.openidconnect.jaspic.internal.ValidateContext;
import net.trajano.openidconnect.jaspic.internal.ValidateRequestProcessor;
import net.trajano.openidconnect.jaspic.internal.ValidateRequestProcessors;
import net.trajano.openidconnect.rs.JsonWebKeyProvider;
import net.trajano.openidconnect.rs.JsonWebKeySetProvider;
import net.trajano.openidconnect.token.GrantType;
import net.trajano.openidconnect.token.IdTokenResponse;

/**
 * OpenID Connect Server Auth Module. This uses OpenID Connect Discovery to
 * configure the OAuth 2.0 Login.
 * 

* OAuth 2.0 server authentication module. This is an implementation of the * OAuth 2.0 authentication * framework. This assumes no HttpSessions which makes it useful for RESTful * applications and uses the OAuth token to manage the authentication state. The * e-mail addresses are not requested. *

* * @author Archimedes Trajano */ public class OpenIdConnectAuthModule implements ServerAuthModule, ServerAuthContext { /** * Access token attribute name. */ public static final String ACCESS_TOKEN_KEY = "auth_access"; /** * Cookie context option key. The value is optional. */ public static final String COOKIE_CONTEXT_KEY = "cookie_context"; /** * Disable HTTP certificate checks key. This this is set to true, the auth * module will disable HTTPS certificate checks for the REST client * connections. This should only be used in development. */ public static final String DISABLE_CERTIFICATE_CHECKS_KEY = "disable_certificate_checks"; /** * https prefix. */ protected static final String HTTPS_PREFIX = "https://"; /** * Open ID token attribute name. */ public static final String ID_TOKEN_KEY = "auth_idtoken"; /** * Issuer URI option key. */ public static final String ISSUER_URI_KEY = "issuer_uri"; /** * Logger for configuration. */ protected static final Logger LOGCONFIG; /** * URI to go to when the user has logged out relative to the context path. */ public static final String LOGOUT_GOTO_URI_KEY = "logout_goto_uri"; public static final String LOGOUT_URI_KEY = "logout_uri"; /** * Age cookie name. The value of this cookie is an encrypted version of the * IP Address and will expire based on the max age of the token. */ public static final String NET_TRAJANO_AUTH_AGE = "net.trajano.oidc.age"; /** * ID token cookie name. This one expires when the browser closes. */ public static final String NET_TRAJANO_AUTH_ID = "net.trajano.oidc.id"; /** * Nonce cookie name. This one expires when the browser closes. */ public static final String NET_TRAJANO_AUTH_NONCE = "net.trajano.oidc.nonce"; /** * Redirection endpoint URI key. The value is optional and defaults to the * context root of the application. */ public static final String REDIRECTION_ENDPOINT_URI_KEY = "redirection_endpoint"; //$NON-NLS-1$ /** * Refresh token attribute name. */ public static final String REFRESH_TOKEN_KEY = "auth_refresh"; /** * Token URI key. The value is optional and if not specified, the token * request functionality will not be available. */ public static final String TOKEN_URI_KEY = "token_uri"; /** * User info attribute name. */ public static final String USERINFO_KEY = "auth_userinfo"; /** * User Info URI key. The value is optional and if not specified, the * userinfo request functionality will not be available. */ public static final String USERINFO_URI_KEY = "userinfo_uri"; static { LOGCONFIG = Logger.getLogger("net.trajano.oidc.jaspic.config", "META-INF/Messages"); } /** * Client ID. This is set through {@value #CLIENT_ID_KEY} option. */ private String clientId; /** * Client secret. This is set through {@value #CLIENT_SECRET_KEY} option. */ private String clientSecret; /** * Cookie context path. Set through through "cookie_context" option. This is * optional. */ private String cookieContext; /** * Callback handler. */ private CallbackHandler handler; /** * Flag to indicate that authentication is mandatory. */ private boolean mandatory; /** * Options for the module. */ private Map moduleOptions; /** * Randomizer used for nonce generation. It does not need to be * cryptographically secure. */ private final Random random = new SecureRandom(); /** * Redirection endpoint URI. This is set through "redirection_endpoint" * option. This must start with a forward slash. This value is optional. */ private String redirectionEndpointUri; /** * Response mode used by the module. */ private ResponseMode responseMode = ResponseMode.query; /** * REST Client. This is not final so a different one can be put in for * testing. */ private Client restClient; /** * Scope. */ private String scope; /** * Secret key used for module level ciphers. */ private SecretKey secret; /** * Token URI. This is set through "token_uri" option. This must start with a * forward slash. This value is optional. The calling the token URI will * return the contents of the JWT token object to the user. Make sure that * this is intended before setting the value. */ private String tokenUri; /** * User info URI. This is set through "userinfo_uri" option. This must start * with a forward slash. This value is optional. The calling the user info * URI will return the contents of the user info object to the user. Make * sure that this is intended before setting the value. */ private String userInfoUri; /** * Builds a REST client that bypasses SSL security checks. Made public so it * can be used for testing. * * @return JAX-RS client. */ public Client buildUnsecureRestClient() throws GeneralSecurityException { final SSLContext context = SSLContext.getInstance("TLSv1"); final TrustManager[] trustManagerArray = { NullX509TrustManager.INSTANCE }; context.init(null, trustManagerArray, null); return ClientBuilder.newBuilder() .hostnameVerifier(NullHostnameVerifier.INSTANCE) .sslContext(context) .build(); } /** * Cleans off the user principal and group principal from the subject. * * @param messageInfo * message info * @param subject * subject */ @Override public void cleanSubject(final MessageInfo messageInfo, final Subject subject) throws AuthException { final Iterator i = subject.getPrincipals() .iterator(); while (i.hasNext()) { final Principal principal = i.next(); if (principal instanceof UserPrincipal) { i.remove(); } if (principal instanceof GroupPrincipal) { i.remove(); } } } /** * Removes authentication cookies. * * @param resp * HTTP servlet response */ private void deleteAuthCookies(final HttpServletResponse resp) { for (final String cookieName : new String[] { NET_TRAJANO_AUTH_ID, NET_TRAJANO_AUTH_AGE }) { final Cookie deleteCookie = new Cookie(cookieName, ""); deleteCookie.setMaxAge(0); deleteCookie.setSecure(true); deleteCookie.setPath(cookieContext); resp.addCookie(deleteCookie); } } /** * Gets the ID token. This ensures that both cookies are present, if not * then this will return null. * * @param req * HTTP servlet request * @return ID token * @throws GeneralSecurityException * @throws IOException */ private String getIdToken(final HttpServletRequest req) throws GeneralSecurityException, IOException { final Cookie[] cookies = req.getCookies(); if (cookies == null) { return null; } String idToken = null; boolean foundAge = false; for (final Cookie cookie : cookies) { if (NET_TRAJANO_AUTH_ID.equals(cookie.getName()) && !isNullOrEmpty(cookie.getValue())) { idToken = cookie.getValue(); } else if (NET_TRAJANO_AUTH_AGE.equals(cookie.getName())) { final String remoteAddr = req.getRemoteAddr(); final String cookieAddr = new String(CipherUtil.decrypt(Encoding.base64urlDecode(cookie.getValue()), secret), "US-ASCII"); if (!remoteAddr.equals(cookieAddr)) { throw new AuthException(MessageFormat.format(Log.r("ipaddressMismatch"), remoteAddr, cookieAddr)); } foundAge = true; } if (idToken != null && foundAge) { return idToken; } } return null; } /** * Lets subclasses change the provider configuration. * * @param req * request message * @param client * REST client * @param options * module options * @return configuration * @throws AuthException * wraps exceptions thrown during processing */ protected OpenIdProviderConfiguration getOpenIDProviderConfig(final HttpServletRequest req, final Client restClient, final Map options) throws AuthException { final String issuerUri = options.get(ISSUER_URI_KEY); if (issuerUri == null) { Log.severe("missingOption", ISSUER_URI_KEY); throw new AuthException(MessageFormat.format(Log.r("missingOption"), ISSUER_URI_KEY)); } return restClient.target(URI.create(issuerUri) .resolve("/.well-known/openid-configuration")) .request(MediaType.APPLICATION_JSON_TYPE) .get(OpenIdProviderConfiguration.class); } /** * This gets the redirection endpoint URI. It uses the * {@link #REDIRECTION_ENDPOINT_URI_KEY} option resolved against the request * URL to get the host name. * * @param req * request * @return redirection endpoint URI. */ protected URI getRedirectionEndpointUri(final HttpServletRequest req) { return URI.create(req.getRequestURL() .toString()) .resolve(redirectionEndpointUri); } /** * Gets an option and ensures it is present. * * @param optionKey * option key * @return the option value * @throws AuthException * missing option exception */ private String getRequiredOption(final String optionKey) throws AuthException { final String optionValue = moduleOptions.get(optionKey); if (optionValue == null) { Log.severe("missingOption", optionKey); throw new AuthException(MessageFormat.format(Log.r("missingOption"), optionKey)); } return optionValue; } /** * Calculate the value for state based on the current request. * * @param req * @return */ private String getState(final HttpServletRequest req) { final StringBuilder stateBuilder = new StringBuilder(req.getRequestURI() .substring(req.getContextPath() .length())); if (req.getQueryString() != null) { stateBuilder.append('?'); stateBuilder.append(req.getQueryString()); } return Encoding.base64urlEncode(stateBuilder.toString()); } /** *

* Supported message types. For our case we only need to deal with HTTP * servlet request and responses. On Java EE 7 this will handle WebSockets * as well. *

*

* This creates a new array for security at the expense of performance. *

* * @return {@link HttpServletRequest} and {@link HttpServletResponse} * classes. */ @SuppressWarnings("rawtypes") @Override public Class[] getSupportedMessageTypes() { return new Class[] { HttpServletRequest.class, HttpServletResponse.class }; } /** * Sends a request to the token endpoint to get the token for the code. * * @param req * servlet request * @param oidProviderConfig * OpenID provider config * @return token response */ protected IdTokenResponse getTokenViaRefresh(final HttpServletRequest req, final String refreshToken, final OpenIdProviderConfiguration oidProviderConfig) throws IOException { final MultivaluedMap requestData = new MultivaluedHashMap<>(); requestData.putSingle(OpenIdConnectKey.REFRESH_TOKEN, refreshToken); requestData.putSingle(GRANT_TYPE, GrantType.refresh_token.name()); requestData.putSingle(REDIRECT_URI, getRedirectionEndpointUri(req).toASCIIString()); try { final String authorization = "Basic " + Encoding.base64Encode(clientId + ":" + clientSecret); final IdTokenResponse authorizationTokenResponse = restClient.target(oidProviderConfig.getTokenEndpoint()) .request(MediaType.APPLICATION_JSON_TYPE) .header("Authorization", authorization) .post(Entity.form(requestData), IdTokenResponse.class); if (Log.isFinestLoggable()) { Log.getInstance() .finest("authorization token response = " + authorizationTokenResponse); } return authorizationTokenResponse; } catch (final BadRequestException e) { // workaround for google that does not support BASIC authentication // on their endpoint. requestData.putSingle(CLIENT_ID, clientId); requestData.putSingle(CLIENT_SECRET, clientSecret); final IdTokenResponse authorizationTokenResponse = restClient.target(oidProviderConfig.getTokenEndpoint()) .request(MediaType.APPLICATION_JSON_TYPE) .post(Entity.form(requestData), IdTokenResponse.class); if (Log.isFinestLoggable()) { Log.getInstance() .finest("authorization token response = " + authorizationTokenResponse); } return authorizationTokenResponse; } } /** * Gets the web keys from the options and the OpenID provider configuration. * This may be overridden by clients. * * @param config * provider configuration * @return web keys * @throws GeneralSecurityException * wraps exceptions thrown during processing */ private net.trajano.openidconnect.crypto.JsonWebKeySet getWebKeys(final OpenIdProviderConfiguration config) throws GeneralSecurityException { return restClient.target(config.getJwksUri()) .request(MediaType.APPLICATION_JSON_TYPE) .get(net.trajano.openidconnect.crypto.JsonWebKeySet.class); } /** * Workaround for the issuer value for Google. This was documented in * 15.6.2. of the spec. In which case if the issuer does not start with * https:// it will prepend it. * * @param issuer * issuer * @return updated issuer */ private String googleWorkaround(final String issuer) { if (issuer.startsWith(HTTPS_PREFIX)) { return issuer; } return HTTPS_PREFIX + issuer; } /** * {@inheritDoc} * * @param requestPolicy * request policy, ignored * @param responsePolicy * response policy, ignored * @param h * callback handler * @param options * options */ @SuppressWarnings("unchecked") @Override public void initialize(final MessagePolicy requestPolicy, final MessagePolicy responsePolicy, final CallbackHandler h, @SuppressWarnings("rawtypes") final Map options) throws AuthException { try { moduleOptions = options; clientId = getRequiredOption(CLIENT_ID); cookieContext = moduleOptions.get(COOKIE_CONTEXT_KEY); redirectionEndpointUri = getRequiredOption(REDIRECTION_ENDPOINT_URI_KEY); tokenUri = moduleOptions.get(TOKEN_URI_KEY); userInfoUri = moduleOptions.get(USERINFO_URI_KEY); scope = moduleOptions.get(SCOPE); if (isNullOrEmpty(scope)) { scope = "openid"; } clientSecret = getRequiredOption(CLIENT_SECRET); LOGCONFIG.log(Level.CONFIG, "options", moduleOptions); final String responseModeIn = moduleOptions.get(OpenIdConnectKey.RESPONSE_MODE); if (responseModeIn != null) { responseMode = ResponseMode.valueOf(responseModeIn); } handler = h; mandatory = requestPolicy.isMandatory(); secret = CipherUtil.buildSecretKey(clientId, clientSecret); if (restClient == null) { if (moduleOptions.get(DISABLE_CERTIFICATE_CHECKS_KEY) != null && Boolean.valueOf(moduleOptions.get(DISABLE_CERTIFICATE_CHECKS_KEY))) { restClient = buildUnsecureRestClient(); } else { restClient = ClientBuilder.newClient(); } restClient.register(JsonWebKeySetProvider.class) .register(JsonWebKeyProvider.class); } } catch (final Exception e) { // Should not happen Log.severe("initializeException", e); throw new AuthException(MessageFormat.format(Log.r("initializeException"), e.getMessage())); } } /** * Generate the next nonce. * * @return nonce */ private String nextNonce() { final byte[] bytes = new byte[8]; random.nextBytes(bytes); return Encoding.base64urlEncode(bytes); } /** * Builds the token cookie and updates the subject principal and sets the * token and user info attribute in the request. Any exceptions or * validation problems during validation will make this return * null to indicate that there was no valid token. * * @param subject * subject * @param req * servlet request * @return token cookie. */ private TokenCookie processTokenCookie(final Subject subject, final HttpServletRequest req, final HttpServletResponse resp) { try { final String idToken = getIdToken(req); TokenCookie tokenCookie = null; if (idToken != null) { tokenCookie = new TokenCookie(idToken, secret); if (tokenCookie.isExpired() && tokenCookie.getRefreshToken() != null) { final OpenIdProviderConfiguration oidProviderConfig = getOpenIDProviderConfig(req, restClient, moduleOptions); final IdTokenResponse token = getTokenViaRefresh(req, tokenCookie.getRefreshToken(), oidProviderConfig); final net.trajano.openidconnect.crypto.JsonWebKeySet webKeys = getWebKeys(oidProviderConfig); final JsonObject claimsSet = new JsonWebTokenProcessor(token.getEncodedIdToken()).jwks(webKeys) .getJsonPayload(); final String iss = googleWorkaround(claimsSet.getString("iss")); final String issuer = googleWorkaround(oidProviderConfig.getIssuer()); if (!iss.equals(issuer)) { Log.severe("issuerMismatch", iss, issuer); throw new GeneralSecurityException(Log.r("issuerMismatch", iss, issuer)); } updateSubjectPrincipal(subject, claimsSet); if (oidProviderConfig.getUserinfoEndpoint() != null && Pattern.compile("\\bprofile\\b") .matcher(scope) .find()) { final Response userInfoResponse = restClient.target(oidProviderConfig.getUserinfoEndpoint()) .request(MediaType.APPLICATION_JSON_TYPE) .header("Authorization", token.getTokenType() + " " + token.getAccessToken()) .get(); if (userInfoResponse.getStatus() == 200) { tokenCookie = new TokenCookie(token.getAccessToken(), token.getRefreshToken(), claimsSet, token.getEncodedIdToken(), userInfoResponse.readEntity(JsonObject.class)); } else { Log.getInstance() .log(Level.WARNING, "unableToGetProfile"); tokenCookie = new TokenCookie(claimsSet, token.getEncodedIdToken()); } } else { tokenCookie = new TokenCookie(claimsSet, token.getEncodedIdToken()); } final String requestCookieContext; if (isNullOrEmpty(cookieContext)) { requestCookieContext = req.getContextPath(); } else { requestCookieContext = cookieContext; } final Cookie idTokenCookie = new Cookie(NET_TRAJANO_AUTH_ID, tokenCookie.toCookieValue(secret)); idTokenCookie.setMaxAge(-1); idTokenCookie.setSecure(true); idTokenCookie.setHttpOnly(true); idTokenCookie.setPath(requestCookieContext); resp.addCookie(idTokenCookie); } validateIdToken(clientId, tokenCookie.getIdToken(), null, tokenCookie.getAccessToken()); updateSubjectPrincipal(subject, tokenCookie.getIdToken()); req.setAttribute(ACCESS_TOKEN_KEY, tokenCookie.getAccessToken()); req.setAttribute(REFRESH_TOKEN_KEY, tokenCookie.getRefreshToken()); req.setAttribute(ID_TOKEN_KEY, tokenCookie.getIdToken()); if (tokenCookie.getUserInfo() != null) { req.setAttribute(USERINFO_KEY, tokenCookie.getUserInfo()); } } return tokenCookie; } catch (final GeneralSecurityException | IOException e) { e.printStackTrace(); Log.getInstance() .log(Level.FINE, "invalidToken", e.getMessage()); Log.getInstance() .throwing(this.getClass() .getName(), "validateRequest", e); return null; } } /** * Sends a redirect to the authorization endpoint. It sends the current * request URI as the state so that the user can be redirected back to the * last place. However, this does not work for non-idempotent requests such * as POST in those cases it will result in a 401 error and * {@link AuthStatus#SEND_FAILURE}. For idempotent requests, it will build * the redirect URI and return {@link AuthStatus#SEND_CONTINUE}. It will * also destroy the cookies used for authorization as part of the response. *

* It stores an encrypted nonce in the cookies and uses it to verify the * nonce value later. *

* * @param req * HTTP servlet request * @param resp * HTTP servlet response * @param reason * reason for redirect (used for logging) * @return authentication status * @throws AuthException */ private AuthStatus redirectToAuthorizationEndpoint(final HttpServletRequest req, final HttpServletResponse resp, final String reason) throws AuthException { Log.fine("redirecting", reason); URI authorizationEndpointUri = null; try { final OpenIdProviderConfiguration oidProviderConfig = getOpenIDProviderConfig(req, restClient, moduleOptions); final String state = getState(req); final String requestCookieContext; if (isNullOrEmpty(cookieContext)) { requestCookieContext = req.getContextPath(); } else { requestCookieContext = cookieContext; } final String nonce = nextNonce(); final Cookie nonceCookie = new Cookie(NET_TRAJANO_AUTH_NONCE, Encoding.base64urlEncode(CipherUtil.encrypt(nonce.getBytes(), secret))); nonceCookie.setMaxAge(-1); nonceCookie.setPath(requestCookieContext); nonceCookie.setHttpOnly(true); nonceCookie.setSecure(true); resp.addCookie(nonceCookie); final URI redirectUri = URI.create(req.getRequestURL() .toString()) .resolve(moduleOptions.get(REDIRECTION_ENDPOINT_URI_KEY)); final AuthenticationRequest ab = new AuthenticationRequest.Builder().clientId(clientId) .scope(scope) .redirectUri(redirectUri) .responseType(ResponseType.code) .state(state) .nonce(nonce) .uiLocale(req.getLocales()) .responseMode(responseMode) .build(); final UriBuilder b = UriBuilder.fromUri(oidProviderConfig.getAuthorizationEndpoint()); if (oidProviderConfig.isRequestParameterSupported()) { final String kex = JsonWebAlgorithm.getFirstMatchingKexAlgorithm(oidProviderConfig.getRequestObjectEncryptionAlgValuesSupported()); final String enc = JsonWebAlgorithm.getFirstMatchingEncAlgorithm(oidProviderConfig.getRequestObjectEncryptionEncValuesSupported()); if (kex == null) { throw new AuthException("no matching kex with provider"); } if (enc == null) { throw new AuthException("no matching env with provider"); } final JsonWebTokenBuilder jwtBuilder = new JsonWebTokenBuilder().alg(kex) .enc(enc) .compress(true) .jwk(getWebKeys(oidProviderConfig)) .payload(ab.toJsonObject()); b.queryParam(OpenIdConnectKey.REQUEST, jwtBuilder.build() .toString()); } else { ab.addQueryParams(b); if (responseMode != ResponseMode.query) { b.queryParam(RESPONSE_MODE, responseMode.toString()); } } authorizationEndpointUri = b.build(); deleteAuthCookies(resp); resp.sendRedirect(authorizationEndpointUri.toASCIIString()); return AuthStatus.SEND_CONTINUE; } catch (final IOException | GeneralSecurityException e) { // Should not happen Log.getInstance() .log(Level.SEVERE, "sendRedirectException", new Object[] { authorizationEndpointUri, e.getMessage() }); Log.getInstance() .throwing(this.getClass() .getName(), "redirectToAuthorizationEndpoint", e); throw new AuthException(MessageFormat.format(Log.r("sendRedirectException"), authorizationEndpointUri, e.getMessage())); } } /** * Return {@link AuthStatus#SEND_SUCCESS}. * * @param messageInfo * contains the request and response messages. At this point the * response message is already committed so nothing can be * changed. * @param subject * subject. * @return {@link AuthStatus#SEND_SUCCESS} */ @Override public AuthStatus secureResponse(final MessageInfo messageInfo, final Subject subject) throws AuthException { return AuthStatus.SEND_SUCCESS; } /** * Override REST client for testing. * * @param restClient * REST client. May be mocked. */ public void setRestClient(final Client restClient) { this.restClient = restClient; } /** * Updates the principal for the subject. This is done through the * callbacks. * * @param subject * subject * @param jwtPayload * JWT payload * @throws AuthException * @throws GeneralSecurityException */ private void updateSubjectPrincipal(final Subject subject, final JsonObject jwtPayload) throws GeneralSecurityException { try { final String iss = googleWorkaround(jwtPayload.getString("iss")); handler.handle(new Callback[] { new CallerPrincipalCallback(subject, UriBuilder.fromUri(iss) .userInfo(jwtPayload.getString("sub")) .build() .toASCIIString()), new GroupPrincipalCallback(subject, new String[] { iss }) }); } catch (final IOException | UnsupportedCallbackException e) { // Should not happen Log.getInstance() .log(Level.SEVERE, "updatePrincipalException", e.getMessage()); Log.getInstance() .throwing(this.getClass() .getName(), "updateSubjectPrincipal", e); throw new AuthException(MessageFormat.format(Log.r("updatePrincipalException"), e.getMessage())); } } /** * Validates the request. The request must be secure otherwise it will * return {@link AuthStatus#FAILURE}. It then tries to build the token * cookie data if available, if the token is valid, subject is set correctly * and user info data if present is stored in the request, then call HTTP * method specific operations. * * @param messageInfo * request and response * @param clientSubject * client subject * @param serviceSubject * service subject, ignored. * @return Auth status */ @Override public AuthStatus validateRequest(final MessageInfo messageInfo, final Subject clientSubject, final Subject serviceSubject) throws AuthException { final HttpServletRequest req = (HttpServletRequest) messageInfo.getRequestMessage(); final HttpServletResponse resp = (HttpServletResponse) messageInfo.getResponseMessage(); try { final TokenCookie tokenCookie = processTokenCookie(clientSubject, req, resp); final ValidateContext context = new ValidateContext(restClient, clientSubject, mandatory, moduleOptions, req, resp, tokenCookie, cookieContext, handler); final ValidateRequestProcessor requestProcessor = ValidateRequestProcessors.getInstance(); final AuthStatus status = requestProcessor.validateRequest(context); if (status != null) { return status; } if (mandatory && tokenCookie != null && tokenCookie.isExpired()) { return redirectToAuthorizationEndpoint(req, resp, "token cookie is expired"); } if (mandatory && tokenCookie == null) { return redirectToAuthorizationEndpoint(req, resp, "token cookie is missing"); } if (req.isSecure() && isHeadRequest(req) && req.getRequestURI() .equals(tokenUri)) { resp.setContentType(MediaType.APPLICATION_JSON); return AuthStatus.SEND_SUCCESS; } if (req.getRequestURI() .equals(userInfoUri) && isHeadRequest(req)) { resp.setContentType(MediaType.APPLICATION_JSON); return AuthStatus.SEND_SUCCESS; } if (!isRetrievalRequest(req)) { resp.sendError(HttpURLConnection.HTTP_FORBIDDEN, "Unable to POST when unauthorized."); return AuthStatus.SEND_FAILURE; } return redirectToAuthorizationEndpoint(req, resp, "request is not valid"); } catch (final AuthException e) { // Any problems with the data should be caught and force redirect to // authorization endpoint. Log.getInstance() .log(Level.FINE, "validationException", e.getMessage()); Log.getInstance() .throwing(this.getClass() .getName(), "validateRequest", e); return AuthStatus.FAILURE; } catch (final IOException e) { // Any problems with the data should be caught and force redirect to // authorization endpoint. Log.getInstance() .log(Level.FINE, "validationException", e.getMessage()); Log.getInstance() .throwing(this.getClass() .getName(), "validateRequest", e); return AuthStatus.FAILURE; } catch (final GeneralSecurityException e) { // Any problems with the data should be caught and force redirect to // authorization endpoint. Log.getInstance() .log(Level.FINE, "validationException", e.getMessage()); Log.getInstance() .throwing(this.getClass() .getName(), "validateRequest", e); return redirectToAuthorizationEndpoint(req, resp, e.getMessage()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy