Hi Jesse, Thanks for comments. We are trying to implement it step-by-step, but things are kind of related in order to be able to install a gadget that does a three-legged call to Google to retrieve all of your contacts. Basically the missing piece of information is how we can get the client side (= rave server side) generated securityToken into the osapi call (for now Shindig produces an error when encountering the default token %st%).
In the SURFconext codebase we did this by putting the securityToken on the gadget in JavaScript and calling shindig.container.addGadget: gadget.secureToken = COIN.securityTokens[i]; //generated by the Portal server gadget.userPrefs = shindig.container.userPrefStore.getPrefs(gadget); shindig.container.addGadget(gadget); } shindig.container.layoutManager.setGadgetChromeIds(gadgetIds); shindig.container.renderGadgets(); However with the new common container (http://comments.gmane.org/gmane.comp.web.shindig.devel/6286) this seems to have changed. You're right about the pem file, but that is a naming miscommunication: gadgets.securityTokenKeyFile does point to our key file which we also use to create the token on the Rave server: BasicBlobCrypter blobCrypter = new BasicBlobCrypter(key.getBytes()); BlobCrypterSecurityToken st = new BlobCrypterSecurityToken(blobCrypter, environment.getContainerName(), "localhost"); st.setViewerId(personId); st.setOwnerId(personId); st.setAppUrl(gadget.getDefinition().getUrl()); String token = Utf8UrlCoder.encode(st.encrypt()); To be continued... Okke On Jun 23, 2011, at 4:47 PM, Ciancetta, Jesse E. wrote: > Hi Jasha, > > Great to hear that you and Okke are taking this on -- it will be awesome to > get security tokens and three-legged OAuth working. > > One thing that might help in general is to just try to do one piece of this > at a time -- it sounds like your trying to get security tokens, three-legged > OAuth and alternate config location loading all working at the same time > which seems like it could be a challenge... It would also probably be good > to commit them one piece at a time too so it will be easier for us to > understand what the changes do (which you may have been planning to do > already). > > When I ran through this when working on OSEC internally I ran into issues > with getting all the right keys setup in the right format and in the right > place. I ended up creating a script to create everything which I thought > might be helpful (I did some search/replace on paths and filenames so > hopefully I didn't break anything): > > ----------- BEGIN SCRIPT ----------- > > # local variables > TMP_DIR=/tmp/shindig_keys > PRIVATE_KEYS_DIR=/private/shindig/keys > PUBLIC_KEYS_DIR=/public/shindig/keys > > # make a temporary working directory > mkdir $TMP_DIR > cd $TMP_DIR > > # generate our private key -- this is used by shindig to sign requests > openssl genrsa -out portal_signing_key_private.pem 1024 > > # create a pkcs8 formatted version of the private key -- this seems to be the > format shindig wants the key in -- this is the filed used for > shindig.signing.key-file in shindig.properties > openssl pkcs8 -in portal_signing_key_private.pem -out > portal_signing_key_private.pk8.pem -topk8 -nocrypt -outform PEM > > # create a DER formatted version of the private key in case we need it at > some point in the future > openssl rsa -in portal_signing_key_private.pem -inform PEM -out > portal_signing_key_private.der -outform DER > > # export the public key component in PEM format - this could be used by third > parties to validate the signature on shindig signed requests > openssl rsa -in portal_signing_key_private.pem -pubout -out > portal_signing_key_public.pem > > # export the public key component in DER format - this could be used by third > parties to validate the signature on shindig signed requests > openssl rsa -in portal_signing_key_private.pem -pubout -out > portal_signing_key_public.der -outform DER > > # create a new x509 certificate based on our private key and write it out in > PEM format - this could be used by third parties to validate the signature on > shindig signed requests > openssl req -new -x509 -key portal_signing_key_private.pem -days 365 -subj > '/C=US/ST=MA/L=Bedford/O=Foo Corp/OU=Foo/CN=foo.example.com' -out > portal_signing_certificate.pem > > # create a DER formatted version of our x509 certificate - this could be used > by third parties to validate the signature on shindig signed requests > openssl x509 -in portal_signing_certificate.pem -inform PEM -out > portal_signing_certificate.der -outform DER > > # generate the shared secret symmetric key used by shindig and the container > to encrypt/decrypt gadget security tokens - this is the file used for > gadgets.securityTokenKeyFile in container.js > dd if=/dev/random bs=32 count=1 | openssl base64 > > portal_security_token_key_private.txt > > # copy the private keys to the shindig directory > mkdir $PRIVATE_KEYS_DIR > cp portal_signing_key_private.* portal_security_token_key_private.txt > $PRIVATE_KEYS_DIR > > # copy the public keys and certificates to the public keys directory > mkdir $PUBLIC_KEYS_DIR > cp portal_signing_key_public.* portal_signing_certificate.* $PUBLIC_KEYS_DIR > > # remove the temporary working directory > cd .. > rm -rf $TMP_DIR > > ----------- END SCRIPT ----------- > > Additional comments inline below. > >> -----Original Message----- >> From: Jasha Joachimsthal [mailto:[email protected]] >> Sent: Thursday, June 23, 2011 8:37 AM >> To: [email protected] >> Subject: 3-legged oAuth >> >> Hi, >> >> Okke and I are trying to get 3 legged oAuth working in Rave but we're stuck >> on passing the security token from the portal to the (Shindig) container. >> >> What we have done so far: >> - generated a key using >> openssl req -newkey rsa:1024 -days 365 -nodes -x509 -keyout testkey.pem - >> out >> testkey.pem -subj '/CN=mytestkey' >> openssl pkcs8 -in testkey.pem -out oauthkey.pem -topk8 -nocrypt -outform >> PEM >> >> - replaced the default PropertiesModule by a properties module that can read >> shindig properties from a different location >> - configured our custom shindig properties to read a different container.js >> in shindig.containers.default >> >> In our custom container.js we've set >> "gadgets.securityTokenType" : "secure", >> "gadgets.securityTokenKeyFile" : "/path/to/my/oauthkey.pem", #with the >> correct location of course > > This doesn't look like the right file to me -- this looks like what shindig > might use to sign outbound requests, not what's used for > encrypting/decrypting security tokens. Using the script I posted above I > think the path you'd want would be: > > /private/shindig/keys/portal_security_token_key_private.txt > >> >> - replaced CommonContainerAuthGuiceModule with a custom module that >> loads DefaultSecurityTokenCodec instad of >> CommonContainerSecurityTokenCodec > > It doesn't seem like you should need to do this -- the > CommonContainerSecurityTokenCodec seems to use the > BlobCrypterSecurityTokenCodec by default when it encounters the "secure" > configuration in container.js which seems like what we want. > >> - added a gadget from http://gadgets.jasha.eu/oauthtest.xml (and registered >> the domain in Google) >> - added consumer key & secret to config/oauth.json >> >> Now when I add this gadget to my page, I get a HTTP Status 401 - Malformed >> security token %st% Invalid security token %st% response. >> This makes sense because the portal doesn't pass a security token to >> Shindig. This could be done by generating a token like in [1], in Rave >> probably in OpenSocialWidgetRenderer, and then set the security token to > > Agreed -- OpenSocialWidgetRenderer does seem like the right place to do this > but I think I might push the actual work off to a SecurityTokenService which > takes the RegionWidget and returns the SecurityToken. We've updated our > SecurityTokenService internally and haven't pushed those updates back to the > public OSEC codebase yet, so I'll post what we have currently here in hopes > that it might be useful (the changes from what's in public OSEC currently is > basically that this service no longer does any URL encoding of the values it > deals with since this really isn't the right architectural layer to be doing > that work): > > ---------- > > import org.apache.shindig.auth.SecurityToken; > import org.mitre.portal.model.PersonGadget; > import org.mitre.portal.service.exception.SecurityTokenException; > > public interface SecurityTokenService { > SecurityToken getSecurityToken(PersonGadget personGadget) throws > SecurityTokenException; > String getEncryptedSecurityToken(PersonGadget personGadget) throws > SecurityTokenException; > SecurityToken decryptSecurityToken(String encryptedSecurityToken) throws > SecurityTokenException; > String refreshEncryptedSecurityToken(String encryptedSecurityToken) throws > SecurityTokenException; > } > > ---------- > > import org.apache.commons.logging.Log; > import org.apache.commons.logging.LogFactory; > import org.apache.shindig.auth.BlobCrypterSecurityToken; > import org.apache.shindig.auth.MitreBlobCrypterSecurityToken; > import org.apache.shindig.auth.SecurityToken; > import org.apache.shindig.common.crypto.BasicBlobCrypter; > import org.apache.shindig.common.crypto.BlobCrypter; > import org.apache.shindig.common.util.CharsetUtil; > import org.mitre.portal.model.Gadget; > import org.mitre.portal.model.Person; > import org.mitre.portal.model.PersonGadget; > import org.mitre.portal.security.SecurityTokenService; > import org.mitre.portal.security.UserService; > import org.mitre.portal.service.exception.SecurityTokenException; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.security.access.prepost.PreAuthorize; > import org.springframework.stereotype.Service; > > import java.io.File; > import java.io.IOException; > import java.net.MalformedURLException; > import java.net.URL; > import java.util.ResourceBundle; > > @Service(value = "securityTokenService") > public class EncryptedBlobSecurityTokenService implements > SecurityTokenService { > private static final Log log = > LogFactory.getLog(EncryptedBlobSecurityTokenService.class); > > private static final String SECURITY_PROPERTIES = "application"; > private static final String ENCRYPTION_PROPERTY = "security.encryptionkey"; > private static final String EMBEDDED_KEY_PREFIX = "embedded:"; > //TODO - should this be read in from a properties file? > private static final String CONTAINER = "default"; > private static final String DOMAIN = " default"; > private static final String CHAR_ENCODING = "UTF-8"; > > private final UserService userService; > private final BlobCrypter blobCrypter; > > @Autowired > public EncryptedBlobSecurityTokenService(UserService userService) { > this.userService = userService; > > ResourceBundle resourceBundle = > ResourceBundle.getBundle(SECURITY_PROPERTIES); > if (resourceBundle.containsKey(ENCRYPTION_PROPERTY)) { > String key = resourceBundle.getString(ENCRYPTION_PROPERTY); > if (key.startsWith(EMBEDDED_KEY_PREFIX)) { > byte[] encryptionKey = > CharsetUtil.getUtf8Bytes(key.substring(EMBEDDED_KEY_PREFIX.length())); > this.blobCrypter = new BasicBlobCrypter(encryptionKey); > } else { > try { > this.blobCrypter = new BasicBlobCrypter(new File(key)); > } catch (IOException e) { > throw new SecurityException("Unable to load encryption key > from file: " + key); > } > } > } else { > throw new SecurityException("Unable to find encryption key!"); > } > } > > @Override > @PreAuthorize("#personGadget.userId == principal.userId") > public SecurityToken getSecurityToken(PersonGadget personGadget) throws > SecurityTokenException { > return this.getBlobCrypterSecurityToken(personGadget); > } > > @Override > @PreAuthorize("#personGadget.userId == principal.userId") > public String getEncryptedSecurityToken(PersonGadget personGadget) throws > SecurityTokenException { > String encryptedToken = null; > > try { > BlobCrypterSecurityToken securityToken = > this.getBlobCrypterSecurityToken(personGadget); > encryptedToken = this.encryptSecurityToken(securityToken); > } catch (Exception e) { > throw new SecurityTokenException("Error creating security token > from person gadget", e); > } > > return encryptedToken; > } > > @Override > public SecurityToken decryptSecurityToken(String encryptedSecurityToken) > throws SecurityTokenException { > SecurityToken securityToken; > > try { > log.debug("Decrypting security token: " + encryptedSecurityToken); > > //Remove the header container string and : > encryptedSecurityToken = > encryptedSecurityToken.substring((CONTAINER + ":").length()); > > //Decrypt > securityToken = MitreBlobCrypterSecurityToken.decrypt(blobCrypter, > CONTAINER, DOMAIN, encryptedSecurityToken); > } catch (Exception e) { > throw new SecurityTokenException("Error creating security token > from encrypted string: " + encryptedSecurityToken, e); > } > > return securityToken; > } > > @Override > public String refreshEncryptedSecurityToken(String encryptedSecurityToken) > throws SecurityTokenException { > try { > //Decrypt the current token > SecurityToken securityToken = > this.decryptSecurityToken(encryptedSecurityToken); > > //Make sure the person is authorized to refresh this token > String userId = > userService.getCurrentAuthenticatedUser().getUserId(); > if (!securityToken.getOwnerId().equalsIgnoreCase(userId)) { > throw new SecurityTokenException("Illegal attempt by user " + > userId + " to refresh security token owned by " + securityToken.getOwnerId()); > } > > //Create a new PersonGadget instance from it so we can use it to > generate a new encrypted token > PersonGadget personGadget = new > PersonGadget(securityToken.getModuleId(), securityToken.getOwnerId()); > personGadget.setGadget(new Gadget(-1L, "", "", new > URL(securityToken.getAppUrl()))); > > //Create and return the newly encrypted token > return getEncryptedSecurityToken(personGadget); > } catch (MalformedURLException e) { > throw new SecurityTokenException(e.getMessage(), e); > } > } > > private BlobCrypterSecurityToken getBlobCrypterSecurityToken(PersonGadget > personGadget) throws SecurityTokenException { > Person user = userService.getCurrentAuthenticatedUser(); > > BlobCrypterSecurityToken securityToken = new > BlobCrypterSecurityToken(blobCrypter, CONTAINER, DOMAIN); > securityToken.setAppUrl(personGadget.getGadget().getUrl().toString()); > securityToken.setModuleId(personGadget.getPersonGadgetId()); > securityToken.setOwnerId(personGadget.getUserId()); > securityToken.setViewerId(user.getUserId()); > securityToken.setTrustedJson(""); > > log.debug("Token created for personGadget " + personGadget.toString() > + " and user " + user.toString()); > return securityToken; > } > > private String encryptSecurityToken(BlobCrypterSecurityToken > securityToken) throws SecurityTokenException { > String encryptedToken = null; > > try { > encryptedToken = securityToken.encrypt(); > log.debug("Encrypted token created from security token: " + > securityToken.toString() + " -- encrypted token is: " + encryptedToken); > } catch (Exception e) { > throw new SecurityTokenException("Error creating security token > from person gadget", e); > } > > return encryptedToken; > } > } > > ---------- > > package org.apache.shindig.auth; > > import org.apache.shindig.common.crypto.BlobCrypter; > import org.apache.shindig.common.crypto.BlobCrypterException; > > /** > * This class is needed because the BlobCrypterSecurityToken.decrypt method is > marked as protected and we > * need to be able to decrypt the security token in the container > */ > public class MitreBlobCrypterSecurityToken extends BlobCrypterSecurityToken { > public MitreBlobCrypterSecurityToken(BlobCrypter crypter, String > container, String domain) { > super(crypter, container, domain); > } > > public static BlobCrypterSecurityToken decrypt(BlobCrypter crypter, String > container, String domain, > String token) throws > BlobCrypterException { > return BlobCrypterSecurityToken.decrypt(crypter, container, domain, > token, null); > } > } > > ---------- > > I think some of the API's have changed on the Shindig side since this code > was written (this was written for Shindig 2.0) but I think the concepts are > still the same. > >> the >> gadget object like in [2]. In rave_opensocial.js we don't pass the gadget as >> an object to the container, just its url and render params. > > I think for now we could just add another securityToken property to the > object we push onto the widgets array. I haven't yet looked into how we push > this into the Shindig common container code however. Hopefully there's an > easy way to do it... > >> >> Is there anyone who does know the right place where to handle the >> securityToken between the portal and container? >> >> >> [1] >> http://svn.apache.org/repos/asf/incubator/rave/donations/surfconext- >> portal/coin-portal/trunk/coin-portal- >> war/src/main/java/nl/surfnet/coin/portal/control/HomeController.java >> [2] >> http://svn.apache.org/repos/asf/incubator/rave/donations/surfconext- >> portal/coin-portal/trunk/coin-portal- >> war/src/main/webapp/js/coin/shindig/container.js >> >> Jasha Joachimsthal >> >> Europe - Amsterdam - Oosteinde 11, 1017 WT Amsterdam - +31(0)20 522 4466 >> US - Boston - 1 Broadway, Cambridge, MA 02142 - +1 877 414 4776 (toll free) >> >> www.onehippo.com Met vriendelijke groet, Okke Harsta Partner Zilverline Agile Management Consultant en Coach Lean Architect http://www.zilverline.com http://nl.linkedin.com/in/okkeharsta tel: +31 6 118 601 74
