/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.jboss.identity.federation.api.openid;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.jboss.identity.federation.api.openid.OpenIDLifecycleEvent.OP;
import org.jboss.identity.federation.api.openid.OpenIDLifecycleEvent.TYPE;
import org.jboss.identity.federation.api.openid.exceptions.OpenIDAssociationException;
import org.jboss.identity.federation.api.openid.exceptions.OpenIDConsumerException;
import org.jboss.identity.federation.api.openid.exceptions.OpenIDDiscoveryException;
import org.jboss.identity.federation.api.openid.exceptions.OpenIDLifeCycleException;
import org.jboss.identity.federation.api.openid.exceptions.OpenIDMessageException;
import org.jboss.identity.federation.api.openid.exceptions.OpenIDProtocolException;
import org.openid4java.association.AssociationException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.InMemoryConsumerAssociationStore;
import org.openid4java.consumer.InMemoryNonceVerifier;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.MessageException;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.sreg.SRegRequest;
/**
* OpenID Manager for consumers
* @author Anil.Saldhana@redhat.com
* @since Jul 6, 2009
*/
public class OpenIDManager
{
public enum CONST
{
OPENID("openid"),
OPENID_CLAIMED("openid-claimed"),
OPENID_DISC("openid-discovered");
private String val;
CONST(String val)
{
this.val = val;
}
public String get()
{
return this.val;
}
}
private OpenIDRequest request = null;
private ConsumerManager consumerManager = null;
private String userString = null;
public OpenIDManager(OpenIDRequest theReq)
{
this.request = theReq;
try
{
consumerManager = new ConsumerManager();
consumerManager.setAssociations(new InMemoryConsumerAssociationStore());
consumerManager.setNonceVerifier(new InMemoryNonceVerifier(5000));
userString = request.getURL();
}
catch(ConsumerException ce)
{
throw new RuntimeException(ce);
}
}
/**
* Get the OpenID Request
* @return
*/
public OpenIDRequest getOpenIDRequest()
{
return this.request;
}
@SuppressWarnings("unchecked")
public OpenIDProviderList discoverProviders() throws OpenIDDiscoveryException, OpenIDConsumerException
{
// perform discovery on the user-supplied identifier
List<DiscoveryInformation> discoveries;
try
{
discoveries = consumerManager.discover(userString);
}
catch (DiscoveryException e1)
{
throw new OpenIDDiscoveryException(e1);
}
return new OpenIDProviderList(discoveries);
}
/**
* Associate with a list of open id providers
* @param adapter Protocol adapter (such as http)
* @param listOfProviders (a list of providers from discovery)
* @return
* @throws OpenIDConsumerException
* @throws OpenIDLifeCycleException
*/
public OpenIDProviderInformation associate(OpenIDProtocolAdapter adapter,
OpenIDProviderList listOfProviders)
throws OpenIDConsumerException, OpenIDLifeCycleException
{
OpenIDLifecycle lifeCycle = null;
if(adapter instanceof OpenIDLifecycle)
{
lifeCycle = (OpenIDLifecycle) adapter;
}
List<DiscoveryInformation> discoveries = listOfProviders.get();
if(discoveries.size() == 0)
throw new OpenIDConsumerException("No open id endpoints discovered");
// attempt to associate with the OpenID provider
// and retrieve one service endpoint for authentication
DiscoveryInformation discovered = consumerManager.associate(discoveries);
// store the discovery information in the user's session for later use
// leave out for stateless operation / if there is no session
if(lifeCycle != null)
{
OpenIDLifecycleEvent ev =new OpenIDLifecycleEvent(TYPE.SESSION,
OP.ADD, CONST.OPENID_DISC.get(), discovered);
lifeCycle.handle(ev);
}
return new OpenIDProviderInformation(discovered);
}
/**
* Authenticate an user with the provider
* @param adapter protocol adapter
* @param providerInfo Information about a provider derived from discovery process
* @return
* @throws OpenIDDiscoveryException
* @throws OpenIDConsumerException
* @throws OpenIDMessageException
* @throws OpenIDProtocolException
*/
@SuppressWarnings("unchecked")
public boolean authenticate(OpenIDProtocolAdapter adapter, OpenIDProviderInformation providerInfo)
throws OpenIDDiscoveryException,
OpenIDConsumerException, OpenIDMessageException, OpenIDProtocolException
{
DiscoveryInformation discovered = providerInfo.get();
// obtain a AuthRequest message to be sent to the OpenID provider
try
{
AuthRequest authReq = consumerManager.authenticate(discovered,
adapter.getReturnURL());
// Attribute Exchange example: fetching the 'email' attribute
FetchRequest fetch = FetchRequest.createFetchRequest();
SRegRequest sregReq = SRegRequest.createFetchRequest();
OpenIDAttributeMap amap = adapter.getAttributeMap();
if ("1".equals(amap.get("nickname")))
{
// fetch.addAttribute("nickname",
// "http://schema.openid.net/contact/nickname", false);
sregReq.addAttribute("nickname", false);
}
if ("1".equals(amap.get("email")))
{
fetch.addAttribute("email",OpenIDConstants.EMAIL.url(), false);
sregReq.addAttribute("email", false);
}
if ("1".equals(amap.get("fullname")))
{
fetch.addAttribute("fullname",OpenIDConstants.FULLNAME.url(), false);
sregReq.addAttribute("fullname", false);
}
if ("1".equals(amap.get("dob")))
{
fetch.addAttribute("dob",OpenIDConstants.DOB.url(), true);
sregReq.addAttribute("dob", false);
}
if ("1".equals(amap.get("gender")))
{
fetch.addAttribute("gender",OpenIDConstants.GENDER.url(), false);
sregReq.addAttribute("gender", false);
}
if ("1".equals(amap.get("postcode")))
{
fetch.addAttribute("postcode",OpenIDConstants.POSTCODE.url(), false);
sregReq.addAttribute("postcode", false);
}
if ("1".equals(amap.get("country")))
{
fetch.addAttribute("country",OpenIDConstants.COUNTRY.url(), false);
sregReq.addAttribute("country", false);
}
if ("1".equals(amap.get("language")))
{
fetch.addAttribute("language", OpenIDConstants.LANGUAGE.url(),false);
sregReq.addAttribute("language", false);
}
if ("1".equals(amap.get("timezone")))
{
fetch.addAttribute("timezone", OpenIDConstants.TIMEZONE.url(), false);
sregReq.addAttribute("timezone", false);
}
// attach the extension to the authentication request
if (!sregReq.getAttributes().isEmpty())
{
authReq.addExtension(sregReq);
}
if (!discovered.isVersion2())
{
// Option 1: GET HTTP-redirect to the OpenID Provider endpoint
// The only method supported in OpenID 1.x
// redirect-URL usually limited ~2048 bytes
adapter.sendToProvider(1, authReq.getDestinationUrl(true), null);
return false;
}
else
{
// Option 2: HTML FORM Redirection (Allows payloads >2048 bytes)
adapter.sendToProvider(2, authReq.getDestinationUrl(false),
authReq.getParameterMap());
}
}
catch (MessageException e)
{
throw new OpenIDMessageException(e);
}
catch (ConsumerException e)
{
throw new OpenIDConsumerException(e);
}
return false;
}
/**
* Verify a previously authenticated user with the provider
* @param adapter protocol adapter
* @param parameterMap request parameters
* @param receivedURL url where the response will be received
* @return
* @throws OpenIDMessageException
* @throws OpenIDDiscoveryException
* @throws OpenIDAssociationException
* @throws OpenIDLifeCycleException
*/
public boolean verify(OpenIDProtocolAdapter adapter, Map<String,String> parameterMap,
String receivedURL) throws OpenIDMessageException,
OpenIDDiscoveryException, OpenIDAssociationException, OpenIDLifeCycleException
{
OpenIDLifecycle lifeCycle = null;
if(adapter instanceof OpenIDLifecycle)
{
lifeCycle = (OpenIDLifecycle) adapter;
}
ParameterList responselist = new ParameterList(parameterMap);
if(lifeCycle == null)
throw new IllegalStateException("Lifecycle not found");
DiscoveryInformation discovered =
(DiscoveryInformation) lifeCycle.getAttributeValue(CONST.OPENID_DISC.get());
// verify the response; ConsumerManager needs to be the same
// (static) instance used to place the authentication request
try
{
VerificationResult verification = this.consumerManager.verify(
receivedURL,
responselist, discovered);
// examine the verification result and extract the verified identifier
Identifier verified = verification.getVerifiedId();
if (verified != null)
{
AuthSuccess authSuccess =
(AuthSuccess) verification.getAuthResponse();
//Create an lifecycle event array
OpenIDLifecycleEvent[] eventArr = new OpenIDLifecycleEvent[]
{
/**Store the id**/
new OpenIDLifecycleEvent(TYPE.SESSION,
OP.ADD, CONST.OPENID.get(), authSuccess.getIdentity()),
/** Store the claimed **/
new OpenIDLifecycleEvent(TYPE.SESSION,
OP.ADD, CONST.OPENID_CLAIMED.get(), authSuccess.getClaimed()),
/** Indicate success **/
new OpenIDLifecycleEvent(TYPE.SUCCESS,
null, null, null)
};
lifeCycle.handle(eventArr);
return true;
}
}
catch (MessageException e)
{
throw new OpenIDMessageException(e);
}
catch (DiscoveryException e)
{
throw new OpenIDDiscoveryException(e);
}
catch (AssociationException e)
{
throw new OpenIDAssociationException(e);
}
return false;
}
/**
* Log an user out from an openid provider
* @param adapter protocol adapter
* @throws OpenIDLifeCycleException
*/
public void logout(OpenIDProtocolAdapter adapter) throws OpenIDLifeCycleException
{
OpenIDLifecycle lifeCycle = null;
if(adapter instanceof OpenIDLifecycle)
{
lifeCycle = (OpenIDLifecycle) adapter;
}
if(lifeCycle != null)
{
lifeCycle.handle(new OpenIDLifecycleEvent(TYPE.SESSION, OP.REMOVE, CONST.OPENID.get(), null));
lifeCycle.handle(new OpenIDLifecycleEvent(TYPE.SESSION, OP.REMOVE, CONST.OPENID_CLAIMED.get(), null));
}
}
/**
* Information about a provider from the
* discovery process
*/
public class OpenIDProviderInformation
{
private DiscoveryInformation discovered;
OpenIDProviderInformation(DiscoveryInformation di)
{
this.discovered = di;
}
DiscoveryInformation get()
{
return this.discovered;
}
}
/**
* List of OpenID providers
*/
public class OpenIDProviderList
{
private List<DiscoveryInformation> providers = null;
OpenIDProviderList(List<DiscoveryInformation> providers)
{
this.providers = providers;
}
List<DiscoveryInformation> get()
{
return Collections.unmodifiableList(providers);
}
public int size()
{
return this.providers != null ? providers.size() : 0;
}
}
}