package com.ibm.sbt.opensocial.domino.security;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.shindig.auth.AnonymousSecurityToken;
import org.apache.shindig.auth.BasicSecurityTokenCodec;
import org.apache.shindig.auth.BlobCrypterSecurityTokenCodec;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.auth.SecurityTokenCodec;
import org.apache.shindig.auth.SecurityTokenException;
import org.apache.shindig.config.ContainerConfig;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* Security token codec responsible for encrypting/decrypting security tokens.
*
*/
@Singleton
public class DominoSecurityTokenCodec implements SecurityTokenCodec {
private static final String CLASS = DominoSecurityTokenCodec.class.getName();
private static final String SECURITY_TOKEN_TYPE = "gadgets.securityTokenType";
private SecurityTokenCodec secureCodec;
private SecurityTokenCodec insecureCodec;
private Map<String, String> tokenTypes;
private ContainerConfig config;
private final Logger log;
private ContainerConfig.ConfigObserver observer = new ContainerConfig.ConfigObserver() {
@Override
public void containersChanged(ContainerConfig config,
Collection<String> changed, Collection<String> removed) {
populateTokenTypes(config, changed, removed, DominoSecurityTokenCodec.this.tokenTypes);
}
};
@Inject
public DominoSecurityTokenCodec(ContainerConfig config, Logger log) {
this.config = config;
this.tokenTypes = Maps.newHashMap();
this.log = log;
config.addConfigObserver(observer, false);
populateTokenTypes(config, config.getContainers(), Collections.EMPTY_LIST, this.tokenTypes);
}
private void populateTokenTypes(ContainerConfig config, Collection<String> added, Collection<String> removed,
Map<String, String> tokenTypes) {
synchronized(tokenTypes) {
for (String container : added) {
tokenTypes.put(container, config.getString(container, SECURITY_TOKEN_TYPE));
}
for(String container : removed) {
tokenTypes.remove(container);
}
}
}
public SecurityToken createToken(Map<String, String> tokenParameters)
throws SecurityTokenException {
final String method = "createToken";
// FIXME: This is so gross that I have to do this. Shindig needs to be fixed so I can
// consistently get the container from tokenParameters.
String token = tokenParameters.get(SecurityTokenCodec.SECURITY_TOKEN_NAME);
if (token == null || token.length() == 0) {
return new AnonymousSecurityToken();
}
String[] tokenParts = token.split(":");
String container;
if (tokenParts.length == 2) {
// BlobCrypter. Part 0 is the container. Part 1 is the encrypted blob.
container = tokenParts[0];
} else {
// BasicCrypter. 6 is the magic number that is private static final in
// BasicBlobCrypterSecurityToken
container = tokenParts[6];
}
SecurityTokenCodec codec = getCodec(container);
if(codec == null) {
//Shindig is really bad about making sure containers are encoded before they
//are placed on URLs. It basically makes the assumption that container ids do
//not need to be encoded and just sticks them on the URLs. This is the case when
//it makes the request to the /rpc endpoint so just to be sure lets encode the container
//and try to look up the token type
try {
String encodedContainer = URLEncoder.encode(container, "UTF-8");
codec = getCodec(encodedContainer);
if(codec!= null) {
//Since the container was not properly encoded make sure it is correct in the token parameters
putContainerInTokenParams(tokenParameters, encodedContainer);
} else {
throw new RuntimeException("Could not find security token codec for container " + container);
}
} catch (UnsupportedEncodingException e) {
log.logp(Level.WARNING, CLASS, method, "Error while encoding container.");
}
}
return codec.createToken(tokenParameters);
}
public void putContainerInTokenParams(Map<String, String> tokenParams, String container) {
String token = StringUtils.defaultString(tokenParams.get(SecurityTokenCodec.SECURITY_TOKEN_NAME));
String[] parts = token.split(":");
if(parts.length == 2) {
String newToken = container + ":" + parts[1];
tokenParams.put(SecurityTokenCodec.SECURITY_TOKEN_NAME, newToken);
}
}
public String encodeToken(SecurityToken token) throws SecurityTokenException {
if (token == null) {
return null;
}
return getCodec(token.getContainer()).encodeToken(token);
}
@Deprecated
public int getTokenTimeToLive() {
return getCodec("default").getTokenTimeToLive("defualt");
}
public int getTokenTimeToLive(String container) {
return getCodec(container).getTokenTimeToLive(container);
}
private SecurityTokenCodec getCodec(String container) {
synchronized(tokenTypes) {
String tokenType = this.tokenTypes.get(container);
return getCodecByType(tokenType);
}
}
SecurityTokenCodec getCodecByType(String tokenType) {
if ("insecure".equals(tokenType)) {
if (this.insecureCodec == null) {
this.insecureCodec = new BasicSecurityTokenCodec(this.config);
}
return this.insecureCodec;
}
if ("secure".equals(tokenType)) {
if (this.secureCodec == null) {
this.secureCodec = new BlobCrypterSecurityTokenCodec(this.config);
}
return this.secureCodec;
}
return null;
}
}