File Coverage

blib/lib/Dancer2/Plugin/OAuth2/Server/Simple.pm
Criterion Covered Total %
statement 93 101 92.0
branch 27 40 67.5
condition 21 50 42.0
subroutine 18 18 100.0
pod 0 9 0.0
total 159 218 72.9


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