>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?
Sure -- I'd say feel free to go ahead and add the script somewhere in the
codebase. I'm sure it will be helpful for others as they deploy Rave as well.
More comments below.
>
>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.
I think it may be interesting to figure out how to get the Shindig metadata
call to return us a proper security token in the response, but I'm not sure it
is actually the we want to approach the problem. Since we (Rave) are going to
be a full standalone container with server side components capable of
generating the security tokens ourselves, I think that's actually the way we
want to go. I had wired in the Shindig common container code initially "out of
the box" just so we could get things working, but had intended to go back and
make changes so that we could do things like set security tokens and user
preferences from the Rave side instead of having to rely on metadata calls to
Shindig from the client browser for that work -- I just haven't had a chance to
get back to it yet.
I think the confusion (at least what seems somewhat confusing to me) is what
the common container is really targeted to do. I think it's supposed to allow
any web page to script include the common container JS code and then start
embedding gadgets without having to stand up any other infrastructure (aside
from the Shindig server). But that comes at a cost -- using the common
container as is (and as we do now) means that:
** You have to run the your container code (Rave in our case) and Shindig on
the same host/port to allow the ajax call that the common container makes back
to Shindig to fetch gadget metadata succeed (otherwise you'll get security
exceptions for making cross site Ajax calls). The only way around that is to
stand up some kind of server side proxy on your container domain to proxy the
ajax call back to Shindig.
** You have to enable authentication in front of Shindig (at least for the
metadata calls) and SSO between your container and Shindig so that the endpoint
to generate a security token is secure. If it were wide open I could generate
iframe urls with security tokens embedded in them for whatever users I want.
** Common container has to make the ajax call to fetch gadget metadata on every
page render -- and with a full container implementation (like Rave) this really
shouldn't be required. We should be able to cache whatever metadata we need on
the server side and send it down to the browser with our page response, and we
should be able to use it to prime the common container's metadata cache. I've
already got some of that plumbing in place -- so if you look at the source of a
rendered Rave page you'll see the "metadata" property on the JS object we push
onto the widgets array is populated with the results of a server-side call to
the Shindig metadata service. I think that data could then be used to prime
the common container metadata cache to eliminate the need to make that call on
every request - see the TODO notes in the rave_opensocial.js for more
information on how that might be done.
So I think in the end we want to use the common container code as a basis to
take advantage of whatever existing/common/sharable functionality we can, but
we'll also need to override some of its default behavior with our own code too.
I wish I could dig in deeper right now to help more with this piece but I'm
tied up with other work at the moment. Hopefully this makes sense and helps
though. Does this approach sound right to you guys?
>
>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
>>
>>
>>