You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by rb...@apache.org on 2011/11/02 01:53:42 UTC

svn commit: r1196414 - in /shindig/trunk: features/src/main/javascript/features/container.util/ features/src/main/javascript/features/container/ java/common/src/main/java/org/apache/shindig/auth/ java/common/src/test/java/org/apache/shindig/auth/ java/...

Author: rbaxter85
Date: Wed Nov  2 00:53:41 2011
New Revision: 1196414

URL: http://svn.apache.org/viewvc?rev=1196414&view=rev
Log:
SHINDIG-1646
Committed for Dan Dumont.
Push down the token TTL to container page so it can schedule token refreshes accordingly.

Modified:
    shindig/trunk/features/src/main/javascript/features/container.util/constant.js
    shindig/trunk/features/src/main/javascript/features/container.util/util.js
    shindig/trunk/features/src/main/javascript/features/container/container.js
    shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AbstractSecurityToken.java
    shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BasicSecurityTokenCodec.java
    shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenCodec.java
    shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenCodec.java
    shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/SecurityTokenCodec.java
    shindig/trunk/java/common/src/test/java/org/apache/shindig/auth/UrlParameterAuthenticationHandlerTest.java
    shindig/trunk/java/common/src/test/java/org/apache/shindig/common/testing/FakeGadgetToken.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java

Modified: shindig/trunk/features/src/main/javascript/features/container.util/constant.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.util/constant.js?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.util/constant.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.util/constant.js Wed Nov  2 00:53:41 2011
@@ -56,7 +56,8 @@ osapi.container.MetadataResponse = {
   PREFERRED_HEIGHT: 'preferredHeight',
   PREFERRED_WIDTH: 'preferredWidth',
   RESPONSE_TIME_MS: 'responseTimeMs',
-  WIDTH: 'width'
+  WIDTH: 'width',
+  TOKEN_TTL: 'tokenTTL'
 };
 
 

Modified: shindig/trunk/features/src/main/javascript/features/container.util/util.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container.util/util.js?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container.util/util.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container.util/util.js Wed Nov  2 00:53:41 2011
@@ -81,7 +81,8 @@ osapi.container.util.newMetadataRequest 
       'views.preferredWidth',
       'expireTimeMs',
       'responseTimeMs',
-      'rpcServiceIds'
+      'rpcServiceIds',
+      'tokenTTL'
     ]
   };
 };

Modified: shindig/trunk/features/src/main/javascript/features/container/container.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/container/container.js?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/container/container.js (original)
+++ shindig/trunk/features/src/main/javascript/features/container/container.js Wed Nov  2 00:53:41 2011
@@ -103,13 +103,24 @@ osapi.container.Container = function(opt
       osapi.container.ContainerConfig.RENDER_TEST, false));
 
   /**
-   * Security token refresh interval (in ms) for debugging.
+   * Security token refresh interval (in ms). Set to < 0 in config to disable
+   * token refresh.
+   *
+   * Provided this number is >= 0, the smallest encountered token ttl or this
+   * number will be used as the refresh interval, whichever is smaller.
+   *
    * @type {number}
    * @private
    */
   this.tokenRefreshInterval_ = Number(osapi.container.util.getSafeJsonValue(
-      config, osapi.container.ContainerConfig.TOKEN_REFRESH_INTERVAL,
-      30 * 60 * 1000));
+      config, osapi.container.ContainerConfig.TOKEN_REFRESH_INTERVAL, 0));
+
+  /**
+   * The time of the last token refresh.
+   * @type {number}
+   * @private
+   */
+  this.lastRefresh_ = 0;
 
   /**
    * @type {number}
@@ -211,7 +222,7 @@ osapi.container.Container.prototype.navi
           gadgets.warn(['Failed to possibly schedule token refresh for gadget ',
               gadgetUrl, '.'].join(''));
         } else if (gadgetInfo[osapi.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) {
-          self.scheduleRefreshTokens_();
+          self.scheduleRefreshTokens_(gadgetInfo[osapi.container.MetadataResponse.TOKEN_TTL]);
         }
 
         self.applyLifecycleCallbacks_(osapi.container.CallbackType.ON_NAVIGATED,
@@ -527,7 +538,7 @@ osapi.container.Container.prototype.addP
       this.addPreloadedGadgetUrl_(id);
       if (response[id][osapi.container.MetadataResponse.NEEDS_TOKEN_REFRESH]) {
         // Safe to re-schedule many times.
-        this.scheduleRefreshTokens_();
+        this.scheduleRefreshTokens_(response[id][osapi.container.MetadataResponse.TOKEN_TTL]);
       }
     }
   }
@@ -584,25 +595,50 @@ osapi.container.Container.prototype.getG
   return null;
 };
 
-
 /**
  * Start to schedule refreshing of tokens.
+ * @param {number} Encountered token time to live in seconds.
  * @private
  */
