/*
* JBoss, Home of Professional Open Source
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.seam.security.external.oauth;
import static com.google.common.collect.Iterables.getOnlyElement;
import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.BeanManager;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import org.jboss.seam.security.AuthenticationException;
import org.jboss.seam.security.Authenticator;
import org.jboss.seam.security.BaseAuthenticator;
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.events.DeferredAuthenticationEvent;
import org.jboss.seam.security.external.oauth.api.OAuthAuthenticator;
import org.jboss.seam.security.management.picketlink.IdentitySessionProducer;
import org.jboss.seam.social.MultiServicesManager;
import org.jboss.seam.social.SeamSocialExtension;
import org.jboss.seam.social.SocialNetworkServicesHub;
import org.jboss.seam.social.oauth.OAuthService;
import org.jboss.seam.social.oauth.OAuthSession;
import org.jboss.solder.core.Requires;
import org.jboss.solder.logging.Logger;
import org.picketlink.idm.api.Group;
import org.picketlink.idm.api.IdentitySession;
import org.picketlink.idm.api.Role;
import org.picketlink.idm.api.RoleType;
import org.picketlink.idm.api.User;
import org.picketlink.idm.common.exception.FeatureNotSupportedException;
import org.picketlink.idm.common.exception.IdentityException;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
/**
* An Authenticator implementation that uses OAuth to authenticate the user.
*
* Based on OpenIdAuthenticator from Seam Security External module.
*
* The OAuthAuthenticatorImpl has two modes of operation, depending on the value of the serviceName 1. Using the only configured
* OAuthService if serviceName is null, this will raise an IllegalStateException if there is no or more than one available @ServiceRelated
* OAuthService. 2. Using the multiServicesManager, in which case the serviceName selects the service which which to create a
* new connection. This beans exists only if Seam Social is in the classpath
*
* @author maschmid
* @author Antoine Sabot-Durand
*
*/
@Requires("org.jboss.seam.social.oauth.OAuthService")
@Named("oauthAuthenticator")
@SessionScoped
public class OAuthAuthenticatorImpl extends BaseAuthenticator implements OAuthAuthenticator, Authenticator, Serializable {
private static final long serialVersionUID = 3431696230531662201L;
@Inject
@Any
private Instance<OAuthService> serviceInstances;
private String serviceName = null;
/**
* If this property is set to true (the default) then user roles and attributes will be managed using the Identity
* Management API.
*/
private boolean identityManaged = true;
@Inject
Instance<Identity> identity;
@Inject
MultiServicesManager multiServicesManager;
@Inject
Instance<IdentitySession> identitySession;
@Inject
Instance<IdentitySessionProducer> identitySessionProducer;
@Inject
Logger log;
@Inject
SeamSocialExtension extension;
@Inject
BeanManager beanManager;
public boolean isIdentityManaged() {
return identityManaged;
}
public void setIdentityManaged(boolean identityManaged) {
this.identityManaged = identityManaged;
}
@Override
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
@Override
public String getServiceName() {
return serviceName;
}
@Override
public List<String> getListOfServices() {
return multiServicesManager.getListOfServices();
}
private OAuthService getUnambiguousService() {
// Attempts to get the only configured service
if (extension.getSocialRelated().size() == 1) {
String name = getOnlyElement(extension.getSocialRelated());
Annotation qualifier = SeamSocialExtension.getServicesToQualifier().inverse().get(name);
return serviceInstances.select(qualifier).get();
} else {
throw new IllegalStateException("Service name not set and there is no unambiguous OAuthService available");
}
}
private OAuthService getCurrentService() {
if (serviceName == null) {
return getUnambiguousService();
} else {
return multiServicesManager.getCurrentService();
}
}
@Override
public void authenticate() {
String authorizationUrl;
if (serviceName == null) {
log.debug("Service name null, authenticating with unamgiguous oauthService");
OAuthService oauthService = getUnambiguousService();
authorizationUrl = oauthService.getAuthorizationUrl();
} else {
log.debug("authenticating service \"" + serviceName + "\"");
authorizationUrl = multiServicesManager.initNewSession(serviceName);
}
try {
FacesContext.getCurrentInstance().getExternalContext().redirect(authorizationUrl);
setStatus(AuthenticationStatus.DEFERRED);
} catch (IOException e) {
log.error("Failed to redirect ", e);
setStatus(AuthenticationStatus.FAILURE);
}
}
@Override
public String getVerifierParamName() {
return getCurrentHub().getVerifierParamName();
}
private SocialNetworkServicesHub getCurrentHub() {
return multiServicesManager.getCurrentServiceHub();
}
@Override
public String getVerifier() {
return getCurrentService().getVerifier();
}
@Override
public void setVerifier(String verifier) {
getCurrentService().setVerifier(verifier);
}
@Override
public void connect() {
OAuthService currentService;
OAuthSession currentSession;
if (serviceName != null) {
MultiServicesManager manager = multiServicesManager;
manager.connectCurrentService();
currentService = manager.getCurrentService();
currentSession = manager.getCurrentSession();
} else {
currentService = getUnambiguousService();
currentSession = currentService.getSession();
currentService.initAccessToken();
}
OAuthUser user = new OAuthUser(currentService.getType(), currentSession.getUserProfile());
if (isIdentityManaged()) {
// By default we set the status to FAILURE, if we manage to get to the end
// of this method we get rewarded with a SUCCESS
setStatus(AuthenticationStatus.FAILURE);
if (identitySessionProducer.get().isConfigured()) {
validateManagedUser(user);
}
}
setUser(user);
setStatus(AuthenticationStatus.SUCCESS);
beanManager.fireEvent(new DeferredAuthenticationEvent(true));
}
protected void validateManagedUser(OAuthUser principal) {
IdentitySession session = identitySession.get();
try {
// Check that the user's identity exists
if (session.getPersistenceManager().findUser(principal.getId()) == null) {
// The user wasn't found, let's create them
User user = session.getPersistenceManager().createUser(principal.getId());
// TODO allow the OAuth -> IDM attribute mapping to be configured
// e.g.
// session.getAttributesManager().addAttribute(user, "fullName", principal.getUserProfile().getFullName());
// session.getAttributesManager().addAttribute(user, "profileImageUrl",
// principal.getUserProfile().getProfileImageUrl());
// Load the user's roles and groups
try {
Collection<RoleType> roleTypes = session.getRoleManager().findUserRoleTypes(user);
for (RoleType roleType : roleTypes) {
for (Role role : session.getRoleManager().findRoles(user, roleType)) {
identity.get().addRole(role.getRoleType().getName(), role.getGroup().getName(),
role.getGroup().getGroupType());
}
}
for (Group g : session.getRelationshipManager().findAssociatedGroups(user)) {
identity.get().addGroup(g.getName(), g.getGroupType());
}
} catch (FeatureNotSupportedException ex) {
throw new AuthenticationException("Error loading user's roles and groups", ex);
} catch (IdentityException ex) {
throw new AuthenticationException("Error loading user's roles and groups", ex);
}
}
} catch (IdentityException ex) {
throw new AuthenticationException("Error locating User record for OAuth user", ex);
}
}
}