add custom form
[scpubgit/stemmaweb.git] / lib / stemmaweb / Authentication / Credential / Google.pm
CommitLineData
85990daf 1package stemmaweb::Authentication::Credential::Google;
2
3use Crypt::OpenSSL::X509;
4use JSON::WebToken;
5use IO::All;
6use JSON::MaybeXS;
7use MIME::Base64;
8use LWP::Simple qw(get);
9use Date::Parse qw(str2time);
10
11use warnings;
12use strict;
13use strictures 1;
14
15=head1 NAME
16
17stemmaweb::Authentication::Google - JSON Web Token handler for Google tokens.
18
19=head1 DESCRIPTION
20
21Retrieves Google's public certificates, and then retrieves the key from the
22cert using L<Crypt::OpenSSL::X509>. Finally, uses the pubkey to decrypt a
23Google token using L<JSON::WebToken>.
24
25=cut
26
27sub new {
28 my ($class, $config, $app, $realm) = @_;
29 $class = ref $class || $class;
30
b1d9ab02 31 warn "MEEP\n\n";
32
85990daf 33 my $self = {
34 _config => $config,
35 _app => $app,
36 _realm => $realm,
37 };
38
39 bless $self, $class;
40}
41
42sub authenticate {
43 my ($self, $c, $realm, $authinfo) =@_;
44
45 my $id_token = $authinfo->{id_token};
46 $id_token ||= $c->req->method eq 'GET' ?
47 $c->req->query_params->{id_token} : $c->req->body_params->{id_token};
48
49 use Data::Dumper;
50 $c->log->debug(Dumper $authinfo);
51
52 if (!$id_token) {
53 Catalyst::Exception->throw("id_token not specified.");
54 }
55
56 my $userinfo = $self->decode($id_token);
57
58 use Data::Dumper;
59 $c->log->debug(Dumper $userinfo);
60
61 my $sub = $userinfo->{sub};
62 my $openid = $userinfo->{openid_id};
63
64 $c->log->debug($sub);
65 $c->log->debug($openid);
66
67 if (!$sub || !$openid) {
68 Catalyst::Exception->throw(
69 'Could not retrieve sub and openid from token! Is the token
70 correct?'
71 );
72 }
73
74 # Do we have a user with the google id already?
75 my $user = $realm->find_user({
76 id => $sub
77 });
78
79 if ($user) {
80 return $user;
81 }
82
83 # Do we have a user with the openid?
84
85 $user = $realm->find_user({
86 url => $openid
87 });
88
89 if (!$user) {
90 throw ("Could not find a user with that openid or sub!");
91 }
92
93 my $new_user = $realm->add_user({
94 username => $sub,
95 password => $user->password,
96 role => $user->role,
b1d9ab02 97 active => $user->active,
85990daf 98 });
99
100 foreach my $t (@{ $user->traditions }) {
101 $new_user->add_tradition($t);
102 }
103
104 warn ($new_user->id);
105
106 warn (scalar @{$user->traditions});
107 warn (scalar @{$new_user->traditions});
108
109 use Data::Dumper;
110 warn (Dumper($user->id));
111
112 $realm->delete_user({ username => $user->id });
113
b1d9ab02 114
85990daf 115 return $new_user;
116}
117
118=head1 METHODS
119
120=head2 retrieve_certs
121
122Retrieves a pair of JSON-encoded certificates from the given URL (defaults to
123Google's public cert url), and returns the decoded JSON object.
124
125=head3 ARGUMENTS
126
127=over
128
129=item url
130
131Optional. Location where certificates are located.
132Defaults to https://www.googleapis.com/oauth2/v1/certs.
133
134=back
135
136=head3 RETURNS
137
138Decoded JSON object containing certificates.
139
140=cut
141
142sub retrieve_certs {
143 my ($self, $url) = @_;
144
145 $url ||= 'https://www.googleapis.com/oauth2/v1/certs';
146 return decode_json(get($url));
147}
148
149=head2 get_key_from_cert
150
151Given a pair of certificates $certs (defaults to L</retrieve_certs>),
152this function returns the public key of the cert identified by $kid.
153
154=head3 ARGUMENTS
155
156=over
157
158=item $kid
159
160Required. Index of the certificate hash $hash where the cert we want is
161located.
162
163=item $certs
164
165Optional. A (hashref) pair of certificates.
166It's retrieved using L</retrieve_certs> if not given,
167or if the pair is expired.
168
169=back
170
171=head3 RETURNS
172
173Public key of certificate.
174
175=cut
176
177sub get_key_from_cert {
178 my ($self, $kid, $certs) = @_;
179
180 $certs ||= $self->retrieve_certs;
181 my $cert = $certs->{$kid};
182 my $x509 = Crypt::OpenSSL::X509->new_from_string($cert);
183
184 if ($self->is_cert_expired($x509)) {
185 # If we ended up here, we were given
186 # an old $certs string from the user.
187 # Let's force getting another.
188 return $self->get_key_from_cert($kid);
189 }
190
191 return $x509->pubkey;
192}
193
194=head2 is_cert_expired
195
196Returns if a given L<Crypt::OpenSSL::X509> certificate is expired.
197
198=cut
199
200sub is_cert_expired {
201 my ($self, $x509) = @_;
202
203 my $expiry = str2time($x509->notAfter);
204
205 return time > $expiry;
206}
207
208=head2 decode
209
210Returns the decoded information contained in a user's token.
211
212=head3 ARGUMENTS
213
214=over
215
216=item $token
217
218Required. The user's token from Google+.
219
220=item $pubkey
221
222Optional. A public key string with which to decode the token.
223If not given, the public key will be retrieved from $certs.
224
225=item $certs
226
227Optional. A pair of public key certs retrieved from Google.
228If not given, or if the certificates have expired, a new
229pair of certificates is retrieved.
230
231=back
232
233=head2 RETURNS
234
235Decoded JSON object from the decrypted token.
236
237=cut
238
239sub decode {
240 my ($self, $token, $certs, $pubkey) = @_;
241
242 if (!$pubkey) {
243 my $details = decode_json(
244 MIME::Base64::decode_base64(
245 substr( $token, 0, CORE::index($token, '.') )
246 )
247 );
248
249 my $kid = $details->{kid};
250 $pubkey = $self->get_key_from_cert($kid, $certs);
251 }
252
253 return JSON::WebToken->decode($token, $pubkey);
254}
255
256=head1 AUTHOR
257
258Errietta Kostala <e.kostala@shadowcat.co.uk>
259
260=head1 LICENSE
261
262This library is free software. You can redistribute it and/or modify
263it under the same terms as Perl itself.
264
265=cut
266
2671;