File Coverage

blib/lib/WWW/Live/Auth.pm
Criterion Covered Total %
statement 22 89 24.7
branch 2 34 5.8
condition 3 39 7.6
subroutine 5 11 45.4
pod 6 7 85.7
total 38 180 21.1


line stmt bran cond sub pod time code
1             package WWW::Live::Auth;
2              
3 1     1   859 use strict;
  1         1  
  1         37  
4 1     1   5 use warnings;
  1         1  
  1         27  
5              
6 1     1   501 use WWW::Live::Auth::Utils;
  1         2  
  1         155  
7 1     1   8 use Carp;
  1         2  
  1         1404  
8              
9             require WWW::Live::Auth::SecretKey;
10             require WWW::Live::Auth::ApplicationToken;
11             require WWW::Live::Auth::ConsentToken;
12             require LWP::UserAgent;
13             require Crypt::SSLeay; # explicitly require, otherwise you get cryptic https failures with LWP
14             require CGI;
15              
16             our $VERSION = '1.0.0';
17             our $CONSENT_BASE_URL = 'https://consent.live.com/';
18              
19             sub new {
20 1     1 1 719 my ( $proto, %options ) = @_;
21 1   33     11 my $class = ref $proto || $proto;
22              
23 1         3 my $app_id = delete $options{'application_id'};
24 1         2 my $secret_key = delete $options{'secret_key'};
25 1         3 my $client_ip = delete $options{'client_ip'};
26 1 50 33     7 if ( $secret_key && !ref $secret_key ) {
27 0         0 $secret_key = WWW::Live::Auth::SecretKey->new( $secret_key );
28             }
29              
30 1   33     12 $options{'agent'} ||= __PACKAGE__ . "/$VERSION";
31 1         13 my $self = bless {
32             'secret_key' => $secret_key,
33             'application_id' => $app_id,
34             '_ua' => LWP::UserAgent->new( %options ),
35             'debug' => delete $options{'debug'},
36             }, $class;
37 1 50       3661 $self->{'client_ip'} = $client_ip if ( $client_ip );
38              
39 1         5 return $self;
40             }
41              
42             sub proxy {
43 0     0 1   my $self = shift;
44 0           return $self->{'_ua'}->proxy( 'https', shift );
45             }
46              
47             sub consent_url {
48 0     0 1   my ( $self, %args ) = @_;
49              
50 0   0       my $offers = $args{'offers'} || croak('List of offers is required');
51 0   0       my $privacy = $args{'privacy_url'} || croak('Privacy policy URL is required');
52 0   0       my $secret = $self->{'secret_key'} || croak('Secret key is required');
53 0   0       my $app_id = $self->{'application_id'} || croak('Application ID is required');
54              
55 0 0         if ( ref $offers ) {
56 0 0         if ( ref $offers ne 'ARRAY' ) {
57 0           $offers = [ $offers ];
58             }
59 0 0         $offers = join ',', map {
60 0           ref $_ ? $_->offer . '.' . $_->action : $_
61 0           } @{ $offers };
62             }
63              
64             # https://consent.live.com/Delegation.aspx?RU=...&ps=...&pl=...[&app=...][&mkt=...][&appctx=...]
65 0           my $url = sprintf $CONSENT_BASE_URL.'Delegation.aspx?ps=%s&pl=%s',
66             _escape( $offers ), _escape( $privacy );
67            
68 0 0         if ( $args{'return_url'} ) {
69 0           $url .= '&RU=' . _escape( $args{'return_url'} );
70             }
71              
72             # Client IP address is optional
73 0           my $app_token = WWW::Live::Auth::ApplicationToken->new(
74             $secret->signature_key,
75             $app_id,
76             $self->{'client_ip'}
77             )->as_string;
78 0           $url .= sprintf '&app=%s', $app_token;
79            
80 0 0         if ( $args{'market'} ) {
81 0           $url .= '&mkt=' . _escape( $args{'market'} );
82             }
83            
84 0 0         if ( $args{'context'} ) {
85 0           $url .= '&appctx=' . _escape( $args{'context'} );
86             }
87            
88 0           return $url;
89             }
90              
91             sub refresh_url {
92 0     0 1   my ( $self, %args ) = @_;
93 0   0       my $consent_token = $args{'consent_token'} || croak('Consent token is required to construct a refresh URL');
94 0   0       my $secret = $self->{'secret_key'} || croak('Secret key is required to construct a refresh URL');
95 0   0       my $app_id = $self->{'application_id'} || croak('Application ID is required to construct a refresh URL');
96            
97 0 0         if ( !ref $consent_token ) {
98 0           $consent_token = WWW::Live::Auth::ConsentToken->new(
99             'consent_token' => $consent_token,
100             'secret_key' => $secret,
101             );
102             }
103            
104 0           my $offers = join ',', map { $_->offer.'.'.$_->action } $consent_token->offers;
  0            
105            
106             # https://consent.live.com/RefreshToken.aspx?RU=...&ps=...&reft=...
107 0           my $url = sprintf $CONSENT_BASE_URL.'RefreshToken.aspx?ps=%s&reft=%s',
108             _escape( $offers ),
109             $consent_token->refresh_token;
110            
111 0 0         if ( $args{'return_url'} ) {
112 0           $url .= '&ru=' . _escape( $args{'return_url'} );
113             }
114              
115             # Client IP address is optional
116 0           my $app_token = WWW::Live::Auth::ApplicationToken->new(
117             $secret->signature_key,
118             $app_id,
119             $self->{'client_ip'}
120             )->as_string;
121 0           $url .= sprintf '&app=%s', $app_token;
122            
123 0           return $url;
124             }
125              
126             sub is_delegated_authentication {
127 0     0 0   my ( $self, $cgi ) = @_;
128 0   0       $cgi ||= CGI->new();
129              
130 0 0 0       if ( !$cgi->param('action') || $cgi->param('action') ne 'delauth' ) {
131 0           return 0;
132             }
133              
134 0           return 1;
135             }
136              
137             sub receive_consent {
138 0     0 1   my ( $self, $cgi ) = @_;
139 0   0       $cgi ||= CGI->new();
140              
141             # Check we are processing a delegated authentication response
142 0 0         if ( ! $self->is_delegated_authentication( $cgi ) ) {
    0          
143 0           croak('Unable to process consent - request is not a delegated authentication');
144             } elsif ( $cgi->param('ResponseCode') ne 'RequestApproved' ) {
145 0           croak('Authentication denied');
146             }
147            
148 0           my $consent_token = WWW::Live::Auth::ConsentToken->new(
149             'secret_key' => $self->{'secret_key'},
150             'consent_token' => $cgi->param('ConsentToken'),
151             );
152            
153 0           my $app_context = _unescape( $cgi->param('appctx') );
154            
155 0           return $consent_token, $app_context;
156             }
157              
158             sub refresh_consent {
159 0     0 1   my $self = shift;
160 0           my $url = $self->refresh_url( @_ );
161            
162 0 0         if ( $self->{'debug'} ) {
163 0           warn "About to GET $url";
164             }
165            
166 0           my $request = HTTP::Request->new(GET => $url);
167 0           my $response = $self->{'_ua'}->request( $request );
168 0 0         if ( $response->is_success ) {
169             # {"ConsentToken":"delt%3dEwCoARAn ..."}
170 0           my $raw = $response->content;
171            
172 0           my ($error, $msg) = $raw =~ m/"error":"(.+)"}(.+)/mxs;
173 0 0         if ( $error ) {
174 0           croak("Could not refresh consent token: $error - $msg");
175             }
176            
177 0           my ($consent_token) = $raw =~ m/"ConsentToken":"(.+)"/mxs;
178 0 0         if ( !$consent_token ) {
179 0           return;
180             }
181 0           return WWW::Live::Auth::ConsentToken->new(
182             'secret_key' => $self->{'secret_key'},
183             'consent_token' => $consent_token,
184             );
185            
186             } else {
187 0           croak( 'Could not contact Live service: ' . $response->status_line );
188             }
189             }
190              
191             1;
192             __END__