-osapi.container.Container.prototype.scheduleRefreshTokens_ = function() {
-  // TODO: Obtain the interval time by taking the minimum of expiry time of
-  // token in all preloaded- and navigated-to- gadgets. This should be obtained
-  // from the server. For now, constant on 50% of long-lived tokens (1 hour),
-  // which is 30 minutes.
-  if (this.isRefreshTokensEnabled_() && !this.tokenRefreshTimer_) {
-    var self = this;
-    this.tokenRefreshTimer_ = window.setInterval(function() {
-      self.refreshTokens_();
-    }, this.tokenRefreshInterval_);
+osapi.container.Container.prototype.scheduleRefreshTokens_ = function(tokenTTL) {
+  var self = this,
+      oldInterval = this.tokenRefreshInterval_,
+      newInterval = tokenTTL ? this.setRefreshTokenInterval_(tokenTTL * 1000) : oldInterval,
+      refresh = function() {
+        self.lastRefresh_ = osapi.container.util.getCurrentTimeMs();
+        // Schedule the next refresh.
+        self.tokenRefreshTimer_ = setTimeout(refresh, newInterval);
+
+        // Do this last so that if it ever errors, we maintain the refresh schedule.
+        self.refreshTokens_();
+      };
+
+  // If enabled, check to see if we no schedule or if the two intervals are different and update the schedule.
+  if (this.isRefreshTokensEnabled_() && (!this.tokenRefreshTimer_ || newInterval != oldInterval)) {
+    var now = osapi.container.util.getCurrentTimeMs();
+    if (!this.tokenRefreshTimer_) {
+      this.lastRefresh_ = now;
+      this.tokenRefreshTimer_ = setTimeout(refresh, newInterval);
+    }
+    else {
+      var futureRefresh = (this.lastRefresh_ || 0) + oldInterval;
+      if (futureRefresh < now) {
+        // This really shouldn't happen, but if for some reason we missed a
+        // refresh, make sure we cancel any timer we have and schedule
+        // a new one.
+        futureRefresh = now + newInterval;
+        newInterval = 1;
+      }
+      if (futureRefresh > now + newInterval) {
+        // Cancel the old timer and create a new one if the next refresh is
+        // too far away.
+        clearTimeout(this.tokenRefreshTimer_);
+        this.tokenRefreshTimer_ = setTimeout(refresh, newInterval);
+      }
+    }
   }
 };
 
-
 /**
  * Stop already-scheduled refreshing of tokens.
  * @private
@@ -619,10 +655,8 @@ osapi.container.Container.prototype.unsc
 
 
 /**
- * Provides a manual override to disable token refresh to avoid gadgets.rpc
- * warning of service not found. We can do better to detect if token refresh is
- * even necessary, by inspecting the gadget transitively depend on
- * feature=security-token.
+ * Token refresh gets enabled if the value of refresh interval is > 0;
+ *
  * @return {Boolean} if token refresh interval is of valid value.
  * @private
  */
@@ -630,6 +664,30 @@ osapi.container.Container.prototype.isRe
   return this.tokenRefreshInterval_ > 0;
 };
 
+/**
+ * If the refresh interval is < 0, does nothing.  Otherwise updates the tokenTTL
+ * to the smallest value encountered.
+ *
+ * @param {number} Encountered token time to live in milliseconds.
+ * @return {Boolean} The ttl if the set succeeded, otherwise false.
+ * @private
+ */
+osapi.container.Container.prototype.setRefreshTokenInterval_ = function(tokenTTL) {
+  // TODO: Handle the case where we've closed the gadget responsible for the
+  // shortest refresh time, and can now safely extend this.tokenRefreshInterval_
+  if (tokenTTL) {
+    tokenTTL *= .8; // 80% of the TTL value, for buffer.
+    var refresh = this.tokenRefreshInterval_;
+    if (refresh < 0) {
+      return refresh;
+    }
+    else {
+      return this.tokenRefreshInterval_ =
+        refresh == 0 ? tokenTTL : Math.min(refresh, tokenTTL);
+    }
+  }
+};
+
 
 /**
  * Register standard RPC services

Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AbstractSecurityToken.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AbstractSecurityToken.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AbstractSecurityToken.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/AbstractSecurityToken.java Wed Nov  2 00:53:41 2011
@@ -38,8 +38,8 @@ public abstract class AbstractSecurityTo
   /** allow three minutes for clock skew */
   private static final long CLOCK_SKEW_ALLOWANCE = 180;
 
-  // TODO: Make configurable by ContainerConfig
-  private static final int MAX_TOKEN_TTL = 3600;
+  // TODO: Make configurable.
+  public static final int MAX_TOKEN_TTL = 3600; // 1 hour
 
   private static final TimeSource TIME_SOURCE = new TimeSource();
 

Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BasicSecurityTokenCodec.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BasicSecurityTokenCodec.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BasicSecurityTokenCodec.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BasicSecurityTokenCodec.java Wed Nov  2 00:53:41 2011
@@ -18,15 +18,15 @@
  */
 package org.apache.shindig.auth;
 
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
 import org.apache.shindig.common.crypto.BlobCrypterException;
 import org.apache.shindig.common.util.Utf8UrlCoder;
-import org.apache.commons.lang.StringUtils;
 
 import com.google.common.base.Joiner;
 import com.google.inject.Singleton;
 
-import java.util.Map;
-
 /**
  * A SecurityTokenCodec implementation that just provides dummy data to satisfy
  * tests and API calls. Do not use this for any security applications.
@@ -109,9 +109,12 @@ public class BasicSecurityTokenCodec imp
     }
   }
 
+  public int getTokenTimeToLive() {
+    return AbstractSecurityToken.MAX_TOKEN_TTL;
+  }
+
   /**
-   * Creates a signer with 24 hour token expiry
+   * Creates a basic signer
    */
-  public BasicSecurityTokenCodec() {
-  }
+  public BasicSecurityTokenCodec() {}
 }

Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenCodec.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenCodec.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenCodec.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenCodec.java Wed Nov  2 00:53:41 2011
@@ -187,4 +187,8 @@ public class BlobCrypterSecurityTokenCod
       throw new SecurityTokenException(e);
     }
   }
