File Coverage

blib/lib/WebService/Windows/LiveID/Auth.pm
Criterion Covered Total %
statement 95 98 96.9
branch 18 26 69.2
condition 14 26 53.8
subroutine 21 23 91.3
pod 8 8 100.0
total 156 181 86.1


line stmt bran cond sub pod time code
1             package WebService::Windows::LiveID::Auth;
2              
3 6     6   76376 use strict;
  6         19  
  6         521  
4 6     6   33 use warnings;
  6         14  
  6         190  
5              
6 6     6   33 use base qw(Class::Accessor::Fast);
  6         12  
  6         18411  
7              
8 6     6   30052 use Carp::Clan qw(croak);
  6         21270  
  6         255  
9 6     6   26674 use CGI;
  6         101995  
  6         1293  
10 6     6   10275 use Crypt::Rijndael;
  6         5367  
  6         189  
11 6     6   7328 use Digest::SHA ();
  6         27431  
  6         172  
12 6     6   5760 use MIME::Base64 ();
  6         5019  
  6         172  
13 6     6   2229 use URI;
  6         11177  
  6         149  
14 6     6   3075 use URI::QueryParam;
  6         1452  
  6         155  
15 6     6   36 use URI::Escape ();
  6         18  
  6         139  
16              
17 6     6   4016 use WebService::Windows::LiveID::Auth::User;
  6         22  
  6         95  
