From 6534f4a523909cc5e74a4410834dacbb8d3f7e59 Mon Sep 17 00:00:00 2001
From: Shane Tomlinson <stomlinson@mozilla.com>
Date: Fri, 20 Apr 2012 15:24:42 +0100
Subject: [PATCH] Put RP name and logo into the dialog.

* Add a new controller and template - rp_info
* Adjust the screen size hacks to take into account the dynamic height "favicon"
* Add an action to start the rp_info service.

* properly check and escape logoURL and name in dialog.js
* Add some tests ensuring logoURL and name are escaped.
* Print information in rp_info controller.

issue #1098
---
 example/rp/i/card.png                         | Bin 0 -> 5906 bytes
 example/rp/index.html                         |  13 ++
 lib/static_resources.js                       |   2 +-
 .../static/dialog/controllers/actions.js      |   4 +
 resources/static/dialog/controllers/dialog.js |  18 ++-
 .../static/dialog/controllers/rp_info.js      |  47 +++++++
 resources/static/dialog/css/m.css             |  31 ++++-
 resources/static/dialog/css/popup.css         |  18 ++-
 .../dialog/resources/screen_size_hacks.js     |   9 +-
 resources/static/dialog/resources/state.js    |   2 +
 resources/static/dialog/start.js              |   1 +
 resources/static/dialog/views/rp_info.ejs     |  21 ++++
 .../static/test/cases/controllers/dialog.js   | 118 ++++++++++++++++++
 .../static/test/cases/controllers/rp_info.js  |  88 +++++++++++++
 resources/views/dialog.ejs                    |   3 +-
 resources/views/test.ejs                      |   5 +
 16 files changed, 367 insertions(+), 13 deletions(-)
 create mode 100644 example/rp/i/card.png
 create mode 100644 resources/static/dialog/controllers/rp_info.js
 create mode 100644 resources/static/dialog/views/rp_info.ejs
 create mode 100644 resources/static/test/cases/controllers/rp_info.js

diff --git a/example/rp/i/card.png b/example/rp/i/card.png
new file mode 100644
index 0000000000000000000000000000000000000000..eac93fc8481e85d226d934eaa30a2b3b4737837d
GIT binary patch
literal 5906
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
ZKZtBqaksBSn4jEODvBBkFnNohe*xQkgn|G7

literal 0
HcmV?d00001