+
+  public int getTokenTimeToLive() {
+    return AbstractSecurityToken.MAX_TOKEN_TTL;
+  }
 }

Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenCodec.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenCodec.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenCodec.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenCodec.java Wed Nov  2 00:53:41 2011
@@ -44,7 +44,6 @@ import java.util.Map;
  */
 @Singleton
 public class DefaultSecurityTokenCodec implements SecurityTokenCodec {
-
   private static final String SECURITY_TOKEN_TYPE = "gadgets.securityTokenType";
 
   private final SecurityTokenCodec codec;
@@ -52,6 +51,7 @@ public class DefaultSecurityTokenCodec i
   @Inject
   public DefaultSecurityTokenCodec(ContainerConfig config) {
     String tokenType = config.getString(ContainerConfig.DEFAULT_CONTAINER, SECURITY_TOKEN_TYPE);
+
     if ("insecure".equals(tokenType)) {
       codec = new BasicSecurityTokenCodec();
     } else if ("secure".equals(tokenType)) {
@@ -74,4 +74,8 @@ public class DefaultSecurityTokenCodec i
     }
     return codec.encodeToken(token);
   }
+
+  public int getTokenTimeToLive() {
+    return codec.getTokenTimeToLive();
+  }
 }

Modified: shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/SecurityTokenCodec.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/SecurityTokenCodec.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/SecurityTokenCodec.java (original)
+++ shindig/trunk/java/common/src/main/java/org/apache/shindig/auth/SecurityTokenCodec.java Wed Nov  2 00:53:41 2011
@@ -53,4 +53,9 @@ public interface SecurityTokenCodec {
       throws SecurityTokenException;
 
   String encodeToken(SecurityToken token) throws SecurityTokenException;
+
+  /**
+   * @return The amount of time a token generated by this codec can be expected to live.
+   */
+  int getTokenTimeToLive();
 }

