File Coverage

blib/lib/OIDC/Client/Role/AttributesManager.pm
Criterion Covered Total %
statement 109 112 97.3
branch 23 26 88.4
condition 4 5 80.0
subroutine 23 24 95.8
pod n/a
total 159 167 95.2


line stmt bran cond sub pod time code
1             package OIDC::Client::Role::AttributesManager;
2 3     3   160194 use utf8;
  3         321  
  3         25  
3 3     3   487 use Moose::Role;
  3         403361  
  3         19  
4 3     3   31840 use namespace::autoclean;
  3         7793  
  3         30  
5 3     3   336 use feature 'signatures';
  3         6  
  3         499  
6 3     3   22 no warnings 'experimental::signatures';
  3         6  
  3         166  
7 3     3   469 use Readonly;
  3         3322  
  3         230  
8 3     3   18 use Carp qw(croak);
  3         12  
  3         184  
9 3     3   1924 use Data::UUID;
  3         2638  
  3         244  
10 3     3   528 use Mojo::File;
  3         192518  
  3         219  
11 3     3   436 use Mojo::JSON qw(decode_json);
  3         5803  
  3         180  
12 3     3   491 use Mojo::UserAgent;
  3         224730  
  3         30  
13 3     3   1816 use OIDC::Client::ResponseParser;
  3         20  
  3         185  
14 3     3   2372 use OIDC::Client::TokenResponseParser;
  3         48  
  3         10873  
