/*
* 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 org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.services.organization.User;
import org.exoplatform.web.security.AuthenticationRegistry;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.security.oauth.common.OAuthConstants;
import org.gatein.security.oauth.spi.OAuthPrincipal;
import org.gatein.security.oauth.spi.OAuthPrincipalProcessor;
import org.gatein.security.oauth.spi.SocialNetworkService;
import org.gatein.security.oauth.utils.OAuthUtils;
import org.gatein.sso.agent.filter.api.AbstractSSOInterceptor;
/**
* This filter has already access to authenticated OAuth principal, so it's work starts after successful OAuth authentication.
*
* Filter is useful only for anonymous user
*
* Responsibility of this filter is to handle integration with GateIn (Redirect to GateIn registration if needed, establish context
* and redirect to JAAS to finish GateIn authentication etc)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OAuthAuthenticationFilter extends AbstractSSOInterceptor {
private static Logger log = LoggerFactory.getLogger(OAuthAuthenticationFilter.class);
private String loginUrl;
private String registrationUrl;
private boolean attachUsernamePasswordToLoginURL;
private SocialNetworkService socialNetworkService;
private AuthenticationRegistry authenticationRegistry;
@Override
protected void initImpl() {
this.loginUrl = getInitParameter("loginUrl");
this.registrationUrl = getInitParameter("registrationUrl");
if (registrationUrl == null) {
registrationUrl = "/" + getExoContainer().getContext().getName() + "/";
}
String attachUsernamePasswordToLoginURLConfig = getInitParameter("attachUsernamePasswordToLoginURL");
this.attachUsernamePasswordToLoginURL = attachUsernamePasswordToLoginURLConfig == null ? true : Boolean.parseBoolean(attachUsernamePasswordToLoginURLConfig);
log.debug("OAuthAuthenticationFilter configuration: loginURL=" + loginUrl +
", registrationUrl=" + this.registrationUrl +
", attachUsernamePasswordToLoginURL=" + this.attachUsernamePasswordToLoginURL);
socialNetworkService = getExoContainer().getComponentInstanceOfType(SocialNetworkService.class);
authenticationRegistry = getExoContainer().getComponentInstanceOfType(AuthenticationRegistry.class);
}
@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;
// Simply continue with request if we are already authenticated
if (httpRequest.getRemoteUser() != null) {
chain.doFilter(request, response);
return;
}
// Simply continue with request if we are in the middle of registration process
User oauthAuthenticatedUser = (User)authenticationRegistry.getAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER);
if (oauthAuthenticatedUser != null) {
chain.doFilter(request, response);
return;
}
OAuthPrincipal principal = (OAuthPrincipal)authenticationRegistry.getAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL);
if (principal != null) {
try {
begin();
processPrincipal(httpRequest, httpResponse, principal);
} finally {
end();
}
} else {
chain.doFilter(request, response);
}
}
protected void processPrincipal(HttpServletRequest httpRequest, HttpServletResponse httpResponse, OAuthPrincipal principal) throws IOException {
User portalUser = socialNetworkService.findUserByOAuthProviderUsername(principal.getOauthProviderType(), principal.getUserName());
if (portalUser == null) {
// This means that user has been successfully authenticated via OAuth, but doesn't exist in GateIn. So we need to establish context
// with AuthenticationRegistry and redirect to GateIn registration form
handleRedirectToRegistrationForm(httpRequest, httpResponse, principal);
} else {
// This means that user has been successfully authenticated via OAuth and exists in GateIn. So we need to establish SSO context
// and clean our own local context from AuthenticationRegistry. Then redirect to loginUrl to perform GateIn WCI login
handleRedirectToPortalLogin(httpRequest, httpResponse, portalUser, principal);
cleanAuthenticationContext(httpRequest);
}
}
protected void handleRedirectToRegistrationForm(HttpServletRequest httpRequest, HttpServletResponse httpResponse, OAuthPrincipal principal)
throws IOException {
if (log.isTraceEnabled()) {
log.trace("Not found portalUser with username " + principal.getUserName() + ". Redirecting to registration form");
}
//User gateInUser = OAuthUtils.convertOAuthPrincipalToGateInUser(principal);
OAuthPrincipalProcessor principalProcessor = principal.getOauthProviderType().getOauthPrincipalProcessor();
User gateInUser = principalProcessor.convertToGateInUser(principal);
authenticationRegistry.setAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER, gateInUser);
String registrationRedirectUrl = getRegistrationRedirectURL(httpRequest);
registrationRedirectUrl = httpResponse.encodeRedirectURL(registrationRedirectUrl);
httpResponse.sendRedirect(registrationRedirectUrl);
}
protected String getRegistrationRedirectURL(HttpServletRequest req) {
String registrationURL = (String)req.getSession().getAttribute(OAuthConstants.ATTRIBUTE_URL_TO_REDIRECT_AFTER_LINK_SOCIAL_ACCOUNT);
if (registrationURL == null) {
registrationURL = this.registrationUrl;
}
return registrationURL;
}
protected void handleRedirectToPortalLogin(HttpServletRequest httpRequest, HttpServletResponse httpResponse, User portalUser, OAuthPrincipal principal)
throws IOException {
if (log.isTraceEnabled()) {
log.trace("Found portalUser " + portalUser + " corresponding to oauthPrincipal");
}
authenticationRegistry.setAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER_FOR_JAAS, portalUser);
if(portalUser.isEnabled()) {
socialNetworkService.updateOAuthAccessToken(principal.getOauthProviderType(), portalUser.getUserName(), principal.getAccessToken());
}
// Now Facebook/Google authentication handshake is finished and credentials are in session. We can redirect to JAAS authentication
String loginRedirectURL = httpResponse.encodeRedirectURL(getLoginRedirectUrl(httpRequest, portalUser.getUserName()));
httpResponse.sendRedirect(loginRedirectURL);
}
protected String getLoginRedirectUrl(HttpServletRequest req, String username) {
StringBuilder url = new StringBuilder(this.loginUrl);
if (attachUsernamePasswordToLoginURL) {
String fakePassword = req.getSession().getId() + "_" + String.valueOf(System.currentTimeMillis());
// Use sessionId and system millis as password (similar like spnego is doing)
url.append("?username=").append(username).append("&password=").append(fakePassword);
String initialURI = OAuthUtils.getURLToRedirectAfterLinkAccount(req, req.getSession());
initialURI = OAuthUtils.encodeParam(initialURI);
url.append("&").append("initialURI").append("=").append(initialURI);
}
return url.toString();
}
protected void cleanAuthenticationContext(HttpServletRequest httpRequest) {
authenticationRegistry.removeAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_OAUTH_PRINCIPAL);
authenticationRegistry.removeAttributeOfClient(httpRequest, OAuthConstants.ATTRIBUTE_AUTHENTICATED_PORTAL_USER);
}
private void begin() {
if (socialNetworkService instanceof ComponentRequestLifecycle) {
((ComponentRequestLifecycle)socialNetworkService).startRequest(getExoContainer());
}
}
private void end() {
if (socialNetworkService instanceof ComponentRequestLifecycle) {
((ComponentRequestLifecycle)socialNetworkService).endRequest(getExoContainer());
}
}
}