Modified: shindig/trunk/java/common/src/test/java/org/apache/shindig/auth/UrlParameterAuthenticationHandlerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/test/java/org/apache/shindig/auth/UrlParameterAuthenticationHandlerTest.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/test/java/org/apache/shindig/auth/UrlParameterAuthenticationHandlerTest.java (original)
+++ shindig/trunk/java/common/src/test/java/org/apache/shindig/auth/UrlParameterAuthenticationHandlerTest.java Wed Nov  2 00:53:41 2011
@@ -46,6 +46,10 @@ public class UrlParameterAuthenticationH
       public String encodeToken(SecurityToken token) throws SecurityTokenException {
         return null;
       }
+
+      public int getTokenTimeToLive() {
+        return 0; // Not used.
+      }
     };
 
     authHandler = new UrlParameterAuthenticationHandler(codec, true);

Modified: shindig/trunk/java/common/src/test/java/org/apache/shindig/common/testing/FakeGadgetToken.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/testing/FakeGadgetToken.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/common/src/test/java/org/apache/shindig/common/testing/FakeGadgetToken.java (original)
+++ shindig/trunk/java/common/src/test/java/org/apache/shindig/common/testing/FakeGadgetToken.java Wed Nov  2 00:53:41 2011
@@ -89,6 +89,10 @@ public class FakeGadgetToken extends Abs
     public String encodeToken(SecurityToken token) throws SecurityTokenException {
       return null; // NOT USED
     }
