Add support for logging in via a Google account
[?]
Jan 13, 2016, 4:32 PM
3QWDDLBR5DGFK5Y3TDMK55R2SCHRHFVO2KW2BMZGIYRTIQEZC45ACDependencies
- [2]
GS4SFHCPtemplates: Use uri_for to reference static paths. - [3]
Z4NL5TWBFix audience URL - [4]
IFY7BYPSUser.pm: Handle params from JSON properly - [5]
HL6ZYWHFAllow configuring a set of domains to allow logins from Persona. - [6]
2DHE2ZAKAllow Hydra to run as a private instance by requiring a login. - [7]
XOOSZ3WSOnly include Persona JS when Persona is enabled - [8]
36ZTCZ4FAdd basic Persona support - [9]
LZVO64YGMerge in the first bits of the API work - [10]
XMB4MTRLShow sign in as success - [11]
2CZSW5S5Don't redirect to /login if authentication is required - [12]
P75LFRF4Slight cleanup in the Persona sign in code - [13]
JFW656FTAdd a flag to enable Persona support - [14]
MQMF2LBWRe-enable adding new users via the web interface - [15]
QVIQAYZTBe paranoid about the Persona email address - [16]
G4X5IUYJRemove default logo, replaced by text for now. Hide template in jobset edit. - [17]
XJRJ4J7MAdd user registration - [18]
MGOGOKQPadd tracker html code via HYDRA_TRACKER - [19]
OEPUOUNBUsing twitter bootstrap for more consistent looks for Hydra - [20]
DV43UILUDon't float the search bar to the right in collapsed mode - [21]
WDKFN4B2Make sign in a modal dialog box rather than a separate page - [22]
YM27DTVTRemove superfluous HYDRA_LOGO environment variable - [23]
J5UVLXOK* Start of a basic Catalyst web interface. - [24]
D44B24QCStore the account type ("hydra" or "persona") explicitly in the database - [25]
2G63HKCHFix some wellformedness issues - [26]
X5BWNTMAMake the logo configurable via hydra.conf - [27]
PFB5ZUQWFix legacy login - [28]
VVRM3EGCLink to both the Persona and legacy sign in - [29]
QL55ECJ6- adapted ui for hydra, more in line with nixos.org website - [30]
JARRBLZDBootstrapify the Hydra forms (except the project and jobset edit pages) - [31]
BW6TYQJSUse local copy of the Persona sign in button - [32]
R6APT7HGFix hydra_logo setting - [*]
T4LLYESZ* Nix expression for building Hydra. - [*]
6K5PBUUNUse buildEnv to combine Hydra's Perl dependencies - [*]
LSZLZHJYAllow users to edit their own settings - [*]
D5QIOJGP* Move everything up one directory. - [*]
4JPNFWRB* Use jquery for the logfile manipulation. - [*]
SZYDW2DGhydra: added some user admin
Change contents
- edit in release.nix at line 120
CryptJWT - edit in src/lib/Hydra/Controller/Root.pm at line 16
- edit in src/lib/Hydra/Controller/Root.pm at line 22
$c->request->path eq "google-login" || - edit in src/lib/Hydra/Controller/Root.pm at line 40
$c->stash->{personaEnabled} = $c->config->{enable_persona} // "0" eq "1"; - edit in src/lib/Hydra/Controller/Root.pm at line 73
- edit in src/lib/Hydra/Controller/User.pm at line 7
use Crypt::JWT qw(decode_jwt); - edit in src/lib/Hydra/Controller/User.pm at line 47
sub persona_login :Path('/persona-login') Args(0) {my ($self, $c) = @_;requirePost($c);error($c, "Persona support is not enabled.") unless $c->stash->{personaEnabled}; - edit in src/lib/Hydra/Controller/User.pm at line 48[8.183]→[4.0:61](∅→∅),[4.61]→[8.240:392](∅→∅),[8.240]→[8.240:392](∅→∅),[8.392]→[3.0:39](∅→∅),[3.39]→[8.439:451](∅→∅),[8.439]→[8.439:451](∅→∅),[8.451]→[8.22:106](∅→∅)
my $assertion = $c->stash->{params}->{assertion} or die;my $ua = new LWP::UserAgent;my $response = $ua->post('https://verifier.login.persona.org/verify',{ assertion => $assertion,audience => $c->uri_for('/')});error($c, "Did not get a response from Persona.") unless $response->is_success; - replacement in src/lib/Hydra/Controller/User.pm at line 49
my $d = decode_json($response->decoded_content) or die;error($c, "Persona says: $d->{reason}") if $d->{status} ne "okay";sub doEmailLogin {my ($self, $c, $type, $email, $fullName) = @_; - replacement in src/lib/Hydra/Controller/User.pm at line 52
my $email = $d->{email} or die;die "No email address provided.\n" unless defined $email; - replacement in src/lib/Hydra/Controller/User.pm at line 56
die "Illegal email address." unless $email =~ /^[a-zA-Z0-9\.\-\_]+@[a-zA-Z0-9\.\-\_]+$/;die "Illegal email address.\n" unless $email =~ /^[a-zA-Z0-9\.\-\_]+@[a-zA-Z0-9\.\-\_]+$/; - replacement in src/lib/Hydra/Controller/User.pm at line 58
# If persona_allowed_domains is set, check if the email address returned is on these domains.# When not configured, allow all domains.# If persona_allowed_domains is set, check if the email address# returned is on these domains. When not configured, allow all# domains. - replacement in src/lib/Hydra/Controller/User.pm at line 62
if ( $allowed_domains ne "") {if ($allowed_domains ne "") { - replacement in src/lib/Hydra/Controller/User.pm at line 70
die "Email address is not allowed to login." unless $email_ok;error($c, "Your email address does not belong to a domain that is allowed to log in.\n")unless $email_ok; - replacement in src/lib/Hydra/Controller/User.pm at line 76
if (!$user) {if ($user) {# Automatically upgrade Persona accounts to Google accounts.if ($user->type eq "persona" && $type eq "google") {$user->update({type => "google"});}die "You cannot login via login type '$type'.\n" if $user->type ne $type;} else { - edit in src/lib/Hydra/Controller/User.pm at line 86
, fullname => $fullName, - replacement in src/lib/Hydra/Controller/User.pm at line 89
, type => "persona", type => $type - edit in src/lib/Hydra/Controller/User.pm at line 99
sub persona_login :Path('/persona-login') Args(0) {my ($self, $c) = @_;requirePost($c);error($c, "Logging in via Persona is not enabled.") unless $c->config->{enable_persona}; - edit in src/lib/Hydra/Controller/User.pm at line 107
my $assertion = $c->stash->{params}->{assertion} or die; - edit in src/lib/Hydra/Controller/User.pm at line 109
my $ua = new LWP::UserAgent;my $response = $ua->post('https://verifier.login.persona.org/verify',{ assertion => $assertion,audience => $c->uri_for('/')});error($c, "Did not get a response from Persona.") unless $response->is_success;my $d = decode_json($response->decoded_content) or die;error($c, "Persona says: $d->{reason}") if $d->{status} ne "okay";doEmailLogin($self, $c, "persona", $d->{email}, undef);}# From https://www.googleapis.com/oauth2/v3/certs. Should probably not# hard-code this.my $googleKeys = <<'EOF';{"keys": [{"kty": "RSA","alg": "RS256","use": "sig","kid": "10685afd5291883ce668345afd77201390406f82","n": "xeNopuszp35W6H1w2Tw4OrSwT8BZ9f7-2PoOyWZmfMmUDmYT2uxrZezDK0YLap5LVmpLNcpZP5Hj67_32NU3my4qfA-SlxuJMUxHWJF7Dqr-QNAqld0SZ_po4qz5ZTHDxNxoZ4iw_T-4lhIBGm0RIZprDDGPI7Vo8qIeIMjZywoh_nq32zB6tnjEUBvHcgay0qXEnQkKkavzHO_c5sLc1qXM0jDQVqyO1enevW2yA_8gP0Qb7014ycN5umCvEHc66c2_iNT-R4zgw8gd1g05n2xwyET8qb_3wi5LqUV-Cri4mJ2xwGY8uynlD2I4jVtOYJusBgNs6AfwyehzsLdwSQ","e": "AQAB"},{"kty": "RSA","alg": "RS256","use": "sig","kid": "5a68fc8a3ec0c30e0be95aa08db99a68a725467f","n": "zmXvUwXYSo8VouhnkURp-3xywch-jPrk7q0gugqC7QIchBPnvdXdS-bj6sr1AqDl_hEDtiLGfiVr3Ft_U022rtHAl5n5NxyybUtZXWyT5yQZM4jopGBajavEUdCl9b4pqb-q_3fVaxUXe7re23sVjI5Bntd-8RYZ70tq-ZvCWBqsnz6lHi9Ditp3CZGWLMMBZlIv3nKnClOrZXL98Jmt7AAod-Gtk65saqnrMwWtBcI_Q-3u23ytywbMLanCeFFNUWlIOgZqyYYkOm-ylLRJzVaZ1THtcWILWCYUgxXjyF9DtXO3a8nct2JhdacD3LzRiPv3sXr31cg4arwUk19JoQ","e": "AQAB"}]}EOFsub google_login :Path('/google-login') Args(0) {my ($self, $c) = @_;requirePost($c);error($c, "Logging in via Google is not enabled.") unless $c->config->{enable_google_login};my $data = decode_jwt(token => ($c->stash->{params}->{id_token} // die "No token."),kid_keys => $googleKeys,verify_exp => 1,);die unless $data->{aud} eq $c->config->{google_client_id};die unless $data->{iss} eq "accounts.google.com" || $data->{iss} eq "https://accounts.google.com";die "Email address is not verified" unless $data->{email_verified};# FIXME: verify hosted domain claim?doEmailLogin($self, $c, "google", $data->{email}, $data->{name} // undef);} - file addition: auth.tt[37.1486]
[% IF c.user_exists %]<script>function finishSignOut() {$.post("[% c.uri_for('/logout') %]").done(function(data) {window.location.reload();}).fail(function() { bootbox.alert("Server request failed!"); });}function signOut() {[% IF c.user.type == 'google' %]gapi.load('auth2', function() {gapi.auth2.init();var auth2 = gapi.auth2.getAuthInstance();auth2.then(function () {auth2.signOut().then(finishSignOut, finishSignOut);});});[% ELSE %]finishSignOut();[% END %]}</script>[% ELSE %]<div id="hydra-signin" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"><form class="form-horizontal"><div class="modal-body"><div class="control-group"><label class="control-label">User name</label><div class="controls"><input type="text" class="span3" name="username" value=""/></div></div><div class="control-group"><label class="control-label">Password</label><div class="controls"><input type="password" class="span3" name="password" value=""/></div></div></div><div class="modal-footer"><button id="do-signin" class="btn btn-primary">Sign in</button><button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button></div></form></div><script>function finishSignOut() { }$("#do-signin").click(function() {requestJSON({url: "[% c.uri_for('/login') %]",data: $(this).parents("form").serialize(),type: 'POST',success: function(data) {window.location.reload();}});return false;});</script>[% IF c.config.enable_google_login %]<script>function onGoogleSignIn(googleUser) {requestJSON({url: "[% c.uri_for('/google-login') %]",data: "id_token=" + googleUser.getAuthResponse().id_token,type: 'POST',success: function(data) {window.location.reload();}});return false;};</script>[% END %][% END %][% IF c.config.enable_persona %]<script src="https://login.persona.org/include.js"></script><script>navigator.id.watch({onlogin: function(assertion) {requestJSON({url: "[% c.uri_for('/persona-login') %]",data: "assertion=" + assertion,type: 'POST',success: function(data) { window.location.reload(); },postError: function() { navigator.id.logout(); }});}});$("#persona-signin").click(function() {navigator.id.request({ siteName: 'Hydra' });});</script>[% END %] - edit in src/root/layout.tt at line 38
[% IF c.config.enable_google_login %]<meta name="google-signin-client_id" content="[% c.config.google_client_id %]"><script src="https://apis.google.com/js/platform.js" async="1" defer="1"></script>[% END %] - replacement in src/root/layout.tt at line 102
You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>[% IF c.user.type == 'persona' %] via Persona[% END %].You are signed in as <tt>[% HTML.escape(c.user.username) %]</tt>[%- IF c.user.type == 'persona' %] via Persona[%- ELSIF c.user.type == 'google' %] via Google[% END %]. - edit in src/root/layout.tt at line 110[8.1323]→[8.1323:1337](∅→∅),[8.1337]→[8.218:246](∅→∅),[8.246]→[8.166:428](∅→∅),[8.428]→[8.449:471](∅→∅),[8.449]→[8.449:471](∅→∅),[8.471]→[8.7485:7486](∅→∅),[8.2168]→[8.7485:7486](∅→∅),[8.11729]→[8.7485:7486](∅→∅),[8.7486]→[8.642:695](∅→∅),[8.695]→[8.525:603](∅→∅),[8.525]→[8.525:603](∅→∅),[8.603]→[7.0:31](∅→∅),[7.31]→[8.618:685](∅→∅),[8.618]→[8.618:685](∅→∅),[8.685]→[8.2257:2258](∅→∅),[8.2257]→[8.2257:2258](∅→∅),[8.2258]→[8.686:868](∅→∅),[8.868]→[8.35:191](∅→∅),[8.191]→[8.429:561](∅→∅),[8.561]→[8.259:275](∅→∅),[8.259]→[8.259:275](∅→∅),[8.275]→[8.1243:1297](∅→∅),[8.1243]→[8.1243:1297](∅→∅),[8.1297]→[8.2360:2361](∅→∅),[8.2360]→[8.2360:2361](∅→∅),[8.2361]→[8.1298:1346](∅→∅),[8.1346]→[8.0:55](∅→∅),[8.55]→[8.1380:1518](∅→∅),[8.1380]→[8.1380:1518](∅→∅),[8.1518]→[8.422:1400](∅→∅)
<script>function doLogout() {[% IF c.user_exists %]$.post("[% c.uri_for('/logout') %]").done(function(data) {window.location.reload();}).fail(function() { bootbox.alert("Server request failed!"); });[% END %]}</script>[% IF c.user_exists && c.user.type == 'hydra' %]<script>$("#persona-signout").click(doLogout);</script>[% ELSIF personaEnabled %]<script src="https://login.persona.org/include.js"></script><script>navigator.id.watch({loggedInUser: [% c.user_exists ? '"' _ HTML.escape(c.user.username) _ '"' : "null" %],onlogin: function(assertion) {requestJSON({url: "[% c.uri_for('/persona-login') %]",data: "assertion=" + assertion,type: 'POST',success: function(data) { window.location.reload(); },postError: function() { navigator.id.logout(); }});},onlogout: doLogout});$("#persona-signin").click(function() {navigator.id.request({ siteName: 'Hydra' });});$("#persona-signout").click(function() {navigator.id.logout();});</script>[% END %][% IF !c.user_exists %]<div id="hydra-signin" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true"><form class="form-horizontal"><div class="modal-body"><div class="control-group"><label class="control-label">User name</label><div class="controls"><input type="text" class="span3" name="username" value=""/></div></div><div class="control-group"><label class="control-label">Password</label><div class="controls"><input type="password" class="span3" name="password" value=""/></div></div></div><div class="modal-footer"><button id="do-signin" class="btn btn-primary">Sign in</button><button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button></div></form></div> - replacement in src/root/layout.tt at line 111
<script>$("#do-signin").click(function() {requestJSON({url: "[% c.uri_for('/login') %]",data: $(this).parents("form").serialize(),type: 'POST',success: function(data) {window.location.reload();}});return false;});</script>[% END %][% PROCESS auth.tt %] - replacement in src/root/topbar.tt at line 130
<a href="#" id="persona-signout">Sign out</a><a href="#" onclick="signOut();">Sign out</a> - replacement in src/root/topbar.tt at line 133
[% IF personaEnabled %][% WRAPPER makeSubMenu title="Sign in" %][% WRAPPER makeSubMenu title="Sign in" %][% IF c.config.enable_google_login %]<li><a><div class="g-signin2" data-onsuccess="onGoogleSignIn" data-theme="dark"></div></a></li><li class="divider"></li>[% END %][% IF c.config.enable_persona %] - edit in src/root/topbar.tt at line 147
<li><a href="#hydra-signin" data-toggle="modal">Sign in with a Hydra account</a></li> - edit in src/root/topbar.tt at line 148
[% ELSE %] - replacement in src/root/topbar.tt at line 149
<a href="#hydra-signin" data-toggle="modal">Sign in</a><a href="#hydra-signin" data-toggle="modal">Sign in with a Hydra account</a> - replacement in src/root/user.tt at line 56
<input type="text" class="span3" name="emailaddress" [% IF !create && user.type == 'persona' %]disabled="disabled"[% END %] [%+ HTML.attributes(value => user.emailaddress) %]/><input type="text" class="span3" name="emailaddress" [% IF !create && user.username.search('@') %]disabled="disabled"[% END %] [%+ HTML.attributes(value => user.emailaddress) %]/>