File Coverage

lib/Dancer/Plugin/Auth/Google.pm
Criterion Covered Total %
statement 56 56 100.0
branch 4 6 66.6
condition 4 8 50.0
subroutine 15 15 100.0
pod n/a
total 79 85 92.9


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Auth::Google;
2 2     2   2078 use strict;
  2         4  
  2         83  
3 2     2   12 use warnings;
  2         2  
  2         111  
4              
5             our $VERSION = 0.05;
6              
7 2     2   19 use Dancer ':syntax';
  2         1  
  2         13  
8 2     2   1765 use Dancer::Plugin;
  2         2518  
  2         124  
9 2     2   10 use Carp ();
  2         3  
  2         23  
10 2     2   6 use Scalar::Util;
  2         3  
  2         64  
11 2     2   8 use Try::Tiny;
  2         3  
  2         76  
12              
13 2     2   1067 use Furl;
  2         39394  
  2         92  
14 2     2   1766 use IO::Socket::SSL;
  2         145220  
  2         20  
15 2     2   433 use URI;
  2         4  
  2         1165  
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 $furl;
25              
26             register 'auth_google_init' => sub {
27 2     2   1290 my $config = plugin_setting;
28 2         43 $client_id = $config->{client_id};
29 2         4 $client_secret = $config->{client_secret};
30 2         6 $callback_url = $config->{callback_url};
31              
32 2   50     11 $scope = $config->{scope} || 'profile';
33 2   50     7 $callback_success = $config->{callback_success} || '/';
34 2   50     11 $callback_fail = $config->{callback_fail} || '/fail';
35 2   50     12 $access_type = $config->{access_type} || 'online';
36              
37 2         5 foreach my $param ( qw(client_id client_secret callback_url) ) {
38 6 50       16 Carp::croak "'$param' is expected but not found in configuration"
39             unless $config->{$param};
40             }
41              
42 2         15 debug "new google with $client_id, $client_secret, $callback_url";
43 2         117 $furl = Furl->new(
44             agent => "Dancer-Plugin-Auth-Google/$VERSION",
45             timeout => 5,
46             ssl_opts => {
47             SSL_verify_mode => SSL_VERIFY_NONE(),
48             },
49             );
50              
51 2         113 return 1;
52             };
53              
54             register 'auth_google_authenticate_url' => sub {
55 1 50   1   4 Carp::croak 'auth_google_init() must be called first'
56             unless defined $callback_url;
57              
58 1         6 my $uri = URI->new('https://accounts.google.com/o/oauth2/auth');
59 1         5825 $uri->query_form(
60             response_type => 'code',
61             client_id => $client_id,
62             redirect_uri => $callback_url,
63             scope => $scope,
64             access_type => $access_type,
65             );
66              
67 1         242 debug "google auth uri: $uri";
68 1         54 return $uri;
69             };
70              
71             get '/auth/google/callback' => sub {
72             debug 'in google auth callback';
73              
74             return redirect $callback_fail if params->{'error'};
75              
76             my $code = params->{'code'};
77             return redirect $callback_fail unless $code;
78              
79             my $res = $furl->post(
80             'https://accounts.google.com/o/oauth2/token',
81             [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
82             {
83             code => $code,
84             client_id => $client_id,
85             client_secret => $client_secret,
86             redirect_uri => $callback_url,
87             grant_type => 'authorization_code',
88             }
89             );
90              
91             my ($data, $error) = _parse_response( $res->decoded_content );
92             return send_error($error) if $error;
93              
94             return send_error 'google auth: no access token present'
95             unless $data->{access_token};
96              
97             $res = $furl->get(
98             'https://www.googleapis.com/plus/v1/people/me',
99             [ 'Authorization' => 'Bearer ' . $data->{access_token} ],
100             );
101              
102             my $user;
103             ($user, $error) = _parse_response( $res->decoded_content );
104             return send_error($error) if $error;
105              
106             # we need to stringify our JSON::Bool data as some
107             # session backends might have trouble storing objects.
108             # we should be able to safely remove this once
109             # https://github.com/PerlDancer/Dancer-Session-Cookie/pull/1
110             # (or a similar solution) is merged.
111             if (exists $user->{image} and exists $user->{image}{isDefault}) {
112             $user->{image}{isDefault} = "$user->{image}{isDefault}";
113             }
114             if (exists $user->{isPlusUser}) {
115             $user->{isPlusUser} = "$user->{isPlusUser}";
116             }
117             if (exists $user->{verified}) {
118             $user->{verified} = "$user->{verified}";
119             }
120              
121             session 'google_user' => { %$data, %$user };
122             redirect $callback_success;
123             };
124              
125             sub _parse_response {
126 4     4   3974 my ($response) = @_;
127 4         10 my ($data, $error);
128              
129             try {
130 4     4   144 $data = from_json($response);
131             } catch {
132 4 100   4   3794 if ($response =~ /timeout/) {
133 1         6 $error = "google auth: timeout ($response)";
134             }
135             else {
136 3         13 $error = "google auth: error parsing JSON ($_)";
137             }
138 4         43 };
139 4         114 return ($data, $error);
140             }
141              
142             register_plugin;
143             __END__