+
+    public int getTokenTimeToLive() {
+      return 0; // Not used.
+    }
   }
 
   public FakeGadgetToken setAuthenticationMode(String authMode) {

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerApi.java Wed Nov  2 00:53:41 2011
@@ -97,6 +97,7 @@ public class GadgetsHandlerApi {
     public Map<String, View> getViews();
     public Boolean getNeedsTokenRefresh();
     public Set<String> getRpcServiceIds();
+    public Integer getTokenTTL();
   }
 
   public enum ViewContentType {

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerService.java Wed Nov  2 00:53:41 2011
@@ -212,8 +212,12 @@ public class GadgetsHandlerService {
             gadget.getAllFeatures().contains("auth-refresh") : null;
 
     Set<String> rpcServiceIds = getRpcServiceIds(gadget);
+
+    Integer tokenTTL = isFieldIncluded(fields, "tokenTTL") ?
+        securityTokenCodec.getTokenTimeToLive() : null;
+
     return createMetadataResponse(context.getUrl(), gadget.getSpec(), iframeUrl,
-        needsTokenRefresh, fields, timeSource.currentTimeMillis() + specRefreshInterval,
+        needsTokenRefresh, fields, timeSource.currentTimeMillis() + specRefreshInterval, tokenTTL,
         rpcServiceIds);
   }
 
@@ -524,7 +528,7 @@ public class GadgetsHandlerService {
   @VisibleForTesting
   GadgetsHandlerApi.MetadataResponse createMetadataResponse(
       Uri url, GadgetSpec spec, String iframeUrl, Boolean needsTokenRefresh,
-      Set<String> fields, Long expireTime, Set<String> rpcServiceIds) {
+      Set<String> fields, Long expireTime, Integer tokenTTL, Set<String> rpcServiceIds) {
     return (GadgetsHandlerApi.MetadataResponse) beanFilter.createFilteredBean(
         beanDelegator.createDelegator(spec, GadgetsHandlerApi.MetadataResponse.class,
             ImmutableMap.<String, Object>builder()
@@ -534,7 +538,8 @@ public class GadgetsHandlerService {
                 .put("needstokenrefresh", BeanDelegator.nullable(needsTokenRefresh))
                 .put("responsetimems", timeSource.currentTimeMillis())
                 .put("expiretimems", BeanDelegator.nullable(expireTime))
-                .put("rpcserviceids", BeanDelegator.nullable(rpcServiceIds)).build()),
+                .put("rpcserviceids", BeanDelegator.nullable(rpcServiceIds))
+                .put("tokenttl", BeanDelegator.nullable(tokenTTL)).build()),
         fields);
   }
 

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerServiceTest.java Wed Nov  2 00:53:41 2011
@@ -744,5 +744,9 @@ public class GadgetsHandlerServiceTest e
       }
       return authContext;
     }
+
+    public int getTokenTimeToLive() {
+      return 0;  // Not used.
+    }
   }
 }

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java?rev=1196414&r1=1196413&r2=1196414&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/GadgetsHandlerTest.java Wed Nov  2 00:53:41 2011
@@ -30,6 +30,7 @@ import java.util.Map;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.shindig.auth.BasicSecurityTokenCodec;
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.auth.SecurityTokenCodec;
 import org.apache.shindig.auth.SecurityTokenException;
@@ -440,6 +441,36 @@ public class GadgetsHandlerTest extends 
   }
 
   @Test
+  public void testMetadataOneGadgetRequestTokenTTLParam() throws Exception {
+    SecurityTokenCodec codec = createMock(SecurityTokenCodec.class);
+    expect(codec.getTokenTimeToLive()).andReturn(42).anyTimes();
+    replay(codec);
+
+    registerGadgetsHandler(codec);
+    setupGadgetAdminStore();
+    setupMockRegistry(Lists.newArrayList("core"));
+    JSONObject request = makeMetadataRequest(null, null, new String[]{"tokenTTL", "iframeurl"}, GADGET1_URL);
+    RpcHandler operation = registry.getRpcHandler(request);
+    Object responseObj = operation.execute(emptyFormItems, authContext, converter).get();
+    JSONObject response = new JSONObject(converter.convertToString(responseObj));
+
+    assertEquals(42, response.getJSONObject(FakeProcessor.SPEC_URL.toString()).getInt("tokenTTL"));
+  }
+
+  @Test
+  public void testMetadataOneGadgetNoRequestTokenTTLParam() throws Exception {
+    registerGadgetsHandler(null);
+    setupGadgetAdminStore();
+    setupMockRegistry(Lists.newArrayList("core"));
+    JSONObject request = makeMetadataRequest(null, null, null, GADGET1_URL);
+    RpcHandler operation = registry.getRpcHandler(request);
+    Object responseObj = operation.execute(emptyFormItems, authContext, converter).get();
+    JSONObject response = new JSONObject(converter.convertToString(responseObj));
+
+    assertFalse(response.getJSONObject(FakeProcessor.SPEC_URL.toString()).has("tokenTTL"));
+  }
+
+  @Test
   public void testAllowedRpcSecurityIds() throws Exception {
     registerGadgetsHandler(null);
     setupMockRegistry(Lists.newArrayList("core"));