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