--- /dev/null
+package stemmaweb::Authentication::Credential::Google;
+
+use Crypt::OpenSSL::X509;
+use JSON::WebToken;
+use IO::All;
+use JSON::MaybeXS;
+use MIME::Base64;
+use LWP::Simple qw(get);
+use Date::Parse qw(str2time);
+
+use warnings;
+use strict;
+use strictures 1;
+
+=head1 NAME
+
+stemmaweb::Authentication::Google - JSON Web Token handler for Google tokens.
+
+=head1 DESCRIPTION
+
+Retrieves Google's public certificates, and then retrieves the key from the
+cert using L<Crypt::OpenSSL::X509>. Finally, uses the pubkey to decrypt a
+Google token using L<JSON::WebToken>.
+
+=cut
+
+sub new {
+ my ($class, $config, $app, $realm) = @_;
+ $class = ref $class || $class;
+
+ my $self = {
+ _config => $config,
+ _app => $app,
+ _realm => $realm,
+ };
+
+ bless $self, $class;
+}
+
+sub authenticate {
+ my ($self, $c, $realm, $authinfo) =@_;
+
+ my $id_token = $authinfo->{id_token};
+ $id_token ||= $c->req->method eq 'GET' ?
+ $c->req->query_params->{id_token} : $c->req->body_params->{id_token};
+
+ use Data::Dumper;
+ $c->log->debug(Dumper $authinfo);
+
+ if (!$id_token) {
+ Catalyst::Exception->throw("id_token not specified.");
+ }
+
+ my $userinfo = $self->decode($id_token);
+
+ use Data::Dumper;
+ $c->log->debug(Dumper $userinfo);
+
+ my $sub = $userinfo->{sub};
+ my $openid = $userinfo->{openid_id};
+
+ $c->log->debug($sub);
+ $c->log->debug($openid);
+
+ if (!$sub || !$openid) {
+ Catalyst::Exception->throw(
+ 'Could not retrieve sub and openid from token! Is the token
+ correct?'
+ );
+ }
+
+ # Do we have a user with the google id already?
+ my $user = $realm->find_user({
+ id => $sub
+ });
+
+ if ($user) {
+ return $user;
+ }
+
+ # Do we have a user with the openid?
+
+ $user = $realm->find_user({
+ url => $openid
+ });
+
+ if (!$user) {
+ throw ("Could not find a user with that openid or sub!");
+ }
+
+ my $new_user = $realm->add_user({
+ username => $sub,
+ password => $user->password,
+ role => $user->role,
+ });
+
+ foreach my $t (@{ $user->traditions }) {
+ $new_user->add_tradition($t);
+ }
+
+ warn ($new_user->id);
+
+ warn (scalar @{$user->traditions});
+ warn (scalar @{$new_user->traditions});
+
+ use Data::Dumper;
+ warn (Dumper($user->id));
+
+ $realm->delete_user({ username => $user->id });
+
+ return $new_user;
+}
+
+=head1 METHODS
+
+=head2 retrieve_certs
+
+Retrieves a pair of JSON-encoded certificates from the given URL (defaults to
+Google's public cert url), and returns the decoded JSON object.
+
+=head3 ARGUMENTS
+
+=over
+
+=item url
+
+Optional. Location where certificates are located.
+Defaults to https://www.googleapis.com/oauth2/v1/certs.
+
+=back
+
+=head3 RETURNS
+
+Decoded JSON object containing certificates.
+
+=cut
+
+sub retrieve_certs {
+ my ($self, $url) = @_;
+
+ $url ||= 'https://www.googleapis.com/oauth2/v1/certs';
+ return decode_json(get($url));
+}
+
+=head2 get_key_from_cert
+
+Given a pair of certificates $certs (defaults to L</retrieve_certs>),
+this function returns the public key of the cert identified by $kid.
+
+=head3 ARGUMENTS
+
+=over
+
+=item $kid
+
+Required. Index of the certificate hash $hash where the cert we want is
+located.
+
+=item $certs
+
+Optional. A (hashref) pair of certificates.
+It's retrieved using L</retrieve_certs> if not given,
+or if the pair is expired.
+
+=back
+
+=head3 RETURNS
+
+Public key of certificate.
+
+=cut
+
+sub get_key_from_cert {
+ my ($self, $kid, $certs) = @_;
+
+ $certs ||= $self->retrieve_certs;
+ my $cert = $certs->{$kid};
+ my $x509 = Crypt::OpenSSL::X509->new_from_string($cert);
+
+ if ($self->is_cert_expired($x509)) {
+ # If we ended up here, we were given
+ # an old $certs string from the user.
+ # Let's force getting another.
+ return $self->get_key_from_cert($kid);
+ }
+
+ return $x509->pubkey;
+}
+
+=head2 is_cert_expired
+
+Returns if a given L<Crypt::OpenSSL::X509> certificate is expired.
+
+=cut
+
+sub is_cert_expired {
+ my ($self, $x509) = @_;
+
+ my $expiry = str2time($x509->notAfter);
+
+ return time > $expiry;
+}
+
+=head2 decode
+
+Returns the decoded information contained in a user's token.
+
+=head3 ARGUMENTS
+
+=over
+
+=item $token
+
+Required. The user's token from Google+.
+
+=item $pubkey
+
+Optional. A public key string with which to decode the token.
+If not given, the public key will be retrieved from $certs.
+
+=item $certs
+
+Optional. A pair of public key certs retrieved from Google.
+If not given, or if the certificates have expired, a new
+pair of certificates is retrieved.
+
+=back
+
+=head2 RETURNS
+
+Decoded JSON object from the decrypted token.
+
+=cut
+
+sub decode {
+ my ($self, $token, $certs, $pubkey) = @_;
+
+ if (!$pubkey) {
+ my $details = decode_json(
+ MIME::Base64::decode_base64(
+ substr( $token, 0, CORE::index($token, '.') )
+ )
+ );
+
+ my $kid = $details->{kid};
+ $pubkey = $self->get_key_from_cert($kid, $certs);
+ }
+
+ return JSON::WebToken->decode($token, $pubkey);
+}
+
+=head1 AUTHOR
+
+Errietta Kostala <e.kostala@shadowcat.co.uk>
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
}, 2000 );
}
});
+
+ function googleSignIn(authResult) {
+ if (authResult['status']['signed_in']) {
+ document.getElementById('signinButton').setAttribute('style', 'display:none');
+ gapi.client.load('plus', 'v1', function apiClientLoaded() {
+ gapi.client.plus.people.get({ userId: 'me'}).execute(function infoRetrieved(resp) {
+ var primaryEmail;
+ for (var i = 0; i < resp.emails.length; i++) {
+ if (resp.emails[i].type === 'account') {
+ primaryEmail = resp.emails[i].value;
+ }
+ }
+
+ dataRetrieved(authResult, primaryEmail);
+ });
+ });
+ } else {
+ console.log("Error", authResult);
+ }
+ }
+
+ function dataRetrieved(login, email) {
+ console.log(email);
+ console.log(login.id_token);
+
+ document.getElementById('email').value = email;
+ document.getElementById('id_token').value = login.id_token;
+ document.getElementById('google_form').submit();
+ }
+
</script>
+ <script src="https://apis.google.com/js/client:platform.js" async defer></script>
[% END %]
<div id="topbanner">
<h1>Stemmaweb - Sign in</h1>
<h3><a href="#">Sign in with Google</a></h3>
<div>
<p>If you have a Google account, you may use it to sign into Stemmaweb.</p>
- <form class="openid_form" method="post" action="[% c.uri_for_action('/users/login') | html %]" autocomplete="off">
- <input type="hidden" name="realm" value="openid"/>
- <input type="hidden" name="openid_identifier" value="https://www.google.com/accounts/o8/id"/>
- <input type="submit" class="login_button" id="login_google" value="Sign in with Google"></input>
- </form>
- </div>
-
- <h3><a href="#">Sign in with OpenID</a></h3>
- <div>
- <p>If you have an account with an <a href="http://openid.net/get-an-openid/" target="_blank">OpenID provider</a> (e.g. WordPress, Blogger, Flickr, Yahoo), you may use it to sign into Stemmaweb.
- <form class="openid_form" method="post" action="[% c.uri_for_action('/users/login') | html %]" autocomplete="off">
- <input type="hidden" name="realm" value="openid"/>
- <input type="text" name="openid_identifier" id="openid_input"/>
- <input type="submit" class="login_button" id="login_openid" value="Sign in with OpenID"/>
+ <span id="signinButton">
+ <span
+ class="g-signin"
+ data-callback="googleSignIn"
+ data-clientid="577442226093-pi2ud795g49ibip78bgfoabhl4kdrguc.apps.googleusercontent.com"
+ data-cookiepolicy="single_host_origin"
+ data-requestvisibleactions="http://schema.org/AddAction"
+ data-scope="https://www.googleapis.com/auth/plus.profile.emails.read"
+ data-openidrealm="http://sherlock.scsys.co.uk:3000/"
+ >
+ </span>
+ </span>
+ <form id="google_form" action="[% c.uri_for_action('/users/login') | html %]" method="post">
+ <input id='email' name='email' value='' type='hidden' />
+ <input id='id_token' name='id_token' value='' type='hidden' />
</form>
</div>
[% END %]
</div>
[% END %]
-[% PROCESS footer.tt %]
\ No newline at end of file
+[% PROCESS footer.tt %]