File Coverage

blib/lib/Dancer2/Plugin/OAuth2/Server/Simple.pm
Criterion Covered Total %
statement 100 108 92.5
branch 31 44 70.4
condition 21 50 42.0
subroutine 18 18 100.0
pod 0 9 0.0
total 170 229 74.2


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::OAuth2::Server::Simple;
2 4     4   4247 use Moo;
  4         8  
  4         32  
3 4     4   5192 use Time::HiRes qw/ gettimeofday /;
  4         6195  
  4         20  
4 4     4   695 use MIME::Base64 qw/ encode_base64 decode_base64 /;
  4         9  
  4         251  
5 4     4   31 use Carp qw/ croak /;
  4         10  
  4         238  
6 4     4   3051 use Crypt::PRNG qw/ random_string /;
  4         13188  
  4         246  
7 4     4   2883 use Dancer2::Plugin::OAuth2::Server::Role;
  4         11  
  4         166  
8             with 'Dancer2::Plugin::OAuth2::Server::Role';
9 4     4   23 use feature qw/state/;
  4         9  
  4         7438  
10              
11             sub _get_clients {
12 52     52   82 my ($self, $dsl, $settings) = @_;
13              
14 52   50     418 return $settings->{clients} // {};
15             }
16              
17             sub login_resource_owner {
18 6     6 0 14 my ($self, $dsl, $settings) = @_;
19              
20 6         22 return 1;
21             }
22              
23             sub confirm_by_resource_owner {
24 6     6 0 14 my ($self, $dsl, $settings, $client_id, $scopes) = @_;
25              
26 6         13 return 1;
27             }
28              
29             sub verify_client {
30 10     10 0 27 my ($self, $dsl, $settings, $client_id, $scopes, $uri) = @_;
31              
32 10 100       38 if ( my $client = $self->_get_clients($dsl, $settings)->{$client_id} ) {
33              
34 9   50     63 foreach my $scope ( @{ $scopes // [] } ) {
  9         32  
35              
36 10 100       37 if ( ! exists( $self->_get_clients($dsl, $settings)->{$client_id}{scopes}{$scope} ) ) {
    100          
37 1         8 $dsl->debug( "OAuth2::Server: Client lacks scope ($scope)" );
38 1         95 return ( 0,'invalid_scope' );
39             } elsif ( ! $self->_get_clients($dsl, $settings)->{$client_id}{scopes}{$scope} ) {
40 1         24 $dsl->debug( "OAuth2::Server: Client cannot scope ($scope)" );
41 1         89 return ( 0,'access_denied' );
42             }
43             }
44              
45 7 100       18 if ( exists( $self->_get_clients($dsl, $settings)->{$client_id}{redirect_uri} ) ) {
46 3         7 my $whitelisted_uris = $self->_get_clients($dsl, $settings)->{$client_id}{redirect_uri};
47 3 100       7 if( ! grep { $_ eq $uri } @$whitelisted_uris ) {
  6         15  
48 1         23 $dsl->debug( "OAuth2::Server: Client does not accept uri ($uri)" );
49 1         89 return ( 0,'unauthorized_uri' );
50             } else {
51 2         17 $dsl->debug( "OAuth2::Server: Client accept uri ($uri)" );
52             }
53             }
54              
55 6         175 return ( 1 );
56             }
57              
58 1         7 $dsl->debug( "OAuth2::Server: Client ($client_id) does not exist" );
59 1         84 return ( 0,'unauthorized_client' );
60             }
61              
62             sub generate_token {
63 16     16 0 41 my ( $self, $dsl, $settings, $ttl,$client_id,$scopes,$type,$redirect_url,$user_id ) = @_;
64              
65 16         22 my $code;
66              
67             #if ( ! $JWT_SECRET ) {
68 16         96 my ( $sec,$usec ) = gettimeofday;
69 16         67 $code = encode_base64( join( '-',$sec,$usec,rand(),random_string(30) ),'' );
70             #} else {
71             #$code = Mojo::JWT->new(
72             #( $ttl ? ( expires => time + $ttl ) : () ),
73             #secret => $JWT_SECRET,
74             #set_iat => 1,
75             ## https://tools.ietf.org/html/rfc7519#section-4
76             #claims => {
77             ## Registered Claim Names
78             ## iss => undef, # us, the auth server / application (set using plugin config?)
79             ## sub => undef, # the logged in user, we could get this by returning it from the resource_owner_logged_in callback
80             #aud => $redirect_url, # the "audience"
81             #jti => random_string(32),
82              
83             ## Private Claim Names
84             #user_id => $user_id,
85             #client => $client_id,
86             #type => $type,
87             #scopes => $scopes,
88             #},
89             #)->encode;
90             #}
91              
92 16         3670 return $code;
93             }
94              
95             state %AUTH_CODES;
96             sub store_auth_code {
97 6     6 0 20 my ( $self, $dsl, $settings, $auth_code,$client_id,$expires_in,$uri,@scopes ) = @_;
98             #return if $JWT_SECRET;
99              
100             $AUTH_CODES{$auth_code} = {
101             client_id => $client_id,
102             expires => time + $expires_in,
103             redirect_uri => $uri,
104 6         23 scope => { map { $_ => 1 } @scopes },
  7         52  
105             };
106              
107 6         22 return 1;
108             }
109              
110             state %REFRESH_TOKENS;
111             state %ACCESS_TOKENS;
112             sub verify_access_token {
113 8     8 0 21 my ( $self, $dsl, $settings, $access_token,$scopes_ref,$is_refresh_token ) = @_;
114              
115             #return _verify_access_token_jwt( @_ ) if $JWT_SECRET;
116              
117 8 100 66     50 if (
    50          
118             $is_refresh_token
119             && exists( $REFRESH_TOKENS{$access_token} )
120             ) {
121              
122 1 50       6 if ( $scopes_ref ) {
123 1   50     1 foreach my $scope ( @{ $scopes_ref // [] } ) {
  1         6  
124 0 0 0     0 if (
125             ! exists( $REFRESH_TOKENS{$access_token}{scope}{$scope} )
126             or ! $REFRESH_TOKENS{$access_token}{scope}{$scope}
127             ) {
128 0         0 $dsl->debug( "OAuth2::Server: Refresh token does not have scope ($scope)" );
129 0         0 return ( 0,'invalid_grant' )
130             }
131             }
132             }
133              
134 1         8 return $REFRESH_TOKENS{$access_token}{client_id};
135             }
136             elsif ( exists( $ACCESS_TOKENS{$access_token} ) ) {
137              
138 7 50       83 if ( $ACCESS_TOKENS{$access_token}{expires} <= time ) {
    50          
139 0         0 $dsl->debug( "OAuth2::Server: Access token has expired" );
140 0         0 $self->revoke_access_token( $dsl, $settings, $access_token );
141 0         0 return ( 0,'invalid_grant' )
142             } elsif ( $scopes_ref ) {
143              
144 7   50     9 foreach my $scope ( @{ $scopes_ref // [] } ) {
  7         28  
145 7 100 66     56 if (
146             ! exists( $ACCESS_TOKENS{$access_token}{scope}{$scope} )
147             or ! $ACCESS_TOKENS{$access_token}{scope}{$scope}
148             ) {
149 2         14 $dsl->debug( "OAuth2::Server: Access token does not have scope ($scope)" );
150 2         183 return ( 0,'invalid_grant' )
151             }
152             }
153              
154             }
155              
156 5         22 $dsl->debug( "OAuth2::Server: Access token is valid" );
157 5         453 return $ACCESS_TOKENS{$access_token}{client_id};
158             }
159              
160 0         0 $dsl->debug( "OAuth2::Server: Access token does not exist" );
161 0         0 return ( 0,'invalid_grant' )
162             }
163              
164             sub revoke_access_token {
165 2     2 0 6 my ( $self, $dsl, $settings, $access_token ) = @_;
166 2         12 delete( $ACCESS_TOKENS{$access_token} );
167             }
168              
169             sub verify_auth_code {
170 5     5 0 12 my ($self, $dsl, $settings, $client_id,$client_secret,$auth_code,$uri ) = @_;
171             #return _verify_auth_code_jwt( @_ ) if $JWT_SECRET;
172              
173 5         39 my ( $sec,$usec,$rand ) = split( '-',decode_base64( $auth_code ) );
174              
175 5 100 33     38 if (
      33        
      33        
      33        
      66        
      33        
176             ! exists( $AUTH_CODES{$auth_code} )
177             or ! exists( $self->_get_clients($dsl, $settings)->{$client_id} )
178             or ( $client_secret ne $self->_get_clients($dsl, $settings)->{$client_id}{client_secret} )
179             or $AUTH_CODES{$auth_code}{access_token}
180             or ( $uri && $AUTH_CODES{$auth_code}{redirect_uri} ne $uri )
181             or ( $AUTH_CODES{$auth_code}{expires} <= time )
182             ) {
183              
184             $dsl->debug( "OAuth2::Server: Auth code does not exist" )
185 1 50       6 if ! exists( $AUTH_CODES{$auth_code} );
186             $dsl->debug( "OAuth2::Server: Client ($client_id) does not exist" )
187 1 50       4 if ! exists( $self->_get_clients($dsl, $settings)->{$client_id} );
188             $dsl->debug( "OAuth2::Server: Client secret does not match" )
189             if (
190             ! $client_secret
191             or ! $self->_get_clients($dsl, $settings)->{$client_id}
192             or $client_secret ne $self->_get_clients($dsl, $settings)->{$client_id}{client_secret}
193 1 50 33     15 );
      33        
194              
195 1 50       7 if ( $AUTH_CODES{$auth_code} ) {
196             $dsl->debug( "OAuth2::Server: Redirect URI does not match" )
197 1 50 33     9 if ( $uri && $AUTH_CODES{$auth_code}{redirect_uri} ne $uri );
198             $dsl->debug( "OAuth2::Server: Auth code expired" )
199 1 50       6 if ( $AUTH_CODES{$auth_code}{expires} <= time );
200             }
201              
202 1 50       6 if ( my $access_token = $AUTH_CODES{$auth_code}{access_token} ) {
203             # this auth code has already been used to generate an access token
204             # so we need to revoke the access token that was previously generated
205 1         5 $dsl->debug(
206             "OAuth2::Server: Auth code already used to get access token"
207             );
208              
209 1         81 $self->revoke_access_token($dsl, $settings, $access_token );
210             }
211              
212 1         5 return ( 0,'invalid_grant' );
213             } else {
214 4         26 return ( 1,undef,$AUTH_CODES{$auth_code}{scope} );
215             }
216              
217             }
218              
219             sub store_access_token {
220             my (
221 5     5 0 15 $self, $dsl, $settings, $c_id,$auth_code,$access_token,$refresh_token,
222             $expires_in,$scope,$old_refresh_token
223             ) = @_;
224             #return if $JWT_SECRET;
225              
226 5 100 66     23 if ( ! defined( $auth_code ) && $old_refresh_token ) {
227             # must have generated an access token via a refresh token so revoke the old
228             # access token and refresh token and update the AUTH_CODES hash to store the
229             # new one (also copy across scopes if missing)
230 1         4 $auth_code = $REFRESH_TOKENS{$old_refresh_token}{auth_code};
231              
232 1         4 my $prev_access_token = $REFRESH_TOKENS{$old_refresh_token}{access_token};
233              
234             # access tokens can be revoked, whilst refresh tokens can remain so we
235             # need to get the data from the refresh token as the access token may
236             # no longer exist at the point that the refresh token is used
237 1   33     12 $scope //= $REFRESH_TOKENS{$old_refresh_token}{scope};
238              
239 1         4 $dsl->debug( "OAuth2::Server: Revoking old access token (refresh)" );
240 1         82 $self->revoke_access_token($dsl, $settings, $prev_access_token );
241             }
242              
243 5 100       17 delete( $REFRESH_TOKENS{$old_refresh_token} )
244             if $old_refresh_token;
245              
246 5         37 $ACCESS_TOKENS{$access_token} = {
247             scope => $scope,
248             expires => time + $expires_in,
249             refresh_token => $refresh_token,
250             client_id => $c_id,
251             };
252              
253 5         35 $REFRESH_TOKENS{$refresh_token} = {
254             scope => $scope,
255             client_id => $c_id,
256             access_token => $access_token,
257             auth_code => $auth_code,
258             };
259              
260 5         17 $AUTH_CODES{$auth_code}{access_token} = $access_token;
261              
262 5         20 return $c_id;
263             }
264              
265              
266 4     4   33 no Moo;
  4         8  
  4         31  
267             1;