/* * JBoss, a division of Red Hat * Copyright 2013, Red Hat Middleware, LLC, and individual * contributors as indicated by the @authors tag. See the * copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.gatein.security.oauth.web; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.exoplatform.container.component.ComponentRequestLifecycle; import org.exoplatform.web.security.AuthenticationRegistry; import org.gatein.common.logging.Logger; import org.gatein.common.logging.LoggerFactory; import org.gatein.security.oauth.spi.AccessTokenContext; import org.gatein.security.oauth.spi.InteractionState; import org.gatein.security.oauth.common.OAuthConstants; import org.gatein.security.oauth.spi.OAuthPrincipal; import org.gatein.security.oauth.spi.OAuthProviderProcessor; import org.gatein.security.oauth.spi.OAuthProviderType; import org.gatein.security.oauth.spi.OAuthProviderTypeRegistry; import org.gatein.security.oauth.spi.SocialNetworkService; import org.gatein.security.oauth.exception.OAuthException; import org.gatein.security.oauth.utils.OAuthUtils; import org.gatein.sso.agent.filter.api.AbstractSSOInterceptor; /** * Filter to handle OAuth interaction. This filter contains only "generic" common functionality, * which is same for all OAuth providers. For specific functionality, you need to override some methods (especially abstract methods) * * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public abstract class OAuthProviderFilter<T extends AccessTokenContext> extends AbstractSSOInterceptor { protected final Logger log = LoggerFactory.getLogger(getClass()); private AuthenticationRegistry authenticationRegistry; private OAuthProviderProcessor<T> oauthProviderProcessor; private OAuthProviderTypeRegistry oAuthProviderTypeRegistry; private SocialNetworkService socialNetworkService; protected String providerKey; @Override protected void initImpl() { this.providerKey = this.getInitParameter("providerKey"); authenticationRegistry = getExoContainer().getComponentInstanceOfType(AuthenticationRegistry.class); oAuthProviderTypeRegistry = getExoContainer().getComponentInstanceOfType(OAuthProviderTypeRegistry.class); socialNetworkService = getExoContainer().getComponentInstanceOfType(SocialNetworkService.class); oauthProviderProcessor = getOAuthProvider().getOauthProviderProcessor(); } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest)request; HttpServletResponse httpResponse = (HttpServletResponse)response; HttpSession session = httpRequest.getSession(); try { // Restart current state if 'oauthInteraction' param has value 'start' String interaction = httpRequest.getParameter(OAuthConstants.PARAM_OAUTH_INTERACTION); if (OAuthConstants.PARAM_OAUTH_INTERACTION_VALUE_START.equals(interaction)) { initInteraction(httpRequest, httpResponse); saveRememberMe(httpRequest); saveInitialURI(httpRequest); } if (socialNetworkService instanceof ComponentRequestLifecycle) { ((ComponentRequestLifecycle)socialNetworkService).startRequest(getExoContainer()); } // Possibility to init interaction with custom scope. It's needed when custom portlets want bigger scope then the one available in configuration String scopeToUse = obtainCustomScopeIfAvailable(httpRequest); InteractionState<T> interactionState; try { if (scopeToUse == null) { interactionState = getOauthProviderProcessor().processOAuthInteraction(httpRequest, httpResponse); } else { if (log.isTraceEnabled()) { log.trace("Process oauth interaction with scope: " + scopeToUse); } interactionState = getOauthProviderProcessor().processOAuthInteraction(httpRequest, httpResponse, scopeToUse); } } catch (OAuthException ex) { log.warn("Error during OAuth flow with: " + ex.getMessage()); // Save exception to session and redirect to portal. Exception will be processed later on portal side session.setAttribute(OAuthConstants.ATTRIBUTE_EXCEPTION_OAUTH, ex); redirectAfterOAuthError(httpRequest, httpResponse); return; } if (InteractionState.State.FINISH.equals(interactionState.getState())) { OAuthPrincipal<T> oauthPrincipal = getOAuthPrincipal(httpRequest, httpResponse, interactionState); if (oauthPrincipal != null) { if (httpRequest.getRemoteUser() == null) { // Save authenticated OAuthPrincipal to authenticationRegistry in case that we are anonymous user // Other filter should take care of processing it and perform GateIn login or registration authenticationRegistry.setAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL, oauthPrincipal); } else { // For authenticated user, we will save it as request attribute and process it by other filter, which will update // userProfile with new username and accessToken httpRequest.setAttribute(OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL, oauthPrincipal); } // Continue with request chain.doFilter(request, response); } } } finally { if (socialNetworkService instanceof ComponentRequestLifecycle) { ((ComponentRequestLifecycle)socialNetworkService).endRequest(getExoContainer()); } } } protected AuthenticationRegistry getAuthenticationRegistry() { return authenticationRegistry; } protected OAuthProviderProcessor<T> getOauthProviderProcessor() { return oauthProviderProcessor; } protected OAuthProviderTypeRegistry getOAuthProviderTypeRegistry() { return oAuthProviderTypeRegistry; } protected SocialNetworkService getSocialNetworkService() { return socialNetworkService; } protected String obtainCustomScopeIfAvailable(HttpServletRequest httpRequest) { // It's sufficient to use request parameter, because scope is needed only for facebookProcessor.initialInteraction String customScope = httpRequest.getParameter(OAuthConstants.PARAM_CUSTOM_SCOPE); if (customScope != null) { String currentUser = httpRequest.getRemoteUser(); if (currentUser == null) { log.warn("Parameter " + OAuthConstants.PARAM_CUSTOM_SCOPE + " found but there is no user available. Ignoring it."); return null; } else { T currentAccessToken = socialNetworkService.getOAuthAccessToken(getOAuthProvider(), currentUser); if (currentAccessToken != null) { // Add new customScope to set of existing scopes, so accessToken will be obtained for all of them currentAccessToken.addScope(customScope); return currentAccessToken.getScopesAsString(); } else { return customScope; } } } else { return null; } } protected void redirectAfterOAuthError(HttpServletRequest request, HttpServletResponse response) throws IOException { // Similar code like in OAuthLinkAccountFilter HttpSession session = request.getSession(); String urlToRedirect = OAuthUtils.getURLToRedirectAfterLinkAccount(request, session); if (log.isTraceEnabled()) { log.trace("Will redirect user to URL: " + urlToRedirect); } response.sendRedirect(response.encodeRedirectURL(urlToRedirect)); } protected void saveInitialURI(HttpServletRequest request) { String initialURI = request.getParameter(OAuthConstants.PARAM_INITIAL_URI); if (initialURI != null) { request.getSession().setAttribute(OAuthConstants.ATTRIBUTE_URL_TO_REDIRECT_AFTER_LINK_SOCIAL_ACCOUNT, initialURI); } } protected void saveRememberMe(HttpServletRequest request) { String rememberMe = request.getParameter(OAuthConstants.PARAM_REMEMBER_ME); request.getSession().setAttribute(OAuthConstants.ATTRIBUTE_REMEMBER_ME, rememberMe); } protected <T extends AccessTokenContext> OAuthProviderType<T> getOauthProvider(String defaultKey, Class<T> c) { String key = this.providerKey != null ? this.providerKey : defaultKey; return getOAuthProviderTypeRegistry().getOAuthProvider(key, c); } protected abstract OAuthProviderType<T> getOAuthProvider(); protected abstract void initInteraction(HttpServletRequest request, HttpServletResponse response); protected abstract OAuthPrincipal<T> getOAuthPrincipal(HttpServletRequest request, HttpServletResponse response, InteractionState<T> interactionState); }