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 4     4   1655949 use strict;
  4         11  
  4         105  
4 4     4   23 use warnings;
  4         8  
  4         99  
5 4     4   117 use 5.008_005;
  4         12  
6             our $VERSION = '0.09';
7 4     4   3210 use Dancer2::Plugin;
  4         140475  
  4         31  
8 4     4   9372 use URI;
  4         8195  
  4         150  
9 4     4   1923 use URI::QueryParam;
  4         1823  
  4         135  
10 4     4   23 use Class::Load qw(try_load_class);
  4         8  
  4         238  
11 4     4   23 use Carp;
  4         7  
  4         6751  
12              
13             sub get_server_class {
14 27     27 0 60 my ($settings) = @_;
15 27   50     174 my $server_class = $settings->{server_class}//"Dancer2::Plugin::OAuth2::Server::Simple";
16 27         127 my ($ok, $error) = try_load_class($server_class);
17 27 50       2421 if (! $ok) {
18 0         0 confess "Cannot load server class $server_class: $error";
19             }
20              
21 27         161 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   34 my ($dsl, $scopes, $code_ref) = @_;
44              
45 2         9 my $settings = plugin_setting;
46              
47 2 50       203 $scopes = [$scopes] unless ref $scopes eq 'ARRAY';
48              
49             return sub {
50 8     8   64005 my $server = get_server_class( $settings );
51 8         119 my @res = _verify_access_token_and_scope( $dsl, $settings, $server,0, @$scopes );
52 8 100       36 if( not $res[0] ) {
53 3         13 $dsl->status( 400 );
54 3         377 return $dsl->to_json( { error => $res[1] } );
55             } else {
56 5         39 $dsl->app->request->var( oauth_access_token => $res[0] );
57 5         76 goto $code_ref;
58             }
59             }
60 2         17 };
61              
62             sub _authorization_request {
63 11     11   34 my ($dsl, $settings) = @_;
64             my ( $c_id,$url,$type,$scope,$state )
65 11   100     35 = map { $dsl->param( $_ ) // undef }
  55         833  
66             qw/ client_id redirect_uri response_type scope state /;
67              
68 11         189 my $server = get_server_class( $settings );
69              
70 11 100       215 my @scopes = $scope ? split( / /,$scope ) : ();
71              
72 11 100 66     106 if (
      66        
73             ! defined( $c_id )
74             or ! defined( $type )
75             or $type ne 'code'
76             ) {
77 3         65 $dsl->status( 400 );
78 3         2716 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 8   100     47 my $state_required = $settings->{state_required} // 0;
90 8 100 100     35 if(
      66        
91             $state_required
92             and ! defined $state
93             and ! length $state
94             ) {
95 1         7 $dsl->status( 400 );
96 1         131 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 7         55 my $uri = URI->new( $url );
106 7         572 my ( $res,$error ) = $server->verify_client($dsl, $settings, $c_id, \@scopes );
107              
108 7 100       38 if ( $res ) {
109 4 50       19 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 4         19 $dsl->debug( "OAuth2::Server: Resource owner is logged in" );
115 4         368 $res = $server->confirm_by_resource_owner($dsl, $settings, $c_id, \@scopes );
116 4 50       22 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 7 100       23 if ( $res ) {
    50          
129 4         19 $dsl->debug( "OAuth2::Server: Generating auth code for $c_id" );
130 4   50     321 my $expires_in = $settings->{auth_code_ttl} // 600;
131              
132 4         20 my $auth_code = $server->generate_token($dsl, $settings, $expires_in, $c_id, \@scopes, 'auth', $url );
133              
134 4         20 $server->store_auth_code($dsl, $settings, $auth_code,$c_id,$expires_in,$url,@scopes );
135              
136 4         34 $uri->query_param_append( code => $auth_code );
137              
138             } elsif ( $error ) {
139 3         31 $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 7 100       603 $uri->query_param_append( state => $state ) if defined( $state );
147              
148 7         705 $dsl->redirect( $uri );
149             }
150              
151             sub _access_token_request {
152 8     8   24 my ($dsl, $settings) = @_;
153             my ( $client_id,$client_secret,$grant_type,$auth_code,$url,$refresh_token )
154 8   100     25 = map { $dsl->param( $_ ) // undef }
  48         681  
155             qw/ client_id client_secret grant_type code redirect_uri refresh_token /;
156              
157 8         153 my $server = get_server_class( $settings );
158              
159 8 50 66     221 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         319 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         13 my $json_response = {};
180 6         14 my $status = 400;
181 6         9 my ( $client,$error,$scope,$old_refresh_token,$user_id );
182              
183 6 100       29 if ( $grant_type eq 'refresh_token' ) {
184 1         4 ( $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         29 ( $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       23 if ( $client ) {
    50          
195              
196 5         66 $dsl->debug( "OAuth2::Server: Generating access token for $client" );
197              
198 5   50     457 my $expires_in = $settings->{access_token_ttl} // 3600;
199 5         27 my $access_token = $server->generate_token($dsl, $settings, $expires_in,$client,$scope,'access',undef,$user_id );
200 5         22 my $refresh_token = $server->generate_token($dsl, $settings, undef,$client,$scope,'refresh',undef,$user_id );
201              
202 5         25 $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         10 $status = 200;
209 5         27 $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         5 $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         46 $dsl->header( 'Cache-Control' => 'no-store' );
227 6         1523 $dsl->header( 'Pragma' => 'no-cache' );
228              
229 6         532 $dsl->status( $status );
230 6         806 return $dsl->to_json( $json_response );
231             }
232              
233             sub _verify_access_token_and_scope {
234 9     9   37 my ($dsl, $settings, $server, $refresh_token, @scopes) = @_;
235              
236 9         19 my $access_token;
237              
238 9 100       37 if ( ! $refresh_token ) {
239 8 100       73 if ( my $auth_header = $dsl->app->request->header( 'Authorization' ) ) {
240 7         1408 my ( $auth_type,$auth_access_token ) = split( / /,$auth_header );
241              
242 7 50       27 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         21 $access_token = $auth_access_token;
247             }
248             } else {
249 1         147 $dsl->debug( "OAuth2::Server: Authorization header missing" );
250 1         86 return ( 0,'invalid_request' );
251             }
252             } else {
253 1         2 $access_token = $refresh_token;
254             }
255              
256 8         49 return $server->verify_access_token($dsl, $settings, $access_token,\@scopes,$refresh_token );
257             }
258              
259             register_plugin;
260              
261             1;
262             __END__