15              
16             =encoding utf8
17              
18             =head1 NAME
19              
20             OIDC::Client::Role::AttributesManager - Attributes manager
21              
22             =head1 DESCRIPTION
23              
24             This Moose role declares and builds the various attributes of the L<OIDC::Client> module.
25              
26             =cut
27              
28             requires qw(log_msg);
29              
30             Readonly my %DEFAULT_JWT_DECODING_OPTIONS => (
31             verify_exp => 1, # require valid 'exp' claim
32             verify_iat => 1, # require valid 'iat' claim
33             leeway => 60, # to account for clock skew
34             );
35             Readonly my %DEFAULT_CLIENT_SECRET_JWT_ENCODING_OPTIONS => (
36             alg => 'HS256',
37             );
38             Readonly my %DEFAULT_PRIVATE_KEY_JWT_ENCODING_OPTIONS => (
39             alg => 'RS256',
40             );
41             Readonly my %DEFAULT_CHI_CONFIG => (
42             driver => 'Memory',
43             global => 0,
44             );
45             Readonly my $DEFAULT_GRANT_TYPE => 'authorization_code';
46             Readonly my $DEFAULT_TOKEN_TYPE => 'Bearer';
47             Readonly my $DEFAULT_STORE_MODE => 'session';
48             Readonly my $DEFAULT_CLIENT_AUTH_METHOD => 'client_secret_basic';
49             Readonly my $DEFAULT_TOKEN_VALIDATION_METHOD => 'jwt';
50             Readonly my $DEFAULT_CLIENT_ASSERTION_LIFETIME => 120;
51             Readonly my $DEFAULT_MAX_ID_TOKEN_AGE => 30; # in addition to the leeway to account for clock skew
52              
53             has 'config' => (
54             is => 'ro',
55             isa => 'HashRef',
56             default => sub { {} },
57             );
58              
59             foreach my $attr_name (qw( private_key_file private_jwk_file role_prefix client_assertion_audience
60             signin_redirect_path signin_redirect_uri logout_redirect_path post_logout_redirect_uri
61             scope refresh_scope well_known_url )) {
62             has $attr_name => (
63             is => 'ro',
64             isa => 'Maybe[Str]',
65             lazy => 1,
66             default => sub { shift->config->{$attr_name} },
67             );
68             }
69              
70             foreach my $attr_name (qw( username password )) {
71             has $attr_name => (
72             is => 'rw',
73             isa => 'Maybe[Str]',
74             lazy => 1,
75             default => sub { shift->config->{$attr_name} },
76             );
77             }
78              
79             foreach my $attr_name (qw( expiration_leeway identity_expires_in )) {
80             has $attr_name => (
81             is => 'ro',
82             isa => 'Maybe[Int]',
83             lazy => 1,
84             default => sub { shift->config->{$attr_name} },
85             );
86             }
87              
88             foreach my $attr_name (qw( proxy_detect logout_with_id_token )) {
89             has $attr_name => (
90             is => 'ro',
91             isa => 'Maybe[Bool]',
92             lazy => 1,
93             default => sub { shift->config->{$attr_name} },
94             );
95             }
96              
97             foreach my $attr_name (qw( private_jwk mocked_identity mocked_access_token mocked_userinfo
98             authorize_endpoint_extra_params logout_extra_params )) {
99             has $attr_name => (
100             is => 'ro',
101             isa => 'Maybe[HashRef]',
102             lazy => 1,
103             default => sub { shift->config->{$attr_name} },
104             );
105             }
106              
107             has 'provider' => (
108             is => 'ro',
109             isa => 'Str',
110             lazy => 1,
111             builder => '_build_provider',
112             );
113              
114             has 'id' => (
115             is => 'ro',
116             isa => 'Str',
117             lazy => 1,
118             builder => '_build_id',
119             );
120              
121             has 'secret' => (
122             is => 'rw',
123             isa => 'Maybe[Str]',
124             lazy => 1,
125             builder => '_build_secret',
126             );
127              
128             has 'private_key' => (
129             is => 'rw',
130             isa => 'HashRef|ScalarRef',
131             lazy => 1,
132             builder => '_build_private_key',
133             );
134              
135             has 'audience' => (
136             is => 'ro',
137             isa => 'Str',
138             lazy => 1,
139             builder => '_build_audience',
140             );
141              
142             has 'user_agent' => (
143             is => 'ro',
144             isa => 'Mojo::UserAgent',
145             lazy => 1,
146             builder => '_build_user_agent',
147             );
148              
149             has 'claim_mapping' => (
150             is => 'ro',
151             isa => 'HashRef',
152             lazy => 1,
153             builder => '_build_claim_mapping',
154             );
155              
156             has 'audience_alias' => (
157             is => 'ro',
158             isa => 'Maybe[HashRef[HashRef]]',
159             lazy => 1,
160             default => sub { shift->config->{audience_alias} },
161             );
162              
163             has 'authorize_endpoint_response_mode' => (
164             is => 'ro',
165             isa => 'Maybe[ResponseMode]',
166             lazy => 1,
167             default => sub { shift->config->{authorize_endpoint_response_mode} },
168             );
169              
170             has 'max_id_token_age' => (
171             is => 'ro',
172             isa => 'Int',
173             lazy => 1,
174             default => sub { shift->config->{max_id_token_age}
175             || $DEFAULT_MAX_ID_TOKEN_AGE },
176             );
177              
178             has 'jwt_decoding_options' => (
179             is => 'rw',
180             isa => 'HashRef',
181             lazy => 1,
182             default => sub { shift->config->{jwt_decoding_options}
183             || \%DEFAULT_JWT_DECODING_OPTIONS },
184             );
185              
186             has 'client_secret_jwt_encoding_options' => (
187             is => 'rw',
188             isa => 'HashRef',
189             lazy => 1,
190             default => sub { shift->config->{client_secret_jwt_encoding_options}
191             || \%DEFAULT_CLIENT_SECRET_JWT_ENCODING_OPTIONS },
192             );
193              
194             has 'private_key_jwt_encoding_options' => (
195             is => 'rw',
196             isa => 'HashRef',
197             lazy => 1,
198             default => sub { shift->config->{private_key_jwt_encoding_options}
199             || \%DEFAULT_PRIVATE_KEY_JWT_ENCODING_OPTIONS },
200             );
201              
202             has 'token_endpoint_grant_type' => (
203             is => 'ro',
204             isa => 'GrantType',
205             lazy => 1,
206             default => sub { shift->config->{token_endpoint_grant_type}
207             || $DEFAULT_GRANT_TYPE },
208             );
209              
210             has 'client_auth_method' => (
211             is => 'ro',
212             isa => 'Maybe[ClientAuthMethod]',
213             lazy => 1,
214             default => sub { shift->config->{client_auth_method} },
215             );
216              
217             has 'token_endpoint_auth_method' => (
218             is => 'ro',
219             isa => 'ClientAuthMethod',
220             lazy => 1,
221             default => sub { my $self = shift;
222             $self->config->{token_endpoint_auth_method}
223             || $self->client_auth_method
224             || $DEFAULT_CLIENT_AUTH_METHOD },
225             );
226              
227             has 'introspection_endpoint_auth_method' => (
228             is => 'ro',
229             isa => 'ClientAuthMethod',
230             lazy => 1,
231             default => sub { my $self = shift;
232             $self->config->{introspection_endpoint_auth_method}
233             || $self->client_auth_method
234             || $DEFAULT_CLIENT_AUTH_METHOD },
235             );
236              
237             has 'token_validation_method' => (
238             is => 'ro',
239             isa => 'TokenValidationMethod',
240             lazy => 1,
241             default => sub { shift->config->{token_validation_method}
242             || $DEFAULT_TOKEN_VALIDATION_METHOD },
243             );
244              
245             has 'client_assertion_lifetime' => (
246             is => 'ro',
247             isa => 'Int',
248             lazy => 1,
249             default => sub { shift->config->{client_assertion_lifetime}
250             || $DEFAULT_CLIENT_ASSERTION_LIFETIME },
251             );
252              
253             has 'default_token_type' => (
254             is => 'ro',
255             isa => 'Str',
256             lazy => 1,
257             default => sub { $DEFAULT_TOKEN_TYPE },
258             );
259              
260             has 'provider_metadata' => (
261             is => 'ro',
262             isa => 'HashRef',
263             lazy => 1,
264             builder => '_build_provider_metadata',
265             );
266              
267             has 'store_mode' => (
268             is => 'ro',
269             isa => 'StoreMode',
270             lazy => 1,
271             default => sub { shift->config->{store_mode}
272             || $DEFAULT_STORE_MODE },
273             );
274              
275             has 'cache_config' => (
276             is => 'rw',
277             isa => 'HashRef',
278             lazy => 1,
279             default => sub { shift->config->{cache_config}
280             || \%DEFAULT_CHI_CONFIG },
281             );
282              
283             has 'kid_keys' => (
284             is => 'ro',
285             isa => 'HashRef',
286             lazy => 1,
287             clearer => '_clear_kid_keys',
288             builder => '_build_kid_keys',
289             );
290              
291             has 'uuid_generator' => (
292             is => 'ro',
293             isa => 'Data::UUID',
294             lazy => 1,
295             default => sub { Data::UUID->new() },
296             );
297              
298             has 'response_parser' => (
299             is => 'ro',
300             isa => 'OIDC::Client::ResponseParser',
301             lazy => 1,
302             default => sub { OIDC::Client::ResponseParser->new() },
303             );
304              
305             has 'token_response_parser' => (
306             is => 'ro',
307             isa => 'OIDC::Client::TokenResponseParser',
308             lazy => 1,
309             default => sub { OIDC::Client::TokenResponseParser->new() },
310             );
311              
312 55     55   100 sub _build_provider ($self) {
  55         115  
  55         88  
313             my $provider = $self->config->{provider}
314 55 100       1776 or croak('OIDC: no provider in config');
315 54         4446 return $provider;
316             }
317              
318 56     56   87 sub _build_id ($self) {
  56         119  
  56         127  
319             my $id = $self->config->{id}
320 56 100       1791 or croak('OIDC: no id in config');
321 55         1755 return $id;
322             }
323              
324 23     23   58 sub _build_secret ($self) {
  23         34  
  23         33  
325 23         819 my $secret = $self->config->{secret};
326 23 100       64 unless ($secret) {
327 2         101 my $provider = $self->provider;
328 2         12 $secret = $ENV{uc "OIDC_${provider}_SECRET"};
329             }
330 23 100       78 $secret or croak("OIDC: no secret configured or set up in environment");
331 22         728 return $secret;
332             }
333              
334 4     4   10 sub _build_private_key ($self) {
  4         22  
  4         9  
335 4 100       134 if (my $private_jwk_file = $self->private_jwk_file) {
    100          
    100          
    50          
336 1         13 my $private_jwk = decode_json(Mojo::File->new($private_jwk_file)->slurp);
337 1         229 return $private_jwk;
338             }
339             elsif (my $private_jwk = $self->private_jwk) {
340 1         19 return $private_jwk;
341             }
342             elsif (my $private_key_file = $self->private_key_file) {
343 1         16 my $private_key = Mojo::File->new($private_key_file)->slurp;
344 1         369 return \$private_key;
345             }
346             elsif (my $private_key = $self->config->{private_key}) {
347 1         45 return \$private_key;
348             }
349             else {
350 0         0 croak('OIDC: no private_jwk_file, private_jwk, private_key_file or private_key has been configured');
351             }
352             }
353              
354 58     58   89 sub _build_audience ($self) {
  58         94  
  58         93  
355 58   66     1954 return $self->config->{audience} || $self->id;
356             }
357              
358 1     1   3 sub _build_user_agent ($self) {
  1         2  
  1         2  
359 1         10 my $ua = Mojo::UserAgent->new();
360              
361 1 50       59 if ($self->proxy_detect) {
362 1         10 $ua->proxy->detect;
363             }
364              
365 1 50       172 if (my $user_agent = $self->config->{user_agent}) {
366 1         8 $ua->transactor->name($user_agent);
367             }
368              
369             $ua->on(start => sub {
370 0     0   0 my ($ua, $tx) = @_;
371 0         0 $tx->req->headers->accept('application/json');
372 1         57 });
373              
374 1         56 return $ua;
375             }
376              
377 3     3   10 sub _build_claim_mapping ($self) {
  3         7  
  3         24  
378 3   100     128 return $self->config->{claim_mapping} || {};
379             }
380              
381 20     20   78 sub _build_provider_metadata ($self) {
  20         48  
  20         34  
382 20         42 my $provider_metadata = {};
383              
384 20 100       846 if (my $well_known_url = $self->well_known_url) {
385 2         11 $provider_metadata = $self->_get_provider_metadata($well_known_url);
386             }
387              
388             # provider metadata can be overloaded by configuration
389 20         67 for (qw/authorize_url end_session_url issuer token_url introspection_url userinfo_url jwks_url/) {
390 140 100       4619 $provider_metadata->{$_} = $self->config->{$_} if exists $self->config->{$_};
391             }
392              
393 20         766 return $provider_metadata;
394             }
395              
396 4     4   10 sub _build_kid_keys ($self) {
  4         10  
  4         11  
397 4         199 my $provider = $self->provider;
398 4         29 $self->log_msg(info => "OIDC/$provider: fetching JWT kid keys");
399              
400             my $jwks_url = $self->provider_metadata->{jwks_url}
401 4 100       514 or croak("OIDC: jwks_url not found in provider metadata");
402              
403 3         140 my $res = $self->user_agent->get($jwks_url)->result;
404              
405 3         760 return $self->response_parser->parse($res);
406             }
407              
408             sub _get_provider_metadata {
409 2     2   8 my ($self, $well_known_url) = @_;
410              
411 2         93 my $provider = $self->provider;
412 2         18 $self->log_msg(info => "OIDC/$provider: fetching OpenID configuration from $well_known_url");
413              
414 2         277 my $res = $self->user_agent->get($well_known_url)->result;
415 2         549 my $provider_config = $self->response_parser->parse($res);
416              
417             return {
418             authorize_url => $provider_config->{authorization_endpoint},
419             end_session_url => $provider_config->{end_session_endpoint},
420             issuer => $provider_config->{issuer},
421             token_url => $provider_config->{token_endpoint},
422             introspection_url => $provider_config->{introspection_endpoint},
423             userinfo_url => $provider_config->{userinfo_endpoint},
424             jwks_url => $provider_config->{jwks_uri},
425 2         183 };
426             }
427              
428             1;