All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.shindig.gadgets.oauth.GadgetOAuthCallbackGenerator Maven / Gradle / Ivy

Go to download

Renders gadgets, provides the gadget metadata service, and serves all javascript required by the OpenSocial specification.

There is a newer version: 3.0.0-beta4
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.shindig.gadgets.oauth;

import com.google.inject.Inject;
import com.google.inject.name.Named;

import org.apache.commons.lang.StringUtils;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.common.crypto.BlobCrypter;
import org.apache.shindig.common.crypto.BlobCrypterException;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.common.uri.UriBuilder;
import org.apache.shindig.gadgets.Gadget;
import org.apache.shindig.gadgets.GadgetContext;
import org.apache.shindig.gadgets.LockedDomainService;
import org.apache.shindig.gadgets.UrlGenerator;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.oauth.OAuthResponseParams.OAuthRequestException;
import org.apache.shindig.gadgets.process.ProcessingException;
import org.apache.shindig.gadgets.process.Processor;
import org.apache.shindig.gadgets.servlet.OAuthCallbackServlet;

/**
 * Generates callback URLs for gadgets using OAuth 1.0a.  There are three relevant callback URLs:
 * 
 * 1) The consumer key callback URL: registered with service providers when they issue OAuth
 *    consumer keys.  Application authors will tell us the callback URL to send to the SP when they
 *    provide us with their consumer key. 
 *    
 *    The SP will check that the callback URL we send them matches whatever was
 *    preregistered.  It would be nice if they didn't do this, but enough do that we support it.
 *    
 *    We don't control the consumer key callback URL.  Gadget authors need to make sure that it
 *    always redirect to the shindig-deployment global callback URL.
 *    
 * 2) Global callback URL: a single callback URL that can be whitelisted by service providers
 *    and shared by all gadgets.  This keeps service providers (and gadget authors) from needing
 *    to be aware of the complexities of which domain a particular gadget actually runs on.
 *    
 *    The global callback URL always redirects to the gadget-domain callback URL.
 *    
 * 3) Gadget domain callback URL: URL on the same hostname as the gadget.  This URL will pass
 *    the oauth_verifier token into the gadget for reuse.  (It has to be on the same hostname
 *    so that the same-origin policy allows communication.  We could use gadgets.rpc, except that
 *    because the authorization happens in a popup we've got no good way to do all the gadgets.rpc
 *    bootstrapping.)
 *    
 * Here's an example of what you might see happen with these URLs:
 * 
 * Shindig sends request token request to OAuth SP with callback URL of
 *     http://gadgetauthor.com/oauthcallback?cs=
 *     
 * User approves access.  OAuth SP redirects to
 *     http://gadgetauthor.com/oauthcallback?cs=&oauth_verifier=
 *     
 * gadgauthor.com redirects to deployment global callback URL:
 *     http://oauth.shindigexample.com/oauthcallback?cs=&oauth_verifier=
 *     
 * The global callback URL redirects to a gadget-specific callback URL:
 *     http://12345.smodules.com/oauthcallback?oauth_verifier=
 *     
 * The gadget-specific callback will use window.opener to find the opening gadget and hand it
 * the verified callback URL.
 */
public class GadgetOAuthCallbackGenerator implements OAuthCallbackGenerator {

  private final boolean enableSignedCallbacks;
  private final Processor processor;
  private final LockedDomainService lockedDomainService;
  private final UrlGenerator urlGenerator;
  private final BlobCrypter stateCrypter;

  @Inject
  public GadgetOAuthCallbackGenerator(@Named("shindig.signing.enable-signed-callbacks")
      boolean enableSignedCallbacks, Processor processor, LockedDomainService lockedDomainService,
      UrlGenerator urlGenerator, @Named(OAuthFetcherConfig.OAUTH_STATE_CRYPTER)
      BlobCrypter stateCrypter) {
    this.enableSignedCallbacks = enableSignedCallbacks;
    this.processor = processor;
    this.lockedDomainService = lockedDomainService;
    this.urlGenerator = urlGenerator;
    this.stateCrypter = stateCrypter;
  }
  
  public String generateCallback(OAuthFetcherConfig fetcherConfig, String baseCallback,
      HttpRequest request, OAuthResponseParams responseParams) throws OAuthRequestException {
    if (!enableSignedCallbacks) {
      return null;
    }
    Uri activeUrl = checkGadgetCanRender(request.getSecurityToken(),
        request.getOAuthArguments(), responseParams);
    String gadgetDomainCallback = getGadgetDomainCallback(request.getSecurityToken(), activeUrl);
    if (gadgetDomainCallback == null) {
      return null;
    }
    return generateCallbackForProvider(responseParams, baseCallback, gadgetDomainCallback);
  }

  private Uri checkGadgetCanRender(SecurityToken securityToken, OAuthArguments arguments,
      OAuthResponseParams responseParams) throws OAuthRequestException {
    try {
      GadgetContext context = new OAuthGadgetContext(securityToken, arguments);
      // This feels really heavy-weight, is there a simpler way to figure out if a gadget requires
      // a locked-domain?
      Gadget gadget = processor.process(context);

      Uri activeUrl = Uri.parse(securityToken.getActiveUrl());
      String hostname = activeUrl.getAuthority();
      if (!lockedDomainService.gadgetCanRender(hostname, gadget, securityToken.getContainer())) {
        throw responseParams.oauthRequestException(OAuthError.UNKNOWN_PROBLEM,
            "Gadget should not be using URL " + activeUrl);
      }
      return activeUrl;
    } catch (ProcessingException e) {
      throw responseParams.oauthRequestException(OAuthError.UNKNOWN_PROBLEM,
          "Unable to check if gadget is using locked-domain", e);
    }
  }

  private String getGadgetDomainCallback(SecurityToken securityToken, Uri activeUrl) {
    String baseCallback = urlGenerator.getGadgetDomainOAuthCallback(
        securityToken.getContainer(), activeUrl.getAuthority());
    if (baseCallback == null) {
      return null;
    }
    UriBuilder gadgetCallback = UriBuilder.parse(baseCallback);
    if (StringUtils.isEmpty(gadgetCallback.getScheme())) {
      gadgetCallback.setScheme(activeUrl.getScheme());
    }
    return gadgetCallback.toString();
  }
  
  private String generateCallbackForProvider(
      OAuthResponseParams responseParams, String callbackForProvider, String gadgetDomainCallback)
      throws OAuthRequestException {
    OAuthCallbackState state = new OAuthCallbackState(stateCrypter);
    state.setRealCallbackUrl(gadgetDomainCallback);
    UriBuilder callback = UriBuilder.parse(callbackForProvider);
    try {
      callback.addQueryParameter(OAuthCallbackServlet.CALLBACK_STATE_PARAM,
          state.getEncryptedState());
    } catch (BlobCrypterException e) {
      throw responseParams.oauthRequestException(OAuthError.UNKNOWN_PROBLEM,
          "Failure generating callback URL", e);
    }
    return callback.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy