Fixing OAuth in Core Gadget Spec

The Core Gadget spec currently defines that Preload, Link and Content elements can be OAuth protected... however, the spec does not define any means for the container to seek authorization when it attempts to retrieve the content for those items. When using makeRequest or osapi.http, the gadget dev is responsible for providing the UX for the oauth approval, including direction. The oauthpopup feature makes it easier, but that's only available when we use the javascript APIs. Note that, as currently defined, there is NO VALID WAY of using OAuth with Content/Link/Preload elements as currently described in the 2.x spec, so when we ship 2.5, unless changes are made, we'll be defining a nonimplementable "feature"

Although the Core Gadget specification specifically allows the following example gadget... Shindig throws an error when it attempts to render it because there's no way to actually retrieve the access token...

 <?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hey, you do know this won't actually work... right?">
    <OAuth2>
      <Service name="foo">
        ...
      </Service>
    </OAuth2>
    <Require feature="oauthpopup" />
  </ModulePrefs>
  <Content type="html" href="http://www.example.com" authz="oauth" oauth_service_name="foo" />
</Module>

One argument can be made that we don't actually know for sure if anyone is actually using OAuth in this way... in which case, one possible proposal is to simply drop support for using OAuth on Preload, Link and Content elements, restricting OAuth to ONLY Javascript calls... While that may be a valid option, there actually is a way we can fix this without dropping it...

Oh.. and redirected content using type="url" with OAuth doesn't work either... the container simply ignores that oauth is specified and shows the content. It really shouldn't do that. If the access token is not available, the container should ask for authorization...

Adding a View to Service

We can add a view attribute to the Service element under OAuth and OAuth2. It's value would identify a view that the container should render if authorization for that oauth provider is required... The content of that view would essentially be the same oauthpopup code we're familiar with for the makeRequest and osapi.http options... e.g. 

 <?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Demo 3-legged OAuth to Shindig"
               icon="http://localhost:8080/samplecontainer/examples/icon.png">
    <OAuth>
      <Service name="shindig" view="oauth.shindig">
        <Request url="http://localhost:8080/oauth/requestToken" />
        <Authorization url="http://localhost:8080/oauth/authorize?oauth_callback=http://localhost:8080/gadgets/oauthcallback" />
        <Access url="http://localhost:8080/oauth/accessToken" />
      </Service>
    </OAuth>
    <Require feature="oauthpopup" />
  </ModulePrefs>
  <Content type="html" view="oauth.shindig">
      <![CDATA[
    <h1>We need approval!</h1>
    <div><a href="#" id="Authorize">Authorize</a></div>
    <div><a href="#" id="Done">Authorization is Done</a></div>
    <script type="text/javascript">
      function doPrep() {
        var popup = 
          new gadgets.oauth.Popup(
            gadgets.views.getParams()["OAUTH_APPROVAL_URL"],
            null);
        $('personalize').onclick = popup.createOpenerOnClick();
        $('approvaldone').onclick = popup.createApprovedOnClick();
      }
      gadgets.util.registerOnLoadHandler(doPrep);
    </script>
      ]]>
  </Content>

  ...
</Module>

Whenever the container detects that authorization is required in order to acquire an access token, it would render the specified view, perhaps in a separate window, etc. When it returns, it would close and the container can continue processing the request... 

This gives us several benefits...

1. It allows us to simplify makeRequest and osapi.http requests in javascript... rather than sending the redirect url back to the gadget and forcing the makeRequest handler code to handle the redirection and oauthpopup, the container would use the new view to do all that... once the new view is done doing it's thing, the container either completes the request or returns an error... whatever is appropriate... example below...

     <script type="text/javascript">

      function $(x) {
        return document.getElementById(x);
      }

      function fetchData() {
        var url = "http://localhost:8080/social/rest/people/@me/@self";
        var params = {};
        params[gadgets.io.RequestParameters.CONTENT_TYPE] =
          gadgets.io.ContentType.TEXT;
        params[gadgets.io.RequestParameters.AUTHORIZATION] =
          gadgets.io.AuthorizationType.OAUTH;
        params[gadgets.io.RequestParameters.METHOD] =
          gadgets.io.MethodType.GET;
        params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] =
          "shindig";

        gadgets.io.makeRequest(url, function (response) {
          if (response.data) {
            $('main').appendChild(document.createTextNode(response.data));
          } else {
            var whoops = document.createTextNode(
                'OAuth error: ' + response.oauthError + ': ' +
                response.oauthErrorText);
            $('main').appendChild(whoops);
          }
        }, params);
      }
      gadgets.util.registerOnLoadHandler(fetchData);
    </script>

If the developer has a need to override the view (e.g. because it's an embedded or mobile view, whatever... they could pass in a params[gadgets.io.RequestParameters.OAUTH_SERVICE_VIEW] = "myOtherView" or to tell the container to follow the exact same behavior it provides now, simply set OAUTH_SERVICE_VIEW to an empty string. If an oauth service view cannot be found, then the container would pass the redirection off to the gadget the exact same way it does now. Note that this means existing gadgets that do not specify a view attribute would be completely backwards compatible

This approach also allows us to properly support OAuth on Content, Preload and Link elements... e.g.

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Demo 3-legged OAuth to Shindig"
               icon="http://localhost:8080/samplecontainer/examples/icon.png">
    <OAuth>
      <Service name="shindig" view="oauth.shindig">
        <Request url="http://localhost:8080/oauth/requestToken" />
        <Authorization url="http://localhost:8080/oauth/authorize?oauth_callback=http://localhost:8080/gadgets/oauthcallback" />
        <Access url="http://localhost:8080/oauth/accessToken" />
      </Service>
    </OAuth>
    <Require feature="oauthpopup" />
  </ModulePrefs>
<Content type="html" authz="oauth" oauth_service_name="shindig" href="http://example.foo/content" />
  <Content type="html" view="oauth.shindig">
      <![CDATA[
    <h1>We need approval!</h1>
    <div><a href="#" id="Authorize">Authorize</a></div>
    <div><a href="#" id="Done">Authorization is Done</a></div>
    <script type="text/javascript">
      function doPrep() {
         var popup =
            new gadgets.oauth.Popup(
             response.oauthApprovalUrl,
             null);
         $('personalize').onclick = popup.createOpenerOnClick();
         $('approvaldone').onclick = popup.createApprovedOnClick();
       }
      gadgets.util.registerOnLoadHandler(doPrep);
    </script>
      ]]>
  </Content>
</Module>

hen the container attempts to proxy the remote url and detects that an access token is required, it would simply render the "oauth.shindig" view to get the appropriate authorization. The gadget retains control over the basic UX. The main requirement would be that oauth service view's would be required to not use any auth other than, perhaps, signed-fetch.