File Coverage

blib/lib/Net/OAuth2/Scheme/Mixin/Bearer.pm
Criterion Covered Total %
statement 13 13 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 18 100.0


line stmt bran cond sub pod time code
1 1     1   652 use warnings;
  1         2  
  1         26  
2 1     1   5 use strict;
  1         1  
  1         52  
3              
4             package Net::OAuth2::Scheme::Mixin::Bearer;
5             BEGIN {
6 1     1   18 $Net::OAuth2::Scheme::Mixin::Bearer::VERSION = '0.03';
7             }
8             # ABSTRACT: implement bearer token schemes
9              
10 1     1   6 use Net::OAuth2::Scheme::Option::Defines;
  1         1  
  1         48  
11 1     1   5 use parent 'Net::OAuth2::Scheme::Mixin::Current_Secret';
  1         2  
  1         13  
12              
13             use Net::OAuth2::Scheme::HmacUtil
14             qw(encode_base64url decode_base64url
15             sign_binary unsign_binary
16             hmac_name_to_len_fn);
17              
18              
19             # Bearer tokens
20             #
21              
22             # IMPLEMENTATION (transport_)bearer
23             # (bearer_)header = 'Authorization';
24             # (bearer_)header_re = '^Authorization$';
25             # (bearer_)scheme = 'Bearer';
26             # (bearer_)scheme_re = '^Bearer$';
27             # (bearer_)allow_body = 1;
28             # (bearer_)allow_uri = 0;
29             # (bearer_)param = 'access_token';
30             # (bearer_)param_re = '^access_token$';
31             # (bearer_)client_uses_param = 0;
32             # SUMMARY
33             # Bearer token, handle-style
34              
35              
36             Default_Value bearer_token_type => 'Bearer';
37             Default_Value bearer_scheme => 'Bearer';
38             Default_Value bearer_allow_body => 1;
39             Default_Value bearer_allow_uri => 0;
40             Default_Value bearer_param => 'access_token'; #as per draft 15 of the bearer spec
41             Default_Value bearer_client_uses_param => 0;
42              
43             Define_Group bearer_param_re_set => 'default',
44             qw(bearer_param_re);
45              
46             sub pkg_bearer_param_re_set_default {
47             my __PACKAGE__ $self = shift;
48             my $param = $self->uses('bearer_param');
49             $self->install(bearer_param_re => qr(\A\Q$param\E\z));
50             return $self;
51             }
52              
53             sub pkg_transport_bearer {
54             my __PACKAGE__ $self = shift;
55             $self->parameter_prefix(bearer_ => @_);
56             $self->make_alias(bearer_header => 'transport_header');
57             $self->make_alias(bearer_header_re => 'transport_header_re');
58             $self->make_alias(bearer_scheme => 'transport_auth_scheme');
59             $self->make_alias(bearer_scheme_re => 'transport_auth_scheme_re');
60              
61             $self->install(token_type => $self->uses('bearer_token_type'));
62              
63             my $allow_body = $self->uses('bearer_allow_body');
64             my $allow_uri = $self->uses('bearer_allow_uri');
65             my $body_or_uri =
66             ($allow_body ? ($allow_uri ? 'dontcare' : 'body') : ($allow_uri ? 'query' : ''));
67              
68             if ($self->is_client) {
69             $self->install( accept_needs => [] );
70             $self->install( accept_hook => sub {return ()} );
71             if ($self->uses('bearer_client_uses_param')) {
72             $self->croak("bearer_client_uses_param requires bearer_allow_(body|uri)")
73             unless $body_or_uri;
74             my $param_name = $self->uses('bearer_param');
75             $self->http_parameter_inserter($body_or_uri, $param_name, sub { $_[0] });
76             }
77             else {
78             $self->http_header_inserter();
79             }
80             }
81              
82             if ($self->is_resource_server) {
83             my $header_extractor = $self->http_header_extractor();
84              
85             if ($body_or_uri) {
86              
87             my $param_re = $self->uses('bearer_param_re');
88             $param_re = qr{$param_re}is unless ref($param_re);
89              
90             my $param_name = $self->installed('bearer_param');
91             $self->croak("bearer_param_re does not match bearer_param")
92             if (defined($param_name) && $param_name !~ $param_re);
93              
94             my $param_extractor = $self->http_parameter_extractor($body_or_uri, $param_re);
95             $self->install( psgi_extract => sub {
96             my $env = shift;
97             return ($header_extractor->($env), $param_extractor->($env));
98             });
99             }
100             else {
101             $self->install( psgi_extract => $header_extractor );
102             }
103             }
104             }
105              
106             # IMPLEMENTATION (format_)bearer_handle
107             # SUMMARY
108             # Bearer token, handle-style
109             # REQUIRES
110             # v_id_next (v_id_is_random)
111             # v_table_insert
112              
113             sub pkg_format_bearer_handle {
114             my __PACKAGE__ $self = shift;
115              
116             # yes, we can use this for authcodes and refresh tokens
117             $self->install(format_no_params => 1);
118              
119             if ($self->is_auth_server) {
120             $self->uses(v_id_suggest => 'random');
121             my ( $v_id_next, $vtable_insert) = $self->uses_all
122             (qw(v_id_next vtable_insert));
123              
124             # Enforce requirements on v_id_next.
125             # Since, for this token format, v_ids are used directly,
126             # they MUST NOT be predictable.
127             $self->ensure(v_id_is_random => 1,
128             'bearer_handle tokens must use random identifiers');
129              
130             my $token_type = ($self->is_access ? $self->uses('token_type') : ());
131             $self->install( token_create => sub {
132             my ($now, $expires_in, @bindings) = @_;
133             my $v_id = $v_id_next->();
134             my $error = $vtable_insert->($v_id, $expires_in + $now, $now, @bindings);
135             return ($error,
136             ($error ? () :
137             (encode_base64url($v_id),
138             ($token_type ? (token_type => $token_type) : ()),
139             )));
140             });
141             }
142              
143             if ($self->is_resource_server) {
144             # handle token has no @payload
145             $self->install( token_parse => sub {
146             return (decode_base64url($_[0]));
147             });
148             $self->install( token_finish => sub {
149             my ($v) = @_; # ($validator, @payload)
150             return ('unrecognized')
151             unless my ($expiration, $issuance, @bindings) = @$v;
152             return (undef, $issuance, $expiration - $issuance, @bindings);
153             });
154             }
155             return $self;
156             }
157              
158              
159             # IMPLEMENTATION format_bearer_signed FOR format
160             # (bearer_signed_)hmac
161             # (bearer_signed_)nonce_length [=hmac length/2]
162             # (bearer_signed_)fixed
163             # SUMMARY
164             # Bearer token, signed-assertion-style
165             # REQUIRES
166             # current_secret
167             # random
168             #
169             # Access_token value contains a key identifying a shared secret
170             # (and possibly also the authserver and the resource), a set
171             # of values specifying expiration and scope, and a HMAC value to sign
172             # everything. Only the shared secret needs to be separately
173             # communicated.
174              
175             Default_Value bearer_signed_hmac => 'hmac_sha224';
176             Default_Value bearer_signed_fixed => [];
177              
178             sub pkg_format_bearer_signed {
179             my __PACKAGE__ $self = shift;
180             $self->parameter_prefix(bearer_signed_ => @_);
181              
182             # yes, we can use this for authcodes and refresh tokens
183             $self->install(format_no_params => 1);
184              
185             if ($self->is_auth_server) {
186             my $hmac = $self->uses('bearer_signed_hmac');
187             my ($hlen,undef) = hmac_name_to_len_fn($hmac)
188             or $self->croak("unknown/unavailable hmac function: $hmac");
189             my $nonce_len = $self->uses(bearer_signed_nonce_length => $hlen/2);
190              
191             $self->uses(current_secret_length => $hlen);
192             $self->uses(current_secret_payload => $self->uses('bearer_signed_fixed'));
193              
194             my $secret = $self->uses('current_secret');
195             my $auto_rekey_check = $self->uses('current_secret_rekey_check');
196             my $random = $self->uses('random');
197              
198             my $token_type = ($self->is_access ? $self->uses('token_type') : ());
199              
200             $self->install( token_create => sub {
201             my ($now, $expires_in, @bindings) = @_;
202             my ($error) = $auto_rekey_check->($now);
203             return (rekey_failed => $error)
204             if $error;
205              
206             my ($v_id, $v_secret, undef, @fixed) = @{$secret};
207             for my $f (@fixed) {
208             my $given = shift @bindings;
209             return (fixed_parameter_mismatch => $f,$given)
210             if $f ne $given;
211             }
212             my $nonce = $random->($nonce_len);
213             return (undef,
214             encode_base64url(pack 'w/a*a*', $v_id,
215             sign_binary($v_secret,
216             pack('w/a*ww(w/a*)*', $nonce,
217             $now, $expires_in,
218             @bindings),
219             hmac => $hmac,
220             extra => $v_id)),
221             ($token_type ? (token_type => $token_type) : ()),
222             );
223             });
224             }
225             if ($self->is_resource_server) {
226             # On the resource side we cannot use 'current_secret'
227             # since token may have been created with a previous secret,
228             # so we just have to take whatever we get from the vtable
229             $self->install( token_parse => sub {
230             my ($token) = @_; # bearer token, no additional attributes
231             my ($v_id, $bin) = unpack 'w/a*a*', decode_base64url($token);
232             return ($v_id, $v_id, $bin)
233             });
234             $self->install( token_finish => sub {
235             my ($validator, $v_id, $bin) = @_;
236             my (undef, undef, $v_secret, @fixed) = @$validator;
237             my ($payload, $error) = unsign_binary($v_secret, $bin, $v_id);
238             return ($error) if $error;
239             my ($now, $expires_in, @bindings) = unpack 'w/xww(w/a*)*', $payload;
240             return (undef, $now, $expires_in, @fixed, @bindings);
241             });
242             }
243             return $self;
244             }
245              
246              
247             1;
248              
249              
250             __END__