File Coverage

lib/Dancer/Plugin/Auth/Google.pm
Criterion Covered Total %
statement 57 59 96.6
branch 4 8 50.0
condition 6 13 46.1
subroutine 15 16 93.7
pod n/a
total 82 96 85.4


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Auth::Google;
2 2     2   3129 use strict;
  2         6  
  2         58  
3 2     2   10 use warnings;
  2         5  
  2         83  
4              
5             our $VERSION = 0.07;
6              
7 2     2   12 use Dancer ':syntax';
  2         3  
  2         15  
8 2     2   1835 use Dancer::Plugin;
  2         3074  
  2         186  
9 2     2   18 use Carp ();
  2         4  
  2         34  
10 2     2   12 use Scalar::Util;
  2         4  
  2         73  
11 2     2   12 use Try::Tiny;
  2         4  
  2         97  
12              
13 2     2   950 use Furl;
  2         45116  
  2         69  
14 2     2   1604 use IO::Socket::SSL;
  2         138173  
  2         20  
15 2     2   387 use URI;
  2         5  
  2         1983  
16              
17             my $client_id;
18             my $client_secret;
19             my $scope;
20             my $access_type;
21             my $callback_url;
22             my $callback_success;
23             my $callback_fail;
24             my $legacy_gplus;
25             my $furl;
26              
27             register 'auth_google_init' => sub {
28 2     2   1454 my $config = plugin_setting;
29 2         64 $client_id = $config->{client_id};
30 2         7 $client_secret = $config->{client_secret};
31 2         6 $callback_url = $config->{callback_url};
32              
33 2   50     10 $scope = $config->{scope} || 'profile';
34 2   50     8 $callback_success = $config->{callback_success} || '/';
35 2   50     8 $callback_fail = $config->{callback_fail} || '/fail';
36 2   100     18 $access_type = $config->{access_type} || 'online';
37 2   50     12 $legacy_gplus = $config->{legacy_gplus} || 0;
38              
39 2         6 foreach my $param ( qw(client_id client_secret callback_url) ) {
40             Carp::croak "'$param' is expected but not found in configuration"
41 6 50       26 unless $config->{$param};
42             }
43              
44 2         24 debug "new google with $client_id, $client_secret, $callback_url";
45 2         159 $furl = Furl->new(
46             agent => "Dancer-Plugin-Auth-Google/$VERSION",
47             timeout => 5,
48             ssl_opts => {
49             SSL_verify_mode => SSL_VERIFY_NONE(),
50             },
51             );
52              
53 2         132 return 1;
54             };
55              
56             register 'auth_google_authenticate_url' => sub {
57 1 50   1   5 Carp::croak 'auth_google_init() must be called first'
58             unless defined $callback_url;
59              
60 1         8 my $uri = URI->new('https://accounts.google.com/o/oauth2/v2/auth');
61 1         8306 $uri->query_form(
62             client_id => $client_id,
63             redirect_uri => $callback_url,
64             scope => $scope,
65             access_type => $access_type,
66             response_type => 'code',
67             );
68              
69 1         305 debug "google auth uri: $uri";
70 1         65 return $uri;
71             };
72              
73             get '/auth/google/callback' => sub {
74             debug 'in google auth callback';
75              
76             return redirect $callback_fail if params->{'error'};
77              
78             my $code = params->{'code'};
79             return redirect $callback_fail unless $code;
80              
81             my $res = $furl->post(
82             'https://www.googleapis.com/oauth2/v4/token',
83             [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
84             {
85             code => $code,
86             client_id => $client_id,
87             client_secret => $client_secret,
88             redirect_uri => $callback_url,
89             grant_type => 'authorization_code',
90             }
91             );
92              
93             my ($data, $error) = _parse_response( $res->decoded_content );
94             if (ref $data && !$error) {
95             # Google tells us to ignore any unrecognized fields
96             # included in the response (like their "id_token").
97             $data = {
98             access_token => $data->{access_token},
99             expires_in => $data->{expires_in},
100             token_type => $data->{token_type},
101             refresh_token => $data->{refresh_token},
102             };
103             }
104             else {
105             return send_error('google auth: ' . (defined $error ? $error : 'unknown error'));
106             }
107              
108             $res = $furl->get(
109             'https://www.googleapis.com/oauth2/v2/userinfo',
110             [ 'Authorization' => 'Bearer ' . $data->{access_token} ],
111             );
112              
113             my $user;
114             ($user, $error) = _parse_response( $res->decoded_content );
115             return send_error("google auth: $error") if $error;
116              
117             if (exists $user->{verified_email}) {
118             # we stringify our JSON::Bool data as some session
119             # backends might have trouble storing objects.
120             $user->{verified_email} = "$user->{verified_email}";
121             }
122             $user = _convert_to_legacy_gplus_format($user) if $legacy_gplus;
123              
124             session 'google_user' => { %$data, %$user };
125             redirect $callback_success;
126             };
127              
128             sub _convert_to_legacy_gplus_format {
129 0     0   0 my ($user) = @_;
130              
131             return {
132             kind => "plus#person",
133             displayName => $user->{name},
134             name => {
135             givenName => $user->{given_name},
136             familyName => $user->{family_name},
137             },
138             language => $user->{locale},
139             isPlusUser => ($user->{link} && index($user->{link},'http') == 0 ? 1 : 0),
140             url => $user->{link},
141             gender => $user->{gender},
142             image => {
143             url => $user->{picture},
144             isDefault => 0,
145             },
146             domain => $user->{hd},
147             emails => [ { type => "account", value => $user->{email} } ],
148             etag => undef,
149             verified => $user->{verified_email},
150             circledByCount => undef,
151             id => $user->{id},
152 0 0 0     0 objectType => "person",
153             };
154             }
155              
156             sub _parse_response {
157 4     4   2873 my ($response) = @_;
158 4         8 my ($data, $error);
159              
160             try {
161 4     4   199 $data = from_json($response);
162             } catch {
163 2 100   2   414 if ($response =~ /timeout/) {
164 1         5 $error = "google auth: timeout ($response)";
165             }
166             else {
167 1         6 $error = "google auth: error parsing JSON ($_)";
168             }
169 4         29 };
170 4         23311 return ($data, $error);
171             }
172              
173             register_plugin;
174             __END__