/*
* Copyright (c) 2005-2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.wso2.carbon.identity.provider.openid;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.OpenIDException;
import org.openid4java.association.Association;
import org.openid4java.association.AssociationException;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.DirectError;
import org.openid4java.message.Message;
import org.openid4java.message.ParameterList;
import org.openid4java.message.VerifyRequest;
import org.openid4java.message.VerifyResponse;
import org.openid4java.server.ServerAssociationStore;
import org.openid4java.server.ServerException;
import org.openid4java.server.ServerManager;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.core.util.IdentityUtil;
/**
* This class passes OpenID messages such as OpenID Association, OpenID Request
* and Response messages to the ServerManager class of the openid4java library.
* However the purpose of this class is to force the super class to use the
* custom AssociationStore instead the default InMemoryAssociationStores.
*
* @author WSO2 Inc.
*/
public class OpenIDServerManager extends ServerManager {
private static final Log log = LogFactory.getLog(OpenIDServerManager.class);
//When an association is generated, it would be set as a thread local variable.
private static ThreadLocal<Association> threadLocalAssociation = new ThreadLocal<Association> ();
/**
* Here we set our AssociationStore implementation to the parent.
*
*/
public OpenIDServerManager() {
ServerAssociationStore sharedAssociations = new OpenIDServerAssociationStore(OpenIDServerConstants
.ASSOCIATION_STORE_TYPE_SHARED);
super.setSharedAssociations(sharedAssociations);
ServerAssociationStore privateAssociations = null;
synchronized (Runtime.getRuntime().getClass()){
String privateAssociationStoreClassName = IdentityUtil.getProperty(IdentityConstants.ServerConfig.OPENID_PRIVATE_ASSOCIATION_STORE_CLASS);
if(privateAssociationStoreClassName != null && !privateAssociationStoreClassName.trim().isEmpty()) {
privateAssociationStoreClassName = privateAssociationStoreClassName.trim();
if(log.isDebugEnabled()) {
log.debug("Initialising privateAssociation Store : " + privateAssociationStoreClassName);
}
try {
privateAssociations = (ServerAssociationStore)Class.forName(privateAssociationStoreClassName)
.newInstance();
if(log.isDebugEnabled()) {
log.debug("Successfully initialized privateAssociation Store : "
+ privateAssociationStoreClassName);
}
} catch (ClassNotFoundException e) {
log.error("Private association store class : " + privateAssociationStoreClassName + " not found", e);
} catch (InstantiationException e) {
log.error("Error while initializing association store class : " + privateAssociationStoreClassName, e);
} catch (IllegalAccessException e) {
log.error("Error while initializing association store class : " + privateAssociationStoreClassName, e);
} catch (Exception e) {
log.error("Error while initializing private association store", e);
}
}
if(privateAssociations == null) {
privateAssociations = new OpenIDServerAssociationStore(OpenIDServerConstants.ASSOCIATION_STORE_TYPE_PRIVATE);
if(log.isDebugEnabled()){
log.debug("Setting default OpenID Server Association Store: " +
OpenIDServerAssociationStore.class.getName());
}
}
}
super.setPrivateAssociations(privateAssociations);
}
@Override
public Message authResponse(AuthRequest authReq, String userSelId, String userSelClaimed,
boolean authenticatedAndApproved, String opEndpoint, boolean signNow) {
if(log.isDebugEnabled()) {
log.debug("Association handle in AuthRequest : " + authReq.getHandle());
}
return super.authResponse(authReq, userSelId, userSelClaimed, authenticatedAndApproved, opEndpoint, signNow);
}
public void sign(AuthSuccess authSuccess)
throws ServerException, AssociationException {
String handle = authSuccess.getHandle();
Association assoc = null;
try {
// First try in thread local
assoc = getThreadLocalAssociation();
} finally {
// Clear thread local
clearThreadLocalAssociation();
}
// try shared associations, then private
if (assoc == null) {
assoc = getSharedAssociations().load(handle);
}
if (assoc == null) {
assoc = getPrivateAssociations().load(handle);
}
if (assoc == null) {
throw new ServerException("No association found for handle: " + handle);
}
authSuccess.setSignature(assoc.sign(authSuccess.getSignedText()));
}
public Message verify(ParameterList requestParams) {
if(log.isDebugEnabled()) {
log.debug("Processing verification request...");
}
boolean isVersion2 = true;
try {
// build request message from response params (+ ntegrity check)
VerifyRequest vrfyReq = VerifyRequest.createVerifyRequest(requestParams);
isVersion2 = vrfyReq.isVersion2();
String handle = vrfyReq.getHandle();
boolean verified = false;
Association assoc = getPrivateAssociations().load(handle);
String sigMod = null;
if (assoc != null) { // verify the signature
if (log.isDebugEnabled()) {
log.debug("Loaded private association; handle: " + handle);
}
sigMod = vrfyReq.getSignature().replaceAll("\\s", "+");
verified = assoc.verifySignature(vrfyReq.getSignedText(), sigMod);
// remove the association so that the request
// cannot be verified more than once
getPrivateAssociations().remove(handle);
} else {
log.error("No association loaded from the database; handle: " + handle);
}
VerifyResponse vrfyResp =
VerifyResponse.createVerifyResponse(!vrfyReq.isVersion2());
vrfyResp.setSignatureVerified(verified);
if (verified) {
String invalidateHandle = vrfyReq.getInvalidateHandle();
if (invalidateHandle != null &&
getSharedAssociations().load(invalidateHandle) == null) {
if (log.isDebugEnabled()) {
log.debug("Shared association invalidated; handle: " + invalidateHandle);
}
vrfyResp.setInvalidateHandle(invalidateHandle);
}
} else {
log.error("Signature verification failed. handle : " + handle +
" , signed text : " + vrfyReq.getSignedText() +
" , signature : " + sigMod);
}
if (log.isDebugEnabled()) {
log.debug("Responding with " + (verified ? "positive" : "negative") + " verification response");
}
return vrfyResp;
} catch (OpenIDException e) {
log.error("Error processing verification request; responding with verification error", e);
return DirectError.createDirectError(e, !isVersion2);
}
}
static Association getThreadLocalAssociation() {
Association association = threadLocalAssociation.get();
threadLocalAssociation.remove();
return association;
}
static void setThreadLocalAssociation(Association association) {
threadLocalAssociation.set(association);
}
static void clearThreadLocalAssociation(){
threadLocalAssociation.remove();
}
}