File Coverage

blib/lib/Net/OAuth2Server/Request.pm
Criterion Covered Total %
statement 21 111 18.9
branch 0 42 0.0
condition 0 31 0.0
subroutine 7 35 20.0
pod 0 28 0.0
total 28 247 11.3


line stmt bran cond sub pod time code
1 1     1   360 use strict; use warnings;
  1     1   2  
  1         23  
  1         4  
  1         2  
  1         46  
2              
3             package Net::OAuth2Server::Request;
4             our $VERSION = '0.006';
5              
6 1     1   362 use Net::OAuth2Server::Set ();
  1         2  
  1         19  
7 1     1   357 use Net::OAuth2Server::Response ();
  1         5  
  1         32  
8 1     1   660 use MIME::Base64 ();
  1         1664  
  1         41  
9 1     1   11 use Carp ();
  1         2  
  1         81  
10              
11 0     0 0   sub request_body_methods { 'POST' }
12       0 0   sub allowed_methods {}
13       0 0   sub accepted_auth {}
14       0 0   sub required_parameters {}
15 0     0 0   sub set_parameters { 'scope' }
16       0 0   sub confidential_parameters {}
17              
18 1     1   5 use Object::Tiny::Lvalue qw( method headers parameters confidential scope error );
  1         2  
  1         13  
19              
20             my $ct_rx = qr[ \A application/x-www-form-urlencoded [ \t]* (?: ; | \z ) ]xi;
21              
22             my $loaded;
23             sub from_psgi {
24 0     0 0   my ( $class, $env ) = ( shift, @_ );
25 0           my $body;
26 0   0       $body = do { $loaded ||= require Plack::Request; Plack::Request->new( $env )->content }
  0            
27             if ( $env->{'CONTENT_TYPE'} || '' ) =~ $ct_rx
28 0 0 0       and grep $env->{'REQUEST_METHOD'} eq $_, $class->request_body_methods;
      0        
29             $class->from(
30             $env->{'REQUEST_METHOD'},
31             $env->{'QUERY_STRING'},
32 0 0         { map /\A(?:HTTPS?_)?((?:(?!\A)|\ACONTENT_).*)/s ? ( "$1", $env->{ $_ } ) : (), keys %$env },
33             $body,
34             );
35             }
36              
37             my %auth_parser = ( # XXX not sure about this design...
38             Bearer => sub { [ access_token => $_[0] ] },
39             Basic => sub {
40             my @k = qw( client_id client_secret );
41             my @v = split /:/, MIME::Base64::decode( $_[0] ), 2;
42             [ map { ( shift @k, $_ ) x ( '' ne $_ ) } @v ];
43             },
44             );
45              
46             sub from {
47 0     0 0   my ( $class, $meth, $query, $hdr, $body ) = ( shift, @_ );
48              
49 0 0 0       Carp::croak 'missing request method' unless defined $meth and '' ne $meth;
50              
51 0 0         %$hdr = map { my $k = $_; y/-/_/; ( lc, $hdr->{ $k } ) } $hdr ? keys %$hdr : ();
  0            
  0            
  0            
52              
53 0 0         if ( grep $meth eq $_, $class->request_body_methods ) {
54             return $class->new( method => $meth, headers => $hdr )->set_error_invalid_request( 'bad content type' )
55 0 0 0       if ( $hdr->{'content_type'} || '' ) !~ $ct_rx;
56             } else {
57 0           undef $body;
58             }
59              
60 0           for ( $query, $body ) {
61 0 0         defined $_ ? y/+/ / : ( $_ = '' );
62             # parse to k/v pairs, ignoring empty pairs, ensuring both k&v are always defined
63 0           $_ = [ / \G (?!\z) [&;]* ([^=&;]*) =? ([^&;]*) (?: [&;]+ | \z) /xg ];
64 0           s/%([0-9A-Fa-f]{2})/chr hex $1/ge for @$_;
  0            
65             }
66              
67 0           my $auth = $class->accepted_auth;
68 0 0 0       if ( $auth and ( $hdr->{'authorization'} || '' ) =~ /\A\Q$auth\E +([^ ]+) *\z/ ) {
      0        
69 0 0         my $parser = $auth_parser{ $auth }
70             or Carp::croak "unsupported HTTP Auth type '$auth' requested in $class";
71 0           $auth = $parser->( "$1" );
72             }
73 0           else { $auth = [] }
74              
75 0           my ( %param, %visible, %dupe );
76 0           for my $list ( $auth, $body, $query ) {
77 0           while ( @$list ) {
78 0           my ( $name, $value ) = splice @$list, 0, 2;
79 0 0 0       if ( exists $param{ $name } and $value ne $param{ $name } ) {
80 0           $dupe{ $name } = 1;
81             }
82             else {
83 0           $param{ $name } = $value;
84 0 0         $visible{ $name } = 1 if $list == $query;
85             }
86             }
87             }
88              
89 0 0         if ( my @dupe = sort keys %dupe ) {
90 0           my $self = $class->new( method => $meth, headers => $hdr );
91 0           return $self->set_error_invalid_request( "duplicate parameter: @dupe" );
92             }
93              
94 0 0         while ( my ( $k, $v ) = each %param ) { delete $param{ $k } if '' eq $v }
  0            
95              
96 0           my %confidential = map +( $_, 1 ), grep !$visible{ $_ }, keys %param;
97              
98 0           $class->new(
99             method => $meth,
100             headers => $hdr,
101             parameters => \%param,
102             confidential => \%confidential,
103             );
104             }
105              
106             sub new {
107 0     0 0   my $class = shift;
108 0           my $self = bless { @_ }, $class;
109 0 0         $self->method or Carp::croak 'missing request method';
110 0   0       $self->confidential ||= {};
111 0   0       my $params = $self->parameters ||= {};
112 0   0       $self->$_ ||= Net::OAuth2Server::Set->new( $params->{ $_ } ) for $self->set_parameters;
113 0 0         $self->ensure_method( $self->allowed_methods ) or return $self;
114 0 0         $self->ensure_confidential( $self->confidential_parameters ) or return $self;
115 0 0         $self->ensure_required( $self->required_parameters ) or return $self;
116 0           $self;
117             }
118              
119             #######################################################################
120              
121             sub ensure_method {
122 0     0 0   my $self = shift;
123 0           my $meth = $self->method;
124 0           my $disallowed = not grep $meth eq $_, @_;
125 0 0         $self->set_error_invalid_request( "method not allowed: $meth" ) if $disallowed;
126 0           not $disallowed;
127             }
128              
129             sub ensure_required {
130 0     0 0   my $self = shift;
131 0           my $p = $self->parameters;
132 0           my @missing = sort grep !exists $p->{ $_ }, @_;
133 0 0         $self->set_error_invalid_request( "missing parameter: @missing" ) if @missing;
134 0           not @missing;
135             }
136              
137             sub ensure_confidential {
138 0     0 0   my $self = shift;
139 0           my $p = $self->parameters;
140 0           my $confidential = $self->confidential;
141 0   0       my @visible = sort grep exists $p->{ $_ } && !$confidential->{ $_ }, @_;
142 0 0         $self->set_error_invalid_request( "parameter not accepted in query string: @visible" ) if @visible;
143 0           not @visible;
144             }
145              
146             #######################################################################
147              
148 0     0 0   sub params { my $p = shift->parameters; @$p{ @_ } }
  0            