18              
19             __PACKAGE__->mk_accessors(qw/
20             appid
21             algorithm
22             _secret_key
23             _crypt_key
24             _sign_key
25             /);
26              
27             my $control_url = 'http://login.live.com/controls/WebAuth.htm';
28             my $sign_in_url = 'http://login.live.com/wlogin.srf';
29             my $sign_out_url = 'http://login.live.com/logout.srf';
30              
31             =head1 NAME
32              
33             WebService::Windows::LiveID::Auth - Perl implementation of Windows Live ID Web Authentication 1.0
34              
35             =head1 VERSION
36              
37             version 0.01
38              
39             =cut
40              
41             our $VERSION = '0.01';
42              
43             =head1 SYNOPSIS
44              
45             use WebService::Windows::LiveID::Auth;
46              
47             my $appid = '00163FFF80003203';
48             my $secret_key = 'ApplicationKey123';
49             my $appctx = 'zigorou';
50              
51             my $auth = WebService::Windows::LiveID::Auth->new({
52             appid => $appid,
53             secret_key => $secret_key
54             });
55              
56             local $\ = "\n";
57              
58             print $auth->control_url; ### SignIn, SignOut links page by LiveID. Set this page url to iframe's src attribute.
59             print $auth->sign_in_url; ### SignIn page
60             print $auth->sign_out_url; ### SignOut page
61              
62             In the request to "ReturnURL",
63              
64             use CGI;
65             use WebService::Windows::LiveID::Auth;
66              
67             my $q = CGI->new;
68              
69             my $appid = '00163FFF80003203';
70             my $secret_key = 'ApplicationKey123';
71             my $appctx = 'zigorou';
72              
73             my $auth = WebService::Windows::LiveID::Auth->new({
74             appid => $appid,
75             secret_key => $secret_key
76             });
77              
78             my $user = eval { $auth->process_token($q->param("stoken"), $appctx); };
79             print $q->header;
80              
81             unless ($@) {
82             print "

Login sucsess.

\n";
83             print "

uid: " . $user->uid . "

";
84             }
85             else {
86             print "

Login failed.

";
87             }
88              
89             =head1 METHODS
90              
91             =head2 new($arguments)
92              
93             Constructor.
94             $arguments must be HASH reference.
95              
96             ## Constructor parameter sample.
97             $arguments = {
98             appid => '00163FFF80003203', ## required
99             secret_key => 'ApplicationKey123', ## required
100             algorithm => 'wsignin1.0' ## optional
101             };
102              
103             =cut
104              
105             sub new {
106 6     6 1 26518 my ($class, $arguments) = @_;
107              
108 6   50     58 $arguments->{algorithm} ||= 'wsignin1.0';
109              
110 6         13 my $args = {};
111              
112 6         20 for my $prop (qw/appid secret_key algorithm/) {
113 16 100 66     92 if (exists $arguments->{$prop} && $arguments->{$prop}) {
114 15         42 $args->{$prop} = $arguments->{$prop};
115             }
116             else {
117 1         8 croak(qq|$prop is required parameter|);
118             }
119             }
120              
121 5         74 my $self = $class->SUPER::new($args);
122 5         89 $self->secret_key($args->{secret_key});
123              
124 5         82 return $self;
125             }
126              
127             =head2 process_token($stoken, $appctx)
128              
129             Process and validate stoken value.
130             If the authentication is sucsess, then this method will return L object.
131             On fail, return undef value.
132              
133             =cut
134              
135             sub process_token {
136 2     2 1 49 my ($self, $stoken, $appctx) = @_;
137              
138 2 50       11 croak('stoken parameter is required') unless ($stoken);
139              
140 2         6 $stoken = $self->_uud64($stoken);
141              
142 2 100 33     71 croak('Invalid stoken value') if (!$stoken || (length $stoken) <= 16 || (length $stoken) % 16 != 0);
      66        
143              
144 1         2 my $iv = substr($stoken, 0, 16);
145 1         2 my $crypted = substr($stoken, 16);
146              
147 1 50 33     6 croak('Invalid iv or crypted value') unless ($iv && $crypted);
148              
149 1         4 my $cipher = Crypt::Rijndael->new($self->_crypt_key, Crypt::Rijndael::MODE_CBC);
150 1         20 $cipher->set_iv($iv);
151              
152 1         9 my $token = $cipher->decrypt($crypted);
153 1         5 my ($body, $sig) = split(/&sig=/, $token);
154              
155 1 50 33     8 croak('Failed to decrypt token') unless ($body && $sig);
156 1 50       4 croak('Invalid signature') if (Digest::SHA::hmac_sha256($body, $self->_sign_key) ne $self->_uud64($sig));
157              
158 1         20 my $query = CGI->new($token);
159              
160 1         3643 return WebService::Windows::LiveID::Auth::User->new({$query->Vars});
161             }
162              
163             =head2 control_url([$query])
164              
165             Return control url as L object.
166             $query parameter is optional, It must be HASH reference.
167              
168             ## query parameter sample
169             $query = {
170             appctx => "zigorou",
171             style => "font-family: Times Roman;"
172             };
173              
174             Or
175              
176             $query = {
177             appctx => "zigorou",
178             style => {
179             "font-family" => "Verdana",
180             "color" => "Grey"
181             }
182             }
183              
184             The "style" property allows SCALAR and HASH reference.
185              
186             =cut
187              
188             sub control_url {
189 4     4 1 2013 my ($self, $query) = @_;
190 4         16 my $control_url = URI->new($control_url);
191              
192 4         211 $control_url->query_param('appid', $self->appid);
193 4         503 $control_url->query_param('alg', $self->algorithm);
194              
195 4 100 66     451 if ($query && ref $query eq 'HASH') {
196 3 50       17 $control_url->query_param('appctx', $query->{appctx}) if ($query->{appctx});
197 3 100       413 if ($query->{style}) {
198 2 100       13 $query->{style} = $self->_style_to_string($query->{style}) if (ref $query->{style} eq "HASH");
199 2         8 $control_url->query_param('style', $query->{style});
200             }
201             }
202              
203 4         552 return $control_url;
204             }
205              
206             =head2 sign_in_url([$query])
207              
208             Return sign-in url as L object.
209             $query parameter is optional, It must be HASH reference.
210              
211             ## query parameter sample
212             $query = {
213             appctx => "zigorou"
214             };
215              
216             =cut
217              
218             sub sign_in_url {
219 2     2 1 895 my ($self, $query) = @_;
220 2         10 my $sign_in_url = URI->new($sign_in_url);
221              
222 2         150 $sign_in_url->query_param('appid', $self->appid);
223 2         218 $sign_in_url->query_param('alg', $self->algorithm);
224 2 50 66     287 $sign_in_url->query_param('appctx', $query->{appctx}) if ($query && ref $query eq 'HASH' && $query->{appctx});
      66        
225              
226 2         180 return $sign_in_url;
227             }
228              
229             =head2 sign_out_url()
230              
231             Return sign-out url as L object.
232              
233             =cut
234              
235             sub sign_out_url {
236 1     1 1 329 my $self = shift;
237              
238 1         6 my $sign_out_url = URI->new($sign_out_url);
239 1         64 $sign_out_url->query_param('appid', $self->appid);
240              
241 1         93 return $sign_out_url;
242             }
243              
244             =head2 appid([$appid])
245              
246             Application ID
247              
248             =head2 algorithm([$algorithm])
249              
250             Algorithm name
251              
252             =head2 secret_key([$secret_key])
253              
254             Secret key
255              
256             =cut
257              
258             sub secret_key {
259 5     5 1 13 my ($self, $secret_key) = @_;
260              
261 5 50       23 if ($secret_key) {
262 5         36 $self->_secret_key($secret_key);
263 5         92 $self->_sign_key($self->_derive_key("SIGNATURE"));
264 5         221 $self->_crypt_key($self->_derive_key("ENCRYPTION"));
265             }
266             else {
267 0         0 return $self->_secret_key;
268             }
269             }
270              
271             =head2 sign_key()
272              
273             Signature key.
274              
275             =cut
276              
277 0     0 1 0 sub sign_key { shift->_sign_key; }
278              
279             =head2 crypt_key()
280              
281             Encryption key
282              
283             =cut
284              
285 0     0 1 0 sub crypt_key { shift->_crypt_key; }
286              
287             ###
288             ### private methods
289             ###
290              
291             sub _derive_key {
292 10     10   18 my ($self, $prefix) = @_;
293 10         32 return substr(Digest::SHA::sha256($prefix . $self->_secret_key), 0, 16);
294             }
295              
296             sub _style_to_string {
297 1     1   2 my ($self, $props) = @_;
298 1         3 my @allow_props = qw(font-family font-weight font-style font-size color background);
299              
300 6         15 return join(" ",
301 6 50       25 map { join(": ", $_, $props->{$_}) . ";" }
302 1         3 grep { exists $props->{$_} && $props->{$_} }
303             @allow_props
304             );
305             }
306              
307             sub _uud64 {
308 3     3   18 my ($self, $strings) = @_;
309 3         9 return MIME::Base64::decode_base64(URI::Escape::uri_unescape($strings));
310             }
311              
312             =head1 SEE ALSO
313              
314             =over 4
315              
316             =item http://go.microsoft.com/fwlink/?linkid=92886
317              
318             =item http://msdn2.microsoft.com/en-us/library/bb676626.aspx
319              
320             =item http://dev.live.com/blogs/liveid/archive/2006/05/18/8.aspx
321              
322             =item http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=646&SiteID=1
323              
324             =item http://www.microsoft.com/downloads/details.aspx?FamilyId=8BA187E5-3630-437D-AFDF-59AB699A483D&displaylang=en
325              
326             =item http://msdn2.microsoft.com/en-us/library/bb288408.aspx
327              
328             =item L
329              
330             =item L
331              
332             =item L
333              
334             =item L
335              
336             =back
337              
338             =head1 AUTHOR
339              
340             Toru Yamaguchi, C<< >>
341              
342             =head1 BUGS
343              
344             Please report any bugs or feature requests to
345             C, or through the web interface at
346             L. I will be notified, and then you'll automatically be
347             notified of progress on your bug as I make changes.
348              
349             =head1 COPYRIGHT & LICENSE
350              
351             Copyright 2007 Toru Yamaguchi, All Rights Reserved.
352              
353             This program is free software; you can redistribute it and/or modify it
354             under the same terms as Perl itself.
355              
356             =cut
357              
358             1; # End of WebService::Windows::LiveID::Auth