File Coverage

blib/lib/Dancer2/Plugin/OAuth2/Server.pm
Criterion Covered Total %
statement 105 118 88.9
branch 31 40 77.5
condition 28 42 66.6
subroutine 14 14 100.0
pod 0 1 0.0
total 178 215 82.7


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::OAuth2::Server;
2              
3 5     5   1951985 use strict;
  5         15  
  5         142  
4 5     5   27 use warnings;
  5         9  
  5         129  
5 5     5   153 use 5.008_005;
  5         18  
6             our $VERSION = '0.10';
7 5     5   4012 use Dancer2::Plugin;
  5         142298  
  5         35  
8 5     5   18404 use URI;
  5         8401  
  5         230  
9 5     5   2221 use URI::QueryParam;
  5         1829  
  5         158  
10 5     5   30 use Class::Load qw(try_load_class);
  5         8  
  5         284  
11 5     5   25 use Carp;
  5         11  
  5         7702  
12              
13             sub get_server_class {
14 31     31 0 64 my ($settings) = @_;
15 31   50     171 my $server_class = $settings->{server_class}//"Dancer2::Plugin::OAuth2::Server::Simple";
16 31         134 my ($ok, $error) = try_load_class($server_class);
17 31 50       2289 if (! $ok) {
18 0         0 confess "Cannot load server class $server_class: $error";
19             }
20              
21 31         151 return $server_class->new();
22             }
23              
24             on_plugin_import {
25             my $dsl = shift;
26             my $settings = plugin_setting;
27             my $authorization_route = $settings->{authorize_route}//'/oauth/authorize';
28             my $access_token_route = $settings->{access_token_route}//'/oauth/access_token';
29              
30             $dsl->app->add_route(
31             method => 'get',
32             regexp => $authorization_route,
33             code => sub { _authorization_request( $dsl, $settings ) }
34             );
35             $dsl->app->add_route(
36             method => 'post',
37             regexp => $access_token_route,
38             code => sub { _access_token_request( $dsl, $settings ) }
39             );
40             };
41              
42             register 'oauth_scopes' => sub {
43 2     2   33 my ($dsl, $scopes, $code_ref) = @_;
44              
45 2         9 my $settings = plugin_setting;
46              
47 2 50       196 $scopes = [$scopes] unless ref $scopes eq 'ARRAY';
48              
49             return sub {
50 8     8   52983 my $server = get_server_class( $settings );
51 8         100 my @res = _verify_access_token_and_scope( $dsl, $settings, $server,0, @$scopes );
52 8 100       31 if( not $res[0] ) {
53 3         16 $dsl->status( 400 );
54 3         355 return $dsl->to_json( { error => $res[1] } );
55             } else {
56 5         36 $dsl->app->request->var( oauth_access_token => $res[0] );
57 5         64 goto $code_ref;
58             }
59             }
60 2         17 };
61              
62             sub _authorization_request {
63 15     15   41 my ($dsl, $settings) = @_;
64             my ( $c_id,$url,$type,$scope,$state )
65 15   100     37 = map { $dsl->param( $_ ) // undef }
  75         1119  
66             qw/ client_id redirect_uri response_type scope state /;
67              
68 15         244 my $server = get_server_class( $settings );
69              
70 15 100       269 my @scopes = $scope ? split( / /,$scope ) : ();
71              
72 15 100 66     144 if (
      66        
73             ! defined( $c_id )
74             or ! defined( $type )
75             or $type ne 'code'
76             ) {
77 4         161 $dsl->status( 400 );
78 4         3762 return $dsl->to_json(
79             {
80             error => 'invalid_request',
81             error_description => 'the request was missing one of: client_id, '
82             . 'response_type;'
83             . 'or response_type did not equal "code"',
84             error_uri => '',
85             }
86             );
87             }
88              
89 11   100     63 my $state_required = $settings->{state_required} // 0;
90 11 100 100     49 if(
      66        
91             $state_required
92             and ! defined $state
93             and ! length $state
94             ) {
95 1         6 $dsl->status( 400 );
96 1         140 return $dsl->to_json(
97             {
98             error => 'invalid_request',
99             error_description => 'the request was missing : state ',
100             error_uri => '',
101             }
102             );
103             }
104              
105 10         60 my $uri = URI->new( $url );
106 10         755 my ( $res,$error ) = $server->verify_client($dsl, $settings, $c_id, \@scopes, $url );
107              
108 10 100       39 if ( $res ) {
109 6 50       22 if ( ! $server->login_resource_owner( $dsl, $settings ) ) {
110 0         0 $dsl->debug( "OAuth2::Server: Resource owner not logged in" );
111             # call to $resource_owner_logged_in method should have called redirect_to
112 0         0 return;
113             } else {
114 6         22 $dsl->debug( "OAuth2::Server: Resource owner is logged in" );
115 6         513 $res = $server->confirm_by_resource_owner($dsl, $settings, $c_id, \@scopes );
116 6 50       33 if ( ! defined $res ) {
    50          
117 0         0 $dsl->debug( "OAuth2::Server: Resource owner to confirm scopes" );
118             # call to $resource_owner_confirms method should have called redirect_to
119 0         0 return;
120             }
121             elsif ( $res == 0 ) {
122 0         0 $dsl->debug( "OAuth2::Server: Resource owner denied scopes" );
123 0         0 $error = 'access_denied';
124             }
125             }
126             }
127              
128 10 100       35 if ( $res ) {
    50          
129 6         34 $dsl->debug( "OAuth2::Server: Generating auth code for $c_id" );
130 6   50     457 my $expires_in = $settings->{auth_code_ttl} // 600;
131              
132 6         27 my $auth_code = $server->generate_token($dsl, $settings, $expires_in, $c_id, \@scopes, 'auth', $url );
133              
134 6         28 $server->store_auth_code($dsl, $settings, $auth_code,$c_id,$expires_in,$url,@scopes );
135              
136 6         38 $uri->query_param_append( code => $auth_code );
137              
138             } elsif ( $error ) {
139 4         35 $uri->query_param_append( error => $error );
140             } else {
141             # callback has not returned anything, assume server error
142 0         0 $uri->query_param_append( error => 'server_error' );
143 0         0 $uri->query_param_append( error_description => 'call to verify_client returned unexpected value' );
144             }
145              
146 10 100       876 $uri->query_param_append( state => $state ) if defined( $state );
147              
148 10         746 $dsl->redirect( $uri );
149             }
150              
151             sub _access_token_request {
152 8     8   22 my ($dsl, $settings) = @_;
153             my ( $client_id,$client_secret,$grant_type,$auth_code,$url,$refresh_token )
154 8   100     22 = map { $dsl->param( $_ ) // undef }
  48         669  
155             qw/ client_id client_secret grant_type code redirect_uri refresh_token /;
156              
157 8         134 my $server = get_server_class( $settings );
158              
159 8 50 66     208 if (
      66        
      66        
      33        
      66        
      33        
160             ! defined( $grant_type )
161             or ( $grant_type ne 'authorization_code' and $grant_type ne 'refresh_token' )
162             or ( $grant_type eq 'authorization_code' and ! defined( $auth_code ) )
163             or ( $grant_type eq 'authorization_code' and ! defined( $url ) )
164             ) {
165 2         13 $dsl->status( 400 );
166 2         296 return $dsl->to_json(
167             {
168             error => 'invalid_request',
169             error_description => 'the request was missing one of: grant_type, '
170             . 'client_id, client_secret, code, redirect_uri;'
171             . 'or grant_type did not equal "authorization_code" '
172             . 'or "refresh_token"',
173             error_uri => '',
174             }
175             );
176 0         0 return;
177             }
178              
179 6         12 my $json_response = {};
180 6         10 my $status = 400;
181 6         8 my ( $client,$error,$scope,$old_refresh_token,$user_id );
182              
183 6 100       17 if ( $grant_type eq 'refresh_token' ) {
184 1         5 ( $client,$error,$scope,$user_id ) = _verify_access_token_and_scope(
185             $dsl, $settings, $server, $refresh_token
186             );
187 1         3 $old_refresh_token = $refresh_token;
188             } else {
189 5         26 ( $client,$error,$scope,$user_id ) = $server->verify_auth_code(
190             $dsl, $settings, $client_id,$client_secret,$auth_code,$url
191             );
192             }
193              
194 6 100       20 if ( $client ) {
    50          
195              
196 5         29 $dsl->debug( "OAuth2::Server: Generating access token for $client" );
197              
198 5   50     440 my $expires_in = $settings->{access_token_ttl} // 3600;
199 5         23 my $access_token = $server->generate_token($dsl, $settings, $expires_in,$client,$scope,'access',undef,$user_id );
200 5         18 my $refresh_token = $server->generate_token($dsl, $settings, undef,$client,$scope,'refresh',undef,$user_id );
201              
202 5         24 $server->store_access_token(
203             $dsl, $settings,
204             $client,$auth_code,$access_token,$refresh_token,
205             $expires_in,$scope,$old_refresh_token
206             );
207              
208 5         5 $status = 200;
209 5         33 $json_response = {
210             access_token => $access_token,
211             token_type => 'Bearer',
212             expires_in => $expires_in,
213             refresh_token => $refresh_token,
214             };
215              
216             } elsif ( $error ) {
217 1         4 $json_response->{error} = $error;
218             } else {
219             # callback has not returned anything, assume server error
220 0         0 $json_response = {
221             error => 'server_error',
222             error_description => 'call to verify_auth_code returned unexpected value',
223             };
224             }
225              
226 6         47 $dsl->header( 'Cache-Control' => 'no-store' );
227 6         1339 $dsl->header( 'Pragma' => 'no-cache' );
228              
229 6         512 $dsl->status( $status );
230 6         703 return $dsl->to_json( $json_response );
231             }
232              
233             sub _verify_access_token_and_scope {
234 9     9   25 my ($dsl, $settings, $server, $refresh_token, @scopes) = @_;
235              
236 9         17 my $access_token;
237              
238 9 100       24 if ( ! $refresh_token ) {
239 8 100       57 if ( my $auth_header = $dsl->app->request->header( 'Authorization' ) ) {
240 7         1132 my ( $auth_type,$auth_access_token ) = split( / /,$auth_header );
241              
242 7 50       24 if ( $auth_type ne 'Bearer' ) {
243 0         0 $dsl->debug( "OAuth2::Server: Auth type is not 'Bearer'" );
244 0         0 return ( 0,'invalid_request' );
245             } else {
246 7         17 $access_token = $auth_access_token;
247             }
248             } else {
249 1         186 $dsl->debug( "OAuth2::Server: Authorization header missing" );
250 1         92 return ( 0,'invalid_request' );
251             }
252             } else {
253 1         3 $access_token = $refresh_token;
254             }
255              
256 8         45 return $server->verify_access_token($dsl, $settings, $access_token,\@scopes,$refresh_token );
257             }
258              
259             register_plugin;
260              
261             1;
262             __END__