Include site header, footer and CSS on OpenID login form

Bug: issue 1879
Change-Id: I1c583e01433d09627ac845288cd67db35029ae58
This commit is contained in:
Shawn Pearce
2013-04-23 13:49:57 -07:00
parent 98c89249c5
commit b216ac07ec
7 changed files with 283 additions and 151 deletions

View File

@@ -21,6 +21,7 @@ import com.google.common.base.Strings;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.template.SiteHeaderFooter;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -60,14 +61,18 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
private final SchemaFactory<ReviewDb> schema;
private final Provider<WebSession> webSession;
private final AccountManager accountManager;
private final SiteHeaderFooter headers;
@Inject
BecomeAnyAccountLoginServlet(final Provider<WebSession> ws,
final SchemaFactory<ReviewDb> sf,
final AccountManager am, final ServletContext servletContext) {
final AccountManager am,
final ServletContext servletContext,
SiteHeaderFooter shf) {
webSession = ws;
schema = sf;
accountManager = am;
headers = shf;
}
@Override
@@ -149,7 +154,7 @@ class BecomeAnyAccountLoginServlet extends HttpServlet {
private byte[] prepareHtmlOutput() throws IOException, OrmException {
final String pageName = "BecomeAnyAccount.html";
final Document doc = HtmlDomUtil.parseFile(getClass(), pageName);
Document doc = headers.parse(getClass(), pageName);
if (doc == null) {
throw new FileNotFoundException("No " + pageName + " in webapp");
}

View File

@@ -19,6 +19,7 @@ import com.google.common.base.Strings;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.template.SiteHeaderFooter;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountUserNameException;
@@ -26,7 +27,6 @@ import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.auth.AuthenticationUnavailableException;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtexpui.server.CacheHeaders;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.File;
import java.io.IOException;
import javax.annotation.Nullable;
@@ -57,17 +56,17 @@ class LdapLoginServlet extends HttpServlet {
private final AccountManager accountManager;
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final SitePaths sitePaths;
private final SiteHeaderFooter headers;
@Inject
LdapLoginServlet(AccountManager accountManager,
Provider<WebSession> webSession,
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
SitePaths sitePaths) {
SiteHeaderFooter headers) {
this.accountManager = accountManager;
this.webSession = webSession;
this.urlProvider = urlProvider;
this.sitePaths = sitePaths;
this.headers = headers;
if (Strings.isNullOrEmpty(urlProvider.get())) {
log.error("gerrit.canonicalWebUrl must be set in gerrit.config");
@@ -83,13 +82,7 @@ class LdapLoginServlet extends HttpServlet {
cancel += "#" + token;
}
Document doc =
HtmlDomUtil.parseFile(LdapLoginServlet.class, "LoginForm.html");
injectCssFile(doc, "gerrit_sitecss", sitePaths.site_css);
injectXmlFile(doc, "gerrit_header", sitePaths.site_header);
injectXmlFile(doc, "gerrit_footer", sitePaths.site_footer);
Document doc = headers.parse(LdapLoginServlet.class, "LoginForm.html");
HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName());
HtmlDomUtil.find(doc, "login_form").setAttribute("action", self);
HtmlDomUtil.find(doc, "cancel_link").setAttribute("href", cancel);
@@ -114,42 +107,6 @@ class LdapLoginServlet extends HttpServlet {
}
}
private void injectCssFile(final Document hostDoc, final String id,
final File src) throws IOException {
final Element banner = HtmlDomUtil.find(hostDoc, id);
if (banner != null) {
while (banner.getFirstChild() != null) {
banner.removeChild(banner.getFirstChild());
}
String css = HtmlDomUtil.readFile(src.getParentFile(), src.getName());
if (css == null) {
banner.getParentNode().removeChild(banner);
} else {
banner.removeAttribute("id");
banner.appendChild(hostDoc.createCDATASection("\n" + css + "\n"));
}
}
}
private void injectXmlFile(final Document hostDoc, final String id,
final File src) throws IOException {
final Element banner = HtmlDomUtil.find(hostDoc, id);
if (banner != null) {
while (banner.getFirstChild() != null) {
banner.removeChild(banner.getFirstChild());
}
Document html = HtmlDomUtil.parseFile(src);
if (html == null) {
banner.getParentNode().removeChild(banner);
} else {
final Element content = html.getDocumentElement();
banner.appendChild(hostDoc.importNode(content, true));
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException {

View File

@@ -0,0 +1,150 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.httpd.template;
import com.google.common.base.Strings;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.File;
import java.io.IOException;
@Singleton
public class SiteHeaderFooter {
private static final Logger log = LoggerFactory.getLogger(SiteHeaderFooter.class);
private final boolean refreshHeaderFooter;
private final SitePaths sitePaths;
private volatile Template template;
@Inject
SiteHeaderFooter(@GerritServerConfig Config cfg, SitePaths sitePaths) {
this.refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
this.sitePaths = sitePaths;
Template t = new Template(sitePaths);
try {
t.load();
} catch (IOException e) {
log.warn("Cannot load site header or footer", e);
}
template = t;
}
public Document parse(Class<?> clazz, String name) throws IOException {
Template t = template;
if (refreshHeaderFooter && t.isStale()) {
t = new Template(sitePaths);
try {
t.load();
template = t;
} catch (IOException e) {
log.warn("Cannot refresh site header or footer", e);
t = template;
}
}
Document doc = HtmlDomUtil.parseFile(clazz, name);
injectCss(doc, "gerrit_sitecss", t.css);
injectXml(doc, "gerrit_header", t.header);
injectXml(doc, "gerrit_footer", t.footer);
return doc;
}
private void injectCss(Document doc, String id, String content) {
Element e = HtmlDomUtil.find(doc, id);
if (e != null) {
if (!Strings.isNullOrEmpty(content)) {
while (e.getFirstChild() != null) {
e.removeChild(e.getFirstChild());
}
e.removeAttribute("id");
e.appendChild(doc.createCDATASection("\n" + content + "\n"));
} else {
e.getParentNode().removeChild(e);
}
}
}
private void injectXml(Document doc, String id, Element d) {
Element e = HtmlDomUtil.find(doc, id);
if (e != null) {
if (d != null) {
while (e.getFirstChild() != null) {
e.removeChild(e.getFirstChild());
}
e.appendChild(doc.importNode(d, true));
} else {
e.getParentNode().removeChild(e);
}
}
}
private static class Template {
private final FileInfo cssFile;
private final FileInfo headerFile;
private final FileInfo footerFile;
String css;
Element header;
Element footer;
Template(SitePaths site) {
cssFile = new FileInfo(site.site_css);
headerFile = new FileInfo(site.site_header);
footerFile = new FileInfo(site.site_footer);
}
void load() throws IOException {
css = HtmlDomUtil.readFile(
cssFile.path.getParentFile(),
cssFile.path.getName());
header = readXml(headerFile);
footer = readXml(footerFile);
}
boolean isStale() {
return cssFile.isStale() || headerFile.isStale() || footerFile.isStale();
}
private static Element readXml(FileInfo src) throws IOException {
Document d = HtmlDomUtil.parseFile(src.path);
return d != null ? d.getDocumentElement() : null;
}
}
private static class FileInfo {
final File path;
final long time;
FileInfo(File p) {
path = p;
time = path.lastModified();
}
boolean isStale() {
return time != path.lastModified();
}
}
}

View File

@@ -29,51 +29,59 @@
}
})();
</script>
<style id="gerrit_sitecss" type="text/css"></style>
</head>
<body>
<h2>Sign In</h2>
<table border="0">
<tr>
<th>Username:</th>
<td>
<form method="GET">
<input type="text" size="30" name="user_name" />
<input type="submit" value="Become Account" />
</form>
</td>
</tr>
<div id="gerrit_topmenu" style="height:45px;" class="gerritTopMenu"></div>
<div id="gerrit_header"></div>
<div id="gerrit_body" class="gerritBody">
<h2>Sign In</h2>
<table border="0">
<tr>
<th>Username:</th>
<td>
<form method="GET">
<input type="text" size="30" name="user_name" />
<input type="submit" value="Become Account" />
</form>
</td>
</tr>
<tr>
<th>Email Address:</th>
<td>
<form method="GET">
<input type="text" size="30" name="preferred_email" />
<input type="submit" value="Become Account" />
</form>
</td>
</tr>
<tr>
<th>Email Address:</th>
<td>
<form method="GET">
<input type="text" size="30" name="preferred_email" />
<input type="submit" value="Become Account" />
</form>
</td>
</tr>
<tr>
<th>Account ID:</th>
<td>
<form method="GET">
<input type="text" size="12" name="account_id" />
<input type="submit" value="Become Account" />
</form>
</td>
</tr>
<tr>
<th>Account ID:</th>
<td>
<form method="GET">
<input type="text" size="12" name="account_id" />
<input type="submit" value="Become Account" />
</form>
</td>
</tr>
<tr>
<th>Choose:</th>
<td id="userlist"/>
</tr>
</table>
<tr>
<th>Choose:</th>
<td id="userlist"/>
</tr>
</table>
<hr />
<h2>Register</h2>
<form method="POST">
<input type="hidden" name="action" value="create_account" />
<input type="submit" value="New Account" />
</form>
<hr />
<h2>Register</h2>
<form method="POST">
<input type="hidden" name="action" value="create_account" />
<input type="submit" value="New Account" />
</form>
</div>
<div style="clear: both; margin-top: 15px; padding-top: 2px; margin-bottom: 15px;">
<div id="gerrit_footer"></div>
</div>
</body>
</html>

View File

@@ -13,7 +13,7 @@
margin-left: 45px;
}
</style>
<style id="gerrit_sitecss" type="text/css"></style>
<style id="gerrit_sitecss" type="text/css"></style>
</head>
<body>
<div id="gerrit_topmenu" style="height:45px;" class="gerritTopMenu"></div>
@@ -56,28 +56,28 @@
</tr>
</table>
</form>
<script type="text/javascript">
var login_form = document.getElementById('login_form');
var f_user = document.getElementById('f_user');
var f_pass = document.getElementById('f_pass');
f_user.onkeydown = function(e) {
if (e.keyCode == 13) {
f_pass.focus();
return false;
}
}
f_pass.onkeydown = function(e) {
if (e.keyCode == 13) {
login_form.submit();
return false;
}
}
f_user.focus();
</script>
<div style="clear: both; margin-top: 15px; padding-top: 2px; margin-bottom: 15px;">
<div id="gerrit_footer"></div>
</div>
</div>
<script type="text/javascript">
var login_form = document.getElementById('login_form');
var f_user = document.getElementById('f_user');
var f_pass = document.getElementById('f_pass');
f_user.onkeydown = function(e) {
if (e.keyCode == 13) {
f_pass.focus();
return false;
}
}
f_pass.onkeydown = function(e) {
if (e.keyCode == 13) {
login_form.submit();
return false;
}
}
f_user.focus();
</script>
</body>
</html>

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.template.SiteHeaderFooter;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -62,15 +63,18 @@ class LoginForm extends HttpServlet {
private final OpenIdServiceImpl impl;
private final int maxRedirectUrlLength;
private final String ssoUrl;
private final SiteHeaderFooter header;
@Inject
LoginForm(
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
@GerritServerConfig Config config,
AuthConfig authConfig,
OpenIdServiceImpl impl) {
OpenIdServiceImpl impl,
SiteHeaderFooter header) {
this.urlProvider = urlProvider;
this.impl = impl;
this.header = header;
this.maxRedirectUrlLength = config.getInt(
"openid", "maxRedirectUrlLength",
10);
@@ -231,7 +235,7 @@ class LoginForm extends HttpServlet {
cancel += "#" + token;
}
Document doc = HtmlDomUtil.parseFile(LoginForm.class, "LoginForm.html");
Document doc = header.parse(LoginForm.class, "LoginForm.html");
HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName());
HtmlDomUtil.find(doc, "login_form").setAttribute("action", self);
HtmlDomUtil.find(doc, "cancel_link").setAttribute("href", cancel);

View File

@@ -27,50 +27,58 @@
background: #fff url('data:image/gif;base64,R0lGODlhEAAQAMQAAO3t7eHh4srKyvz8/P5pDP9rENLS0v/28P/17tXV1dHEvPDw8M3Nzfn5+d3d3f5jA97Syvnv6MfLzcfHx/1mCPx4Kc/S1Pf189C+tP+xgv/k1N3OxfHy9NLV1/39/f///yH5BAAAAAAALAAAAAAQABAAAAVq4CeOZGme6KhlSDoexdO6H0IUR+otwUYRkMDCUwIYJhLFTyGZJACAwQcgEAQ4kVuEE2AIGAOPQQAQwXCfS8KQGAwMjIYIUSi03B7iJ+AcnmclHg4TAh0QDzIpCw4WGBUZeikDFzk0lpcjIQA7') no-repeat scroll 5px 50%
}
</style>
<style id="gerrit_sitecss" type="text/css"></style>
</head>
<body>
<h1>Sign In to Gerrit Code Review at <span id="hostName">example.com</span></h1>
<form method="POST" action="#" id="login_form">
<input type="hidden" name="link" id="f_link" value="1" />
<div id="logo_box"><div id="logo_img"></div></div>
<div id="error_message">Invalid OpenID identifier.</div>
<div>
<input type="text"
name="id"
id="f_openid"
size="60"
tabindex="1" />
</div>
<div>
<input name="rememberme" id="f_remember"
type="checkbox"
value="1"
tabindex="2" />
<label for="f_remember">Remember me</label>
</div>
<div style="margin-bottom: 25px;">
<input type="submit" value="Sign In" id="f_submit" tabindex="3" />
<a href="../" id="cancel_link">Cancel</a>
</div>
<div id="gerrit_topmenu" style="height:45px;" class="gerritTopMenu"></div>
<div id="gerrit_header"></div>
<div id="gerrit_body" class="gerritBody">
<h1>Sign In to Gerrit Code Review at <span id="hostName">example.com</span></h1>
<form method="POST" action="#" id="login_form">
<input type="hidden" name="link" id="f_link" value="1" />
<div id="logo_box"><div id="logo_img"></div></div>
<div id="error_message">Invalid OpenID identifier.</div>
<div>
<input type="text"
name="id"
id="f_openid"
size="60"
tabindex="1" />
</div>
<div>
<input name="rememberme" id="f_remember"
type="checkbox"
value="1"
tabindex="2" />
<label for="f_remember">Remember me</label>
</div>
<div style="margin-bottom: 25px;">
<input type="submit" value="Sign In" id="f_submit" tabindex="3" />
<a href="../" id="cancel_link">Cancel</a>
</div>
<div id="provider_google">
<img height="16" width="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsUlEQVR42rWSy0sbYRRH88fYhW5KQXThIkUQN6ISJFBBW6FFBN2UqlC78LVKFlHIoqWGakF8gUZ8gajVzKSKZpQmAWtK0pBmhkxmSgPuiuaYmRKhSEwU/ODyDZfzO8ydOxbLQx1NB0H4y8FBlmMpy9LyOavrKUoKB4IXvBsIIopRPJ441dY/2Fr8fAtFSxO0dkRy4eA1PDp+TkVlmGhMKS5IJOFpbYgj6fQalhV4UhVm0esvLojELimv1JiYOPwPflS2gM93WNoIXZ1nPC4/IRDQzYDTqTA0PM2dtuCZ8vGmV6SvP5srL/da5c+4TnNLFlvTMYmEVrpkazuM2x1naOQ7Dc1fqLOmaayXkaSz4hLHewm7fY+Z2R0iP+JmYGpym/q6DDb76u0CwR+g9+0FHz4u3wAdLh9VNWu5UX4VlqysfaXRDoODNz+aw7nDs9ZPpNPpwgJV1WhvF6m1/qb79T6fpwXm5kVGRnd5/mIdQSzwH2iahq7/27nxiq5xLy9fbdDWsUtPzyausRVC4VMMzmAMNv9syTdkWTZLVVUymYx5J5NJs5dKpVAUxaw8Z/SM7BV+fz9Uc4yYRAAAAABJRU5ErkJggg==" />
<a href="?id=https://www.google.com/accounts/o8/id" id="id_google">Sign in with a Google Account</a>
</div>
<div id="provider_yahoo">
<img height="16" width="16" src="data:image/gif;base64,R0lGODlhEAAQAPECAAAAAP8AAP///8zMzCH5BAEAAAMALAAAAAAQABAAAAIqnI+py30BY3AgAjCkfJDjiIAQlgUkNxqWkqrm0honKk7KhZOzw/f+fygAADs=" />
<a href="?id=https://me.yahoo.com" id="id_yahoo">Sign in with a Yahoo! ID</a>
</div>
<div id="provider_google">
<img height="16" width="16" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsUlEQVR42rWSy0sbYRRH88fYhW5KQXThIkUQN6ISJFBBW6FFBN2UqlC78LVKFlHIoqWGakF8gUZ8gajVzKSKZpQmAWtK0pBmhkxmSgPuiuaYmRKhSEwU/ODyDZfzO8ydOxbLQx1NB0H4y8FBlmMpy9LyOavrKUoKB4IXvBsIIopRPJ441dY/2Fr8fAtFSxO0dkRy4eA1PDp+TkVlmGhMKS5IJOFpbYgj6fQalhV4UhVm0esvLojELimv1JiYOPwPflS2gM93WNoIXZ1nPC4/IRDQzYDTqTA0PM2dtuCZ8vGmV6SvP5srL/da5c+4TnNLFlvTMYmEVrpkazuM2x1naOQ7Dc1fqLOmaayXkaSz4hLHewm7fY+Z2R0iP+JmYGpym/q6DDb76u0CwR+g9+0FHz4u3wAdLh9VNWu5UX4VlqysfaXRDoODNz+aw7nDs9ZPpNPpwgJV1WhvF6m1/qb79T6fpwXm5kVGRnd5/mIdQSzwH2iahq7/27nxiq5xLy9fbdDWsUtPzyausRVC4VMMzmAMNv9syTdkWTZLVVUymYx5J5NJs5dKpVAUxaw8Z/SM7BV+fz9Uc4yYRAAAAABJRU5ErkJggg==" />
<a href="?id=https://www.google.com/accounts/o8/id" id="id_google">Sign in with a Google Account</a>
</div>
<div id="provider_yahoo">
<img height="16" width="16" src="data:image/gif;base64,R0lGODlhEAAQAPECAAAAAP8AAP///8zMzCH5BAEAAAMALAAAAAAQABAAAAIqnI+py30BY3AgAjCkfJDjiIAQlgUkNxqWkqrm0honKk7KhZOzw/f+fygAADs=" />
<a href="?id=https://me.yahoo.com" id="id_yahoo">Sign in with a Yahoo! ID</a>
</div>
<div style="margin-top: 25px;">
<h2>What is OpenID?</h2>
<p>OpenID provides secure single-sign-on, without revealing your passwords to this website.</p>
<p>There are many OpenID providers available. You may already be member of one!</p>
<p><a href="http://openid.net/get/" target="_blank">Get OpenID</a></p>
</div>
</form>
<div style="margin-top: 25px;">
<h2>What is OpenID?</h2>
<p>OpenID provides secure single-sign-on, without revealing your passwords to this website.</p>
<p>There are many OpenID providers available. You may already be member of one!</p>
<p><a href="http://openid.net/get/" target="_blank">Get OpenID</a></p>
</div>
</form>
</div>
<div style="clear: both; margin-top: 15px; padding-top: 2px; margin-bottom: 15px;">
<div id="gerrit_footer"></div>
</div>
<script type="text/javascript" language="javascript">
<script type="text/javascript">
var f_openid = document.getElementById('f_openid');
var f_submit = document.getElementById('f_submit');
if (f_openid.value == '')