diff --git a/example/rp/index.html b/example/rp/index.html
index 8487ef70d..1ff44aa9e 100644
--- a/example/rp/index.html
+++ b/example/rp/index.html
@@ -69,6 +69,14 @@ pre {
       <input type="text" id="requiredEmail" width="80">
       <label for="requiredEmail">Require a specific email</label><br />
     </li>
+    </li><li>
+      <input type="text" id="name" width="80">
+      <label for="name">Site Name (optional)</label><br />
+    </li>
+    </li><li>
+      <input type="text" id="logoURL" width="80">
+      <label for="logoURL">Site Logo Path (optional)</label><br />
+    </li>
   </ul>
     <button class="assertion">Get an assertion</button>
     <button class="logout">logout</button>
@@ -178,12 +186,17 @@ $(document).ready(function() {
       return;
     }
 
+    var logoURL = $.trim($('#logoURL').val());
+    var name = $.trim($('#name').val());
+
     $(".specify button.assertion").attr('disabled', 'true');
 
     navigator.id.request({
       privacyURL: $('#privacy').attr('checked') ? "/privacy.html" : undefined,
       tosURL: $('#tos').attr('checked') ? "/TOS.html" : undefined,
       requiredEmail: requiredEmail,
+      name: name,
+      logoURL: logoURL,
       oncancel: function() {
         loggit("oncancel");
         $(".specify button.assertion").removeAttr('disabled');
diff --git a/lib/static_resources.js b/lib/static_resources.js
index ad17eac88..bcf8a92f3 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -104,7 +104,7 @@ var dialog_js = und.flatten([
     '/dialog/controllers/generate_assertion.js',
     '/dialog/controllers/is_this_your_computer.js',
     '/dialog/controllers/set_password.js',
-
+    '/dialog/controllers/rp_info.js',
     '/dialog/start.js'
   ]]);
 
diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js
index e01cb576c..80435e522 100644
--- a/resources/static/dialog/controllers/actions.js
+++ b/resources/static/dialog/controllers/actions.js
@@ -58,6 +58,10 @@ BrowserID.Modules.Actions = (function() {
       if(data.ready) _.defer(data.ready);
     },
 
+    doRPInfo: function(info) {
+      startService("rp_info", info);
+    },
+
     doCancel: function() {
       if(onsuccess) onsuccess(null);
     },
diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js
index 8e5b8f1cd..740994a93 100644
--- a/resources/static/dialog/controllers/dialog.js
+++ b/resources/static/dialog/controllers/dialog.js
@@ -75,7 +75,6 @@ BrowserID.Modules.Dialog = (function() {
 
   function setOrigin(origin) {
     user.setOrigin(origin);
-    dom.setInner("#sitename", user.getHostname());
   }
 
   function onWindowUnload() {
@@ -93,6 +92,12 @@ BrowserID.Modules.Dialog = (function() {
     return encodeURI(u.validate().normalize().toString());
   }
 
+  function fixupAbsolutePath(origin_url, path) {
+    if (/^\//.test(path))  return fixupURL(origin_url, path);
+
+    throw "must be an absolute path: (" + path + ")";
+  }
+
   var Dialog = bid.Modules.PageModule.extend({
     start: function(options) {
       var self=this;
@@ -168,6 +173,17 @@ BrowserID.Modules.Dialog = (function() {
           params.privacyURL = fixupURL(origin_url, paramsFromRP.privacyPolicy);
         }
 
+        if (paramsFromRP.logoURL) {
+          // Until we have our head around the dangers of data uris and images
+          // that come from other domains, only allow absolute paths from the
+          // origin.
+          params.logoURL = fixupAbsolutePath(origin_url, paramsFromRP.logoURL);
+        }
+
+        if (paramsFromRP.name) {
+          params.name = _.escape(paramsFromRP.name);
+        }
+
         if (hash.indexOf("#CREATE_EMAIL=") === 0) {
           var email = hash.replace(/#CREATE_EMAIL=/, "");
           if (!bid.verifyEmail(email))
diff --git a/resources/static/dialog/controllers/rp_info.js b/resources/static/dialog/controllers/rp_info.js
new file mode 100644
index 000000000..92384bfca
--- /dev/null
+++ b/resources/static/dialog/controllers/rp_info.js
@@ -0,0 +1,47 @@
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*global _: true, BrowserID: true, PageController: true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**
+ * Purpose:
+ *  Display to the user RP related data such as hostname, name, and logo.
+ */
+BrowserID.Modules.RPInfo = (function() {
+  "use strict";
+
+  var bid = BrowserID,
+      renderer = bid.Renderer,
+      sc;
+
+  var Module = bid.Modules.PageModule.extend({
+    start: function(options) {
+      options = options || {};
+
+      /**
+       * Very important security info - it is assumed all parameters are
+       * already properly escaped before being passed here.  This is done
+       * in dialog.js.  Check it.
+       *
+       * hostname is set internally based on the RP URL,
+       * so it will not be escaped.  It is set initially in user.js at the very
+       * bottom for the main site, and then in dialog.js->get for the dialog.
+       */
+      renderer.render("#rp_info", "rp_info", {
+        hostname: options.hostname,
+        name: options.name,
+        logoURL: options.logoURL
+      });
+
+      sc.start.call(this, options);
+    }
+  });
+
+  sc = Module.sc;
+
+  return Module;
+
+}());
+
diff --git a/resources/static/dialog/css/m.css b/resources/static/dialog/css/m.css
index 6991dbe02..efc23a635 100644
--- a/resources/static/dialog/css/m.css
+++ b/resources/static/dialog/css/m.css
@@ -43,7 +43,7 @@
   }
 
   #signIn {
-    top: 45px;  /* 45px is a magic number - the height of the favicon area */
+    top: auto;  /* this will be set in JS to be at the bottom of the header */
     right: 0;
     width: auto;
     padding: 0;
@@ -57,13 +57,32 @@
        * being partially cut off by the site URL bar
        */
       position: static;
+      padding: 10px;
+      border-bottom: 1px solid rgba(0,0,0,0.05);
+      background-image: url('/i/bg.png');
+      text-align: center;
+      left: 0;
   }
 
-  #favicon {
-    padding: 10px;
+  #favicon img {
+      max-width: 32px;
+      max-height: 32px;
+      display: inline;
+      margin: 0;
+      vertical-align: middle;
+  }
+
+  #favicon h2, #favicon h3 {
+      margin: 5px 0 0 0;
   }
 
-  #signIn .table {
+  #favicon .vertical {
+      height: auto;
+      line-height: 20px;
+  }
+
+
+  #signIn .table, #signIn .container {
       width: 100%;
   }
 
@@ -96,6 +115,10 @@
       line-height: 20px;
   }
 
+  #formWrap {
+      background-color: transparent;
+  }
+
   .form #formWrap, .waiting #wait, .delay #delay, #error #error {
       display: block;
   }
diff --git a/resources/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css
index c55543e57..b1d96da23 100644
--- a/resources/static/dialog/css/popup.css
+++ b/resources/static/dialog/css/popup.css
@@ -270,7 +270,8 @@ section > .contents {
     left: 400px;
     top: 0;
     bottom: 0;
-    right: 0;
+    right: 20px; /* The same as the left padding of the left hand side */
+    overflow: hidden;
     z-index: 10;
 }
 
@@ -282,14 +283,23 @@ section > .contents {
 
 #favicon img {
     display: block;
-    margin: 0 auto 10px;
+    margin: 0 auto;
+    max-height: 128px;
+    max-width: 128px;
+}
+
+#favicon h2, #favicon h3 {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    height: 1.2em;  /* the 1.2em is to keep y, g, j, etc from having their bottoms chopped off */
+    overflow: hidden;
+    margin: 10px 0 0 0;
 }
 
 #favicon .vertical {
     display: table-cell;
     text-align: center;
-    overflow: hidden;
-    text-overflow: ellipsis;
+    max-width: 0;
 }
 
 div#required_email {
diff --git a/resources/static/dialog/resources/screen_size_hacks.js b/resources/static/dialog/resources/screen_size_hacks.js
index 503d84852..196060f86 100644
--- a/resources/static/dialog/resources/screen_size_hacks.js
+++ b/resources/static/dialog/resources/screen_size_hacks.js
@@ -8,13 +8,15 @@
    */
   function onResize() {
     var selectEmailEl = $("#selectEmail"),
-        contentEl = $("#content");
+        contentEl = $("#content"),
+        signInEl = $("#signIn");
 
     selectEmailEl.css("position", "static");
     if($(window).width() >= 640) {
       // First, remove the mobile hacks
       selectEmailEl.css("width", "");
       contentEl.css("min-height", "");
+      signInEl.css("top", "");
 
       // This is a hack for desktop mode which centers the form vertically in
       // the middle of its container.  We have to do this hack because we use
@@ -105,6 +107,11 @@
         contentEl.css("min-height", contentHeight + "px");
 
         $("section,#signIn").css("position", "");
+
+        favIconHeight = $("#favicon").outerHeight();
+
+        // Force the top of the main content area to be below the favicon area.
+        signInEl.css("top", (headerHeight + favIconHeight) + "px");
     }
 
     selectEmailEl.css("position", "");
diff --git a/resources/static/dialog/resources/state.js b/resources/static/dialog/resources/state.js
index 8a016d6f5..c56eee0dd 100644
--- a/resources/static/dialog/resources/state.js
+++ b/resources/static/dialog/resources/state.js
@@ -46,6 +46,8 @@ BrowserID.State = (function() {
       self.tosURL = info.tosURL;
       requiredEmail = info.requiredEmail;
 
+      startAction(false, "doRPInfo", info);
+
       if (info.email && info.type === "primary") {
         primaryVerificationInfo = info;
         redirectToState("primary_user", info);
diff --git a/resources/static/dialog/start.js b/resources/static/dialog/start.js
index 543136b02..70c574bad 100644
--- a/resources/static/dialog/start.js
+++ b/resources/static/dialog/start.js
@@ -42,6 +42,7 @@
       moduleManager.register("xhr_delay", modules.XHRDelay);
       moduleManager.register("xhr_disable_form", modules.XHRDisableForm);
       moduleManager.register("set_password", modules.SetPassword);
+      moduleManager.register("rp_info", modules.RPInfo);
 
       moduleManager.start("xhr_delay");
       moduleManager.start("xhr_disable_form");
diff --git a/resources/static/dialog/views/rp_info.ejs b/resources/static/dialog/views/rp_info.ejs
new file mode 100644
index 000000000..dd3be3f92
--- /dev/null
+++ b/resources/static/dialog/views/rp_info.ejs
@@ -0,0 +1,21 @@
+<% /* This Source Code Form is subject to the terms of the Mozilla Public
+      License, v. 2.0. If a copy of the MPL was not distributed with this
+      file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %>
+
+<% if(logoURL) { %>
+  <img id="rp_logo" src="<%= logoURL %>" />
+<% } %>
+
+
+<% if(name) { %>
+  <h2 id="rp_name"><%= name %></h2>
+<% } %>
+
+<% if(hostname) { %>
+  <% if(name) { %>
+    <h3 id="rp_hostname"><%= hostname %></h3>
+  <% } else { %>
+    <h2 id="rp_hostname"><%= hostname %></h2>
+  <% } %>
+<% } %>
+
diff --git a/resources/static/test/cases/controllers/dialog.js b/resources/static/test/cases/controllers/dialog.js
index 4190cd915..6efbdefb2 100644
--- a/resources/static/test/cases/controllers/dialog.js
+++ b/resources/static/test/cases/controllers/dialog.js
@@ -469,5 +469,123 @@
     });
   });
 
+  asyncTest("get with relative logoURL - not allowed", function() {
+    createController({
+      ready: function() {
+        mediator.subscribe("start", function(msg, info) {
+          ok(false, "start should not have been called");
+        });
+
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          logoURL: "logo.png",
+        });
+
+        equal(retval, "must be an absolute path: (logo.png)", "expected error");
+        testErrorVisible();
+        start();
+      }
+    });
+  });
+
+  asyncTest("get with javascript: logoURL - not allowed", function() {
+    createController({
+      ready: function() {
+        mediator.subscribe("start", function(msg, info) {
+          ok(false, "start should not have been called");
+        });
+
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          logoURL: "javascript:alert('xss')",
+        });
+
+        equal(retval, "must be an absolute path: (javascript:alert('xss'))", "expected error");
+        testErrorVisible();
+        start();
+      }
+    });
+  });
+
+  asyncTest("get with data-uri: logoURL - not allowed", function() {
+    createController({
+      ready: function() {
+        mediator.subscribe("start", function(msg, info) {
+          ok(false, "start should not have been called");
+        });
+
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          logoURL: "data:image/png,FAKEDATA",
+        });
+
+        equal(retval, "must be an absolute path: (data:image/png,FAKEDATA)", "expected error");
+        testErrorVisible();
+        start();
+      }
+    });
+  });
+
+  asyncTest("get with http: logoURL - not allowed", function() {
+    createController({
+      ready: function() {
+        mediator.subscribe("start", function(msg, info) {
+          ok(false, "start should not have been called");
+        });
+
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          logoURL: HTTP_TEST_DOMAIN + "://logo.png",
+        });
+
+        equal(retval, "must be an absolute path: (" + HTTP_TEST_DOMAIN + "://logo.png)", "expected error");
+        testErrorVisible();
+        start();
+      }
+    });
+  });
+
+  asyncTest("get with https: logoURL - not allowed", function() {
+    createController({
+      ready: function() {
+        mediator.subscribe("start", function(msg, info) {
+          ok(false, "start should not have been called");
+        });
+
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          logoURL: HTTPS_TEST_DOMAIN + "://logo.png",
+        });
+
+        equal(retval, "must be an absolute path: (" + HTTPS_TEST_DOMAIN + "://logo.png)", "expected error");
+        testErrorVisible();
+        start();
+      }
+    });
+  });
+
+  asyncTest("get with absolute path - allowed URL but it must be properly escaped", function() {
+    createController({
+      ready: function() {
+        var startInfo;
+        mediator.subscribe("start", function(msg, info) {
+          startInfo = info;
+        });
+
+        var logoURL = '/i/card.png" onerror="alert(\'xss\')" <script>alert(\'more xss\')</script>';
+        var retval = controller.get(HTTP_TEST_DOMAIN, {
+          logoURL: logoURL
+        });
+
+        start();
+
+        testHelpers.testObjectValuesEqual(startInfo, {
+          logoURL: encodeURI(HTTP_TEST_DOMAIN + logoURL)
+        });
+        equal(typeof retval, "undefined", "no error expected");
+        testErrorNotVisible();
+        start();
+      }
+    });
+
+  });
+
+
+
 }());
 
diff --git a/resources/static/test/cases/controllers/rp_info.js b/resources/static/test/cases/controllers/rp_info.js
new file mode 100644
index 000000000..07a57d1c4
--- /dev/null
+++ b/resources/static/test/cases/controllers/rp_info.js
@@ -0,0 +1,88 @@
+/*jshint browsers:true, forin: true, laxbreak: true */
+/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+(function() {
+  "use strict";
+
+  var controller,
+      bid = BrowserID,
+      user = bid.User,
+      testHelpers = bid.TestHelpers,
+      register = bid.TestHelpers.register,
+      WindowMock = bid.Mocks.WindowMock,
+      RP_HOSTNAME = "hostname.org",
+      RP_NAME = "RP Name",
+      RP_HTTPS_LOGO = "https://en.gravatar.com/userimage/6966791/c4feac761b8544cce13e0406f36230aa.jpg";
+
+  module("controllers/rp_info", {
+    setup: testHelpers.setup,
+
+    teardown: function() {
+      if (controller) {
+        try {
+          controller.destroy();
+          controller = null;
+        } catch(e) {
+          // could already be destroyed from the close
+        }
+      }
+      window.scriptRun = null;
+      delete window.scriptRun;
+      testHelpers.teardown();
+    }
+  });
+
+
+  function createController(options) {
+    options = _.extend({ hostname: RP_HOSTNAME }, options);
+
+    controller = bid.Modules.RPInfo.create();
+    controller.start(options || {});
+  }
+
+  test("neither name nor logo specified - use site's rp_hostname as name", function() {
+    createController();
+    equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in");
+    ok(!$("#rp_name").html(), "rp_name empty");
+    ok(!$("#rp_logo").attr("src"), "rp logo not shown");
+  });
+
+  test("name only specified - show specified name and rp_hostname", function() {
+    createController({
+      name: RP_NAME,
+    });
+
+    equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in");
+    equal($("#rp_name").html(), RP_NAME, "rp_name filled in");
+    ok(!$("#rp_logo").attr("src"), "rp logo not shown");
+  });
+
+  test("logoURLs are allowed", function() {
+    var docMock = new WindowMock().document;
+    docMock.location.protocol = "http:";
+
+    createController({
+      document: docMock,
+      logoURL: RP_HTTPS_LOGO
+    });
+
+    equal($("#rp_logo").attr("src"), RP_HTTPS_LOGO, "rp logo shown");
+    equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in");
+    ok(!$("#rp_name").html(), "rp_name empty");
+  });
+
+  test("both name and logo specified - show name, logo and rp_hostname", function() {
+    createController({
+      name: RP_NAME,
+      logoURL: RP_HTTPS_LOGO
+    });
+
+    equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in");
+    equal($("#rp_name").html(), RP_NAME, "rp_name filled in");
+    equal($("#rp_logo").attr("src"), RP_HTTPS_LOGO, "rp logo shown");
+  });
+
+}());
+
diff --git a/resources/views/dialog.ejs b/resources/views/dialog.ejs
index 1752efdd3..9bf2250be 100644
--- a/resources/views/dialog.ejs
+++ b/resources/views/dialog.ejs
@@ -5,8 +5,7 @@
       <form novalidate>
         <div id="favicon">
             <div class="table">
-                <div class="vertical">
-                    <strong id="sitename"></strong>
+                <div class="vertical" id="rp_info">
                 </div>
             </div>
         </div>
diff --git a/resources/views/test.ejs b/resources/views/test.ejs
index dd25eca2f..8f6ea43fe 100644
--- a/resources/views/test.ejs
+++ b/resources/views/test.ejs
@@ -25,6 +25,9 @@
         <a href="#" onclick="$('#contents').hide(); return false;">Close</a>
         <h3>Test Contents, this will be updated and can be safely ignored</h3>
 
+        <div id="rp_info">
+        </div>
+
         <div id="page_head">
         </div>
 
@@ -131,6 +134,7 @@
     <script src="/dialog/controllers/primary_user_provisioned.js"></script>
     <script src="/dialog/controllers/is_this_your_computer.js"></script>
     <script src="/dialog/controllers/set_password.js"></script>
+    <script src="/dialog/controllers/rp_info.js"></script>
 
     <script src="/pages/page_helpers.js"></script>
     <script src="/pages/verify_secondary_address.js"></script>
@@ -191,6 +195,7 @@
     <script src="cases/controllers/primary_user_provisioned.js"></script>
     <script src="cases/controllers/is_this_your_computer.js"></script>
     <script src="cases/controllers/set_password.js"></script>
+    <script src="cases/controllers/rp_info.js"></script>
 
     <!-- must go last or all other tests will fail. -->
     <script src="cases/controllers/dialog.js"></script>
-- 
GitLab