Good news! The 3-legged oAuth works now on my machine but only with ugly hacks to bypass a possible bug in Shindig [1]. So we'll first focus on other parts of oAuth (like storing keys in the db, instead of reading it from a json file in the war).
[1] http://markmail.org/thread/7r62z2ogkd4ad6pf Jasha On 28 June 2011 14:04, Franklin, Matthew B. <[email protected]> wrote: > I think Jesse hinted at this earlier, but it looks like the issues you > described are more related to the auth tokens than the 3-legged Oauth. > > https://issues.apache.org/jira/browse/RAVE-20 > > > https://issues.apache.org/jira/browse/RAVE-82 > > > I *think* that the auth token is not required for Oauth to work, though it > is absolutely necessary in the real world; however, given that they can be > worked on separately, I recommend taking one problem on at a time :) > > -Matt > > > > On 6/28/11 6:19 AM, "Jasha Joachimsthal" <[email protected]> > wrote: > > >Hi all, > > > >to give an update on this issue: we're still stuck but got to know the > >internals of Shindig a bit better. > > > >The key file was indeed incorrect, thanks Jesse for the script to generate > >the key. Should we document this script or add it to the code base? > > > >CommonContainerSecurityTokenCodec does use BlobCrypterSecurityTokenCodec > >by > >default but its createToken method returns TestSecurityTokenCodec while > >DefaultContainerSecurityCodes#createToken returns a > >BlobCrypterSecurityToken. > > > >We've added a shindig.auth.updateSecurityToken('${securityToken}'); before > >rendering the widgets and this method seems to work but not for the actual > >rendering of the gadgets. > > > >When in rave_opensocialapi.js calls container.navigateGadget(gadgetSite, > >gadget.widgetUrl, {}, renderParams); we get an iframeUrl with security > >token > >%st% back. This is a result > >of DefaultIframeUriManager#generateSecurityToken. It tries to encode an > >AnonymousSecurityToken in the BlobCrypterSecurityTokenCodec class which > >fails. > >We can block the AnonymousSecurityToken by overriding SocialApiGuiceModel > >but then the gadget is not rendered at all because the metadata call in > >OpenSocialWidgetRenderer fails. > > > >We're lost now in the magic that happens inside container.navigateGadget > >so > >I've just posted a question on the Shindig-dev list how we can get an > >iframeUrl with a valid security token. > > > >Regards, > > > >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 > > > > > >On 23 June 2011 17:34, Okke Harsta <[email protected]> wrote: > > > >> 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 > >> > >> > >> > >
