File Coverage

blib/lib/OIDC/Client/AccessToken.pm
Criterion Covered Total %
statement 54 54 100.0
branch 8 8 100.0
condition 4 4 100.0
subroutine 15 15 100.0
pod 5 5 100.0
total 86 86 100.0


line stmt bran cond sub pod time code
1             package OIDC::Client::AccessToken;
2 7     7   673025 use utf8;
  7         311  
  7         58  
3 7     7   1536 use Moose;
  7         1130412  
  7         64  
4 7     7   53395 use MooseX::Params::Validate;
  7         222861  
  7         69  
5 7     7   5901 use namespace::autoclean;
  7         34517  
  7         89  
6              
7 7     7   715 use Carp qw(croak);
  7         13  
  7         528  
8 7     7   3798 use Digest::SHA qw(sha256 sha384 sha512);
  7         20285  
  7         967  
9 7     7   2860 use MIME::Base64 qw(encode_base64url);
  7         4359  
  7         548  
10 7     7   58 use List::Util qw(any);
  7         11  
  7         485  
11 7     7   3408 use OIDC::Client::Error::TokenValidation;
  7         111  
  7         5764  
12              
13             =encoding utf8
14              
15             =head1 NAME
16              
17             OIDC::Client::AccessToken - Access Token class
18              
19             =head1 DESCRIPTION
20              
21             Class representing an access token
22              
23             =head1 ATTRIBUTES
24              
25             =head2 token
26              
27             The string of the access token. Required
28              
29             =head2 token_type
30              
31             The type of the access token
32              
33             =head2 expires_at
34              
35             The expiration time of the access token (number of seconds since 1970-01-01T00:00:00Z)
36              
37             =head2 scopes
38              
39             The scopes (arrayref) of the access token
40              
41             =head2 claims
42              
43             Hashref of claims coming from the access token. Optional, as an access token
44             is not always decoded, depending on the nature of the application.
45              
46             =cut
47              
48             has 'token' => (
49             is => 'ro',
50             isa => 'Str',
51             required => 1,
52             );
53              
54             has 'token_type' => (
55             is => 'ro',
56             isa => 'Maybe[Str]',
57             required => 0,
58             );
59              
60             has 'expires_at' => (
61             is => 'ro',
62             isa => 'Maybe[Int]',
63             required => 0,
64             );
65              
66             has 'scopes' => (
67             is => 'ro',
68             isa => 'Maybe[ArrayRef[Str]]',
69             required => 0,
70             predicate => 'has_scopes',
71             );
72              
73             has 'claims' => (
74             is => 'ro',
75             isa => 'Maybe[HashRef]',
76             required => 0,
77             );
78              
79             =head1 METHODS
80              
81             =head2 has_scope( $expected_scope )
82              
83             my $has_scope = $access_token->has_scope($expected_scope);
84              
85             Returns whether a scope is present in the scopes of the access token.
86              
87             =cut
88              
89             sub has_scope {
90 4     4 1 21 my $self = shift;
91 4         30 my ($expected_scope) = pos_validated_list(\@_, { isa => 'Str', optional => 0 });
92              
93 4   100 5   1318 return any { $_ eq $expected_scope } @{$self->scopes // []};
  5         31  
  4         206  
94             }
95              
96              
97             =head2 has_expired( $leeway )
98              
99             my $has_expired = $access_token->has_expired($leeway);
100              
101             Returns whether the access token has expired.
102              
103             Returns undef if the C<expires_at> attribute is not defined.
104              
105             The list parameters are:
106              
107             =over 2
108              
109             =item leeway
110              
111             Number of seconds of leeway for the token to be considered expired before it actually is.
112              
113             =back
114              
115             =cut
116              
117             sub has_expired {
118 17     17 1 908 my $self = shift;
119 17         150 my ($leeway) = pos_validated_list(\@_, { isa => 'Maybe[Int]', optional => 1 });
120 17   100     4120 $leeway //= 0;
121              
122 17 100       750 return unless defined $self->expires_at;
123              
124 16         725 return ( $self->expires_at - $leeway ) < time;
125             }
126              
127              
128             =head2 compute_at_hash( $alg )
129              
130             my $at_hash = $access_token->compute_at_hash($alg);
131              
132             Returns the computed C<at_hash> for access token. The C<at_hash> is created by
133             hashing the access token using the algorithm specified in the I<$alg> parameter,
134             taking the left-most half of the hash, and then base64url encoding it.
135              
136             =cut
137              
138             sub compute_at_hash {
139 8     8 1 125 my $self = shift;
140 8         62 my ($alg) = pos_validated_list(\@_, { isa => 'Str', optional => 0 });
141              
142             my $digest_func = {
143             HS256 => \&sha256, RS256 => \&sha256, PS256 => \&sha256, ES256 => \&sha256,
144             HS384 => \&sha384, RS384 => \&sha384, PS384 => \&sha384, ES384 => \&sha384,
145             HS512 => \&sha512, RS512 => \&sha512, PS512 => \&sha512, ES512 => \&sha512,
146 8 100       2769 }->{$alg} or croak("OIDC: unsupported signing algorithm: $alg");
147              
148 7         349 my $digest = $digest_func->($self->token);
149 7         36 my $left_half = substr($digest, 0, length($digest)/2);
150 7         40 return encode_base64url($left_half);
151             }
152              
153              
154             =head2 verify_at_hash( $expected_at_hash, $alg )
155              
156             $access_token->verify_at_hash($expected_at_hash, $alg);
157              
158             If the value of the I<$expected_at_hash> parameter is undefined, returns a true value.
159             Throws an L<OIDC::Client::Error::TokenValidation> exception if the computed C<at_hash> for access
160             token and specified I<$alg> algorithm does not match the value of the I<$expected_at_hash> parameter.
161             Otherwise, returns a true value.
162              
163             =cut
164              
165             sub verify_at_hash {
166 7     7 1 99 my $self = shift;
167 7         71 my ($expected_at_hash, $alg) = pos_validated_list(\@_, { isa => 'Maybe[Str]', optional => 0 },
168             { isa => 'Str', optional => 0 });
169 7 100       3828 return 1 unless defined $expected_at_hash;
170              
171 4         24 my $at_hash = $self->compute_at_hash($alg);
172              
173 4 100       161 $at_hash eq $expected_at_hash
174             or OIDC::Client::Error::TokenValidation->throw("OIDC: unexpected at_hash");
175              
176 1         5 return 1;
177             }
178              
179              
180             =head2 to_hashref()
181              
182             my $access_token_href = $access_token->to_hashref();
183              
184             Returns a hashref of the access token data.
185              
186             =cut
187              
188             sub to_hashref {
189 33     33 1 82 my $self = shift;
190              
191             return {
192 103         3142 map { $_ => $self->$_ }
193 165         5810 grep { defined $self->$_ }
194 33         211 map { $_->name } $self->meta->get_all_attributes
  165         2892  
195             };
196             }
197              
198              
199             __PACKAGE__->meta->make_immutable;
200              
201             1;