root/branches/release-35/lib/MT/Auth/OpenID.pm @ 1914

Revision 1914, 12.2 kB (checked in by fumiakiy, 20 months ago)

Condition when to download FOAF/Atom and userpic image from OpenID OP by date. This should speed up logging in of OpenID commenter by 1 - 2 seconds. BugId:67909

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Auth::OpenID;
8use strict;
9
10use MT::Util qw( decode_url is_valid_email escape_unicode ts2epoch );
11use MT::I18N qw( encode_text );
12
13sub login {
14    my $class = shift;
15    my ($app) = @_;
16    my $q = $app->{query};
17    return $app->errtrans("Invalid request.")
18        unless $q->param('blog_id');
19    my $blog = MT::Blog->load(scalar $q->param('blog_id'));
20    my %param = $app->param_hash;
21    my $csr = _get_csr(\%param, $blog) or return;
22    my $identity = $q->param('openid_url');
23    if (!$identity &&
24        (my $u = $q->param('openid_userid')) && $class->can('url_for_userid')) {
25        $identity = $class->url_for_userid($u);
26    }
27    my $claimed_identity = $csr->claimed_identity($identity);
28    if (!$claimed_identity) {
29        my ($err_code, $err_msg) = ($csr->errcode, $csr->errtext);
30        if ($err_code eq 'no_head_tag' || $err_code eq 'no_identity_server' || $err_code eq 'url_gone') {
31            $err_msg = $app->translate('The address entered does not appear to be an OpenID');
32        }
33        elsif ($err_code eq 'empty_url' || $err_code eq 'bogus_url') {
34            $err_msg = $app->translate('The text entered does not appear to be a web address');
35        }
36        elsif ($err_code eq 'url_fetch_error') {
37            $err_msg =~ s{ \A Error \s fetching \s URL: \s }{}xms;
38            $err_msg = $app->translate('Unable to connect to [_1]: [_2]', $identity, $err_msg);
39        }
40        return $app->error($app->translate("Could not verify the OpenID provided: [_1]", $err_msg));
41    }
42
43    my $root = $class->_get_root($blog);
44    my $return_to = $app->base . $app->uri . '?__mode=handle_sign_in'
45        . '&blog_id=' . $q->param('blog_id')
46        . '&static=' . $q->param('static')
47        . '&key=' . $q->param('key');
48    $return_to .= '&entry_id=' . $q->param('entry_id') if $q->param('entry_id');
49
50    my $check_url = $claimed_identity->check_url(
51        return_to => $return_to,
52        trust_root => $root,
53    );
54
55    return $app->redirect($check_url);
56}
57
58sub handle_sign_in {
59    my $class = shift;
60    my ($app, $auth_type) = @_;
61    my $q = $app->{query};
62    my $INTERVAL = 60 * 60 * 24 * 7;
63
64    $auth_type ||= 'OpenID';
65
66    my $blog = MT::Blog->load($q->param('blog_id'));
67
68    my $cmntr;
69    my $session;
70
71    my %param = $app->param_hash;
72    my $csr = _get_csr(\%param, $blog) or return 0;
73
74    if(my $setup_url = $csr->user_setup_url( post_grant => 'return' )) {
75        return $app->redirect($setup_url);
76    } elsif(my $vident = $csr->verified_identity) {
77        my $name = $vident->url;
78        $cmntr = $app->model('author')->load(
79            {
80                name => $name,
81                type => MT::Author::COMMENTER(),
82                auth_type => $auth_type,
83            }
84        );
85        my $nick;
86        if ( $cmntr ) {
87            if ( ( $cmntr->modified_on
88                && ( ts2epoch($blog, $cmntr->modified_on) > time - $INTERVAL ) )
89              || ( $cmntr->created_on
90                && ( ts2epoch($blog, $cmntr->created_on) > time - $INTERVAL ) ) )
91            {
92                $nick = $cmntr->nickname;
93            }
94            else {
95                $nick = $class->get_nickname($vident);
96                $cmntr->nickname($nick);
97                $cmntr->save or return 0;
98            }
99        }
100        else {
101            $nick = $class->get_nickname($vident);
102            $cmntr = $app->_make_commenter(
103                email       => q(),
104                nickname    => $nick,
105                name        => $name,
106                url         => $vident->url,
107                auth_type   => $auth_type,
108                external_id => _url_hash($vident->url),
109            );
110        }
111        return 0 unless $cmntr;
112
113        $nick = $name unless $nick;
114
115        # Signature was valid, so create a session, etc.
116        my $enc = $app->{cfg}->PublishCharset || '';
117        my $nick_escaped = escape_unicode($nick);
118        $nick = encode_text($nick, 'utf-8', undef);
119        $session = $app->_make_commenter_session($app->make_magic_token, q(),
120                                                 $name, $nick_escaped, undef, $name);
121        unless ($session) {
122            $app->error($app->errstr() || $app->translate("Couldn't save the session"));
123            return 0;
124        }
125
126        if (my $userpic = $cmntr->userpic) {
127            my @stat = stat($userpic->file_path());
128            my $mtime = $stat[9];
129            if ( $mtime > time - $INTERVAL ) {
130                # newer than 7 days ago, don't download the userpic
131                return $cmntr;
132            }
133        }
134
135        if ( my $userpic = $class->get_userpicasset($vident) ) {
136            $userpic->tags('@userpic');
137            $userpic->created_by($cmntr->id);
138            $userpic->save;
139            if (my $userpic = $cmntr->userpic) {
140                # Remove the old userpic thumb so the new userpic's will be generated
141                # in its place.
142                my $thumb_file = $cmntr->userpic_file();
143                my $fmgr = MT::FileMgr->new('Local');
144                if ($fmgr->exists($thumb_file)) {
145                    $fmgr->delete($thumb_file);
146                }
147
148                $userpic->remove;
149            }
150            $cmntr->userpic_asset_id($userpic->id);
151            $cmntr->save;
152        }
153    } else {
154        # If there's no signature, then we trust the cookie.
155        my %cookies = $app->cookies();
156        my $cookie_name = MT::App::COMMENTER_COOKIE_NAME();
157        if ($cookies{$cookie_name}
158            && ($session = $cookies{$cookie_name}->value())) 
159        {
160            require MT::Session;
161            require MT::Author;
162            my $sess = MT::Session->load({id => $session});
163            if ($sess) {
164                $cmntr = MT::Author->load({name => $sess->name,
165                                           type => MT::Author::COMMENTER(),
166                                           auth_type => $auth_type});
167            }
168        }
169    }
170    unless ($cmntr) {
171        return 0;
172    }
173    return $cmntr;
174}
175
176sub _get_ua {
177    return MT->new_ua( { paranoid => 1 } );
178}
179
180sub _get_csr {
181    my ($params, $blog) = @_;
182    my $secret = MT->config->SecretToken;
183    my $ua = _get_ua() or return;
184    require Net::OpenID::Consumer;
185    Net::OpenID::Consumer->new(
186        ua => $ua,
187        args => $params,
188        consumer_secret => $secret,
189    );
190}
191
192sub _get_declared_foaf {
193    my ($vident) = @_;
194    my $req      = MT::Request->instance();
195    my $foaf     = $req->stash( 'foaf:' . _url_hash($vident->url) );
196    return $foaf if $foaf;
197
198    my $ua = _get_ua() or return '';
199
200    if ( my $foaf_url = $vident->declared_foaf ) {
201        my $resp = $ua->get($foaf_url);
202        if ( $resp->is_success ) {
203            $foaf = $resp->content;
204            $req->stash( 'foaf:' . _url_hash($vident->url), $foaf );
205            return $foaf;
206        }
207    }
208
209    q();
210}
211
212sub get_nickname {
213    my $class = shift;
214    my ($vident) = @_;
215    _get_nickname(@_);
216}
217
218sub _get_nickname {
219    my ($vident) = @_;
220
221    ## FOAF
222    if ( my $foaf = _get_declared_foaf($vident) ) {
223        my $name;
224
225        require XML::XPath;
226        my $xml = XML::XPath->new( xml => $foaf );
227        $xml->set_namespace('RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
228        $xml->set_namespace('FOAF', 'http://xmlns.com/foaf/0.1/');
229        my ($name_el) = $xml->findnodes('/RDF:RDF/FOAF:Person/FOAF:name');
230        ($name_el) = $xml->findnodes('/RDF:RDF/FOAF:Person/FOAF:nick')
231            unless $name_el;
232        if ($name_el)
233        {
234            $name = $name_el->string_value;
235        }
236        $xml->cleanup;
237
238        return MT::I18N::utf8_off($name) if $name;
239    }
240
241    ## Atom
242    if(my $atom_url = $vident->declared_atom) {
243        if (my $ua = _get_ua()) {
244            my $resp = $ua->get($atom_url);
245            if($resp->is_success) {
246                my $name;
247
248                require XML::XPath;
249                my $xml = XML::XPath->new( xml => $resp->content );
250                if(my ($name_el) = $xml->findnodes('/feed/author/name')) {
251                    $name = $name_el->string_value;
252                }
253                $xml->cleanup;
254           
255                return MT::I18N::utf8_off($name) if $name;
256            }
257        }
258    }
259
260    return $vident->display ? $vident->display : $vident->url;
261}
262
263sub get_userpicasset {
264    my $class = shift;
265    my ($vident) = @_;
266    my $foaf = _get_declared_foaf($vident);
267    return undef unless $foaf;
268
269    require XML::XPath;
270    my $xml = XML::XPath->new( xml => $foaf );
271    $xml->set_namespace('RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
272    $xml->set_namespace('FOAF', 'http://xmlns.com/foaf/0.1/');
273    my $resource = $xml->getNodeText('/RDF:RDF/FOAF:Person/FOAF:img/@RDF:resource');
274    my $url;
275    if ($resource) {
276        $url = $resource->value();
277    }
278    $xml->cleanup;
279    return undef unless $url;
280
281    return _asset_from_url($url);
282}
283
284sub _asset_from_url {
285    my ($image_url) = @_;
286    my $ua   = _get_ua() or return;
287    my $resp = $ua->get($image_url);
288    return undef unless $resp->is_success;
289    my $image = $resp->content;
290    return undef unless $image;
291    my $mimetype = $resp->header('Content-Type');
292    my $def_ext = {
293        'image/jpeg' => '.jpg',
294        'image/png'  => '.png',
295        'image/gif'  => '.gif'}->{$mimetype};
296
297    require Image::Size;
298    my ( $w, $h, $id ) = Image::Size::imgsize(\$image);
299
300    require MT::FileMgr;
301    my $fmgr = MT::FileMgr->new('Local');
302
303    my $save_path  = '%s/support/uploads/';
304    my $local_path =
305      File::Spec->catdir( MT->instance->static_file_path, 'support', 'uploads' );
306    $local_path =~ s|/$||
307      unless $local_path eq '/';    ## OS X doesn't like / at the end in mkdir().
308    unless ( $fmgr->exists($local_path) ) {
309        $fmgr->mkpath($local_path);
310    }
311    my $filename = substr($image_url, rindex($image_url, '/'));
312    if ( $filename =~ m!\.\.|\0|\|! ) {
313        return undef;
314    }
315    my ($base, $uploaded_path, $ext) = File::Basename::fileparse($filename, '\.[^\.]*');
316    $ext = $def_ext if $def_ext;  # trust content type higher than extension
317
318    # Find unique name for the file.
319    my $i = 1;
320    my $base_copy = $base;
321    while ($fmgr->exists(File::Spec->catfile($local_path, $base . $ext))) {
322        $base = $base_copy . '_' . $i++;
323    }
324
325    my $local_relative = File::Spec->catfile($save_path, $base . $ext);
326    my $local = File::Spec->catfile($local_path, $base . $ext);
327    $fmgr->put_data( $image, $local, 'upload' );
328
329    require MT::Asset;
330    my $asset_pkg = MT::Asset->handler_for_file($local);
331    return undef if $asset_pkg ne 'MT::Asset::Image';
332
333    my $asset;
334    $asset = $asset_pkg->new();
335    $asset->file_path($local_relative);
336    $asset->file_name($base.$ext);
337    my $ext_copy = $ext;
338    $ext_copy =~ s/\.//;
339    $asset->file_ext($ext_copy);
340    $asset->blog_id(0);
341
342    my $original = $asset->clone;
343    my $url = $local_relative;
344    $url  =~ s!\\!/!g;
345    $asset->url($url);
346    $asset->image_width($w);
347    $asset->image_height($h);
348    $asset->mime_type($mimetype);
349
350    $asset->save
351        or return undef;
352
353    MT->run_callbacks(
354        'api_upload_file.' . $asset->class,
355        File => $local, file => $local,
356        Url => $url, url => $url,
357        Size => length($image), size => length($image),
358        Asset => $asset, asset => $asset,
359        Type => $asset->class, type => $asset->class,
360    );
361    MT->run_callbacks(
362        'api_upload_image',
363        File => $local, file => $local,
364        Url => $url, url => $url,
365        Size => length($image), size => length($image),
366        Asset => $asset, asset => $asset,
367        Height => $h, height => $h,
368        Width => $w, width => $w,
369        Type => 'image', type => 'image',
370        ImageType => $id, image_type => $id,
371    );
372
373    $asset;
374}
375
376sub _url_hash {
377    my ($url) = @_;
378
379    if (eval { require Digest::MD5; 1; }) {
380        return Digest::MD5::md5_hex($url);
381    }
382    return substr $url, 0, 255;
383}
384
385sub _get_root {
386    my $class = shift;
387    my ($blog) = @_;
388    my $path = MT->config->CGIPath;
389    if ($path =~ m!^/!) {
390        # relative path, prepend blog domain
391        my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|;
392        $path = $blog_domain . $path;
393    }
394    $path .= '/' unless $path =~ m!/$!;
395    $path;
396}
397
3981;
Note: See TracBrowser for help on using the browser.