149 0     0 0   sub param { my $p = shift->parameters; $$p{ $_[0] } }
  0            
150 0     0 0   sub has_param { my $p = shift->parameters; exists $$p{ $_[0] } }
  0            
151             sub param_if_confidential {
152 0     0 0   my ( $self, $name ) = ( shift, @_ );
153 0 0         $self->confidential->{ $name } ? $self->parameters->{ $name } : ();
154             }
155              
156             #######################################################################
157              
158 0     0 0   sub set_error { my $self = shift; $self->error = Net::OAuth2Server::Response->new_error( @_ ); $self }
  0            
  0            
159 0     0 0   sub set_error_invalid_token { shift->set_error( invalid_token => @_ ) }
160 0     0 0   sub set_error_invalid_request { shift->set_error( invalid_request => @_ ) }
161 0     0 0   sub set_error_invalid_client { shift->set_error( invalid_client => @_ ) }
162 0     0 0   sub set_error_invalid_grant { shift->set_error( invalid_grant => @_ ) }
163 0     0 0   sub set_error_unauthorized_client { shift->set_error( unauthorized_client => @_ ) }
164 0     0 0   sub set_error_access_denied { shift->set_error( access_denied => @_ ) }
165 0     0 0   sub set_error_unsupported_response_type { shift->set_error( unsupported_response_type => @_ ) }
166 0     0 0   sub set_error_unsupported_grant_type { shift->set_error( unsupported_grant_type => @_ ) }
167 0     0 0   sub set_error_invalid_scope { shift->set_error( invalid_scope => @_ ) }
168 0     0 0   sub set_error_server_error { shift->set_error( server_error => @_ ) }
169 0     0 0   sub set_error_temporarily_unavailable { shift->set_error( temporarily_unavailable => @_ ) }
170              
171             1;