root/branches/release-34/lib/MT/Auth/OpenID.pm @ 1823

Revision 1823, 11.0 kB (checked in by takayama, 20 months ago)

Fixed BugId:67959
* Added check for result of object loading

  • 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 );
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
63    $auth_type ||= 'OpenID';
64
65    my $blog = MT::Blog->load($q->param('blog_id'));
66
67    my $cmntr;
68    my $session;
69
70    my %param = $app->param_hash;
71    my $csr = _get_csr(\%param, $blog) or return 0;
72
73    if(my $setup_url = $csr->user_setup_url( post_grant => 'return' )) {
74        return $app->redirect($setup_url);
75    } elsif(my $vident = $csr->verified_identity) {
76        my $name = $vident->url;
77        my $nick = $class->get_nickname($vident);
78
79        # Signature was valid, so create a session, etc.
80        my $enc = $app->{cfg}->PublishCharset || '';
81        my $nick_escaped = escape_unicode($nick);
82        $nick = encode_text($nick, 'utf-8', undef);
83        $session = $app->_make_commenter_session($app->make_magic_token, q(),
84                                                 $name, $nick_escaped, undef, $name);
85        unless ($session) {
86            $app->error($app->errstr() || $app->translate("Couldn't save the session"));
87            return 0;
88        }
89        $cmntr = $app->_make_commenter(
90            email       => q(),
91            nickname    => $nick,
92            name        => $name,
93            url         => $vident->url,
94            auth_type   => $auth_type,
95            external_id => _url_hash($vident->url),
96        );
97
98        if ( my $userpic = $class->get_userpicasset($vident) ) {
99            $userpic->tags('@userpic');
100            $userpic->created_by($cmntr->id);
101            $userpic->save;
102            if (my $userpic = $cmntr->userpic) {
103                # Remove the old userpic thumb so the new userpic's will be generated
104                # in its place.
105                my $thumb_file = $cmntr->userpic_file();
106                my $fmgr = MT::FileMgr->new('Local');
107                if ($fmgr->exists($thumb_file)) {
108                    $fmgr->delete($thumb_file);
109                }
110
111                $userpic->remove;
112            }
113            $cmntr->userpic_asset_id($userpic->id);
114            $cmntr->save;
115        }
116    } else {
117        # If there's no signature, then we trust the cookie.
118        my %cookies = $app->cookies();
119        my $cookie_name = MT::App::COMMENTER_COOKIE_NAME();
120        if ($cookies{$cookie_name}
121            && ($session = $cookies{$cookie_name}->value())) 
122        {
123            require MT::Session;
124            require MT::Author;
125            my $sess = MT::Session->load({id => $session});
126            if ($sess) {
127                $cmntr = MT::Author->load({name => $sess->name,
128                                           type => MT::Author::COMMENTER(),
129                                           auth_type => $auth_type});
130            }
131        }
132    }
133    unless ($cmntr) {
134        return 0;
135    }
136    return $cmntr;
137}
138
139sub _get_ua {
140    return MT->new_ua( { paranoid => 1 } );
141}
142
143sub _get_csr {
144    my ($params, $blog) = @_;
145    my $secret = MT->config->SecretToken;
146    my $ua = _get_ua() or return;
147    require Net::OpenID::Consumer;
148    Net::OpenID::Consumer->new(
149        ua => $ua,
150        args => $params,
151        consumer_secret => $secret,
152    );
153}
154
155sub _get_declared_foaf {
156    my ($vident) = @_;
157    my $req      = MT::Request->instance();
158    my $foaf     = $req->stash( 'foaf:' . _url_hash($vident->url) );
159    return $foaf if $foaf;
160
161    my $ua = _get_ua() or return '';
162
163    if ( my $foaf_url = $vident->declared_foaf ) {
164        my $resp = $ua->get($foaf_url);
165        if ( $resp->is_success ) {
166            $foaf = $resp->content;
167            $req->stash( 'foaf:' . _url_hash($vident->url), $foaf );
168            return $foaf;
169        }
170    }
171
172    q();
173}
174
175sub get_nickname {
176    my $class = shift;
177    my ($vident) = @_;
178    _get_nickname(@_);
179}
180
181sub _get_nickname {
182    my ($vident) = @_;
183
184    ## FOAF
185    if ( my $foaf = _get_declared_foaf($vident) ) {
186        my $name;
187
188        require XML::XPath;
189        my $xml = XML::XPath->new( xml => $foaf );
190        $xml->set_namespace('RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
191        $xml->set_namespace('FOAF', 'http://xmlns.com/foaf/0.1/');
192        my ($name_el) = $xml->findnodes('/RDF:RDF/FOAF:Person/FOAF:name');
193        ($name_el) = $xml->findnodes('/RDF:RDF/FOAF:Person/FOAF:nick')
194            unless $name_el;
195        if ($name_el)
196        {
197            $name = $name_el->string_value;
198        }
199        $xml->cleanup;
200
201        return MT::I18N::utf8_off($name) if $name;
202    }
203
204    ## Atom
205    if(my $atom_url = $vident->declared_atom) {
206        if (my $ua = _get_ua()) {
207            my $resp = $ua->get($atom_url);
208            if($resp->is_success) {
209                my $name;
210
211                require XML::XPath;
212                my $xml = XML::XPath->new( xml => $resp->content );
213                if(my ($name_el) = $xml->findnodes('/feed/author/name')) {
214                    $name = $name_el->string_value;
215                }
216                $xml->cleanup;
217           
218                return MT::I18N::utf8_off($name) if $name;
219            }
220        }
221    }
222
223    return $vident->display ? $vident->display : $vident->url;
224}
225
226sub get_userpicasset {
227    my $class = shift;
228    my ($vident) = @_;
229    my $foaf = _get_declared_foaf($vident);
230    return undef unless $foaf;
231
232    require XML::XPath;
233    my $xml = XML::XPath->new( xml => $foaf );
234    $xml->set_namespace('RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
235    $xml->set_namespace('FOAF', 'http://xmlns.com/foaf/0.1/');
236    my $resource = $xml->getNodeText('/RDF:RDF/FOAF:Person/FOAF:img/@RDF:resource');
237    my $url;
238    if ($resource) {
239        $url = $resource->value();
240    }
241    $xml->cleanup;
242    return undef unless $url;
243
244    return _asset_from_url($url);
245}
246
247sub _asset_from_url {
248    my ($image_url) = @_;
249    my $ua   = _get_ua() or return;
250    my $resp = $ua->get($image_url);
251    return undef unless $resp->is_success;
252    my $image = $resp->content;
253    return undef unless $image;
254    my $mimetype = $resp->header('Content-Type');
255    my $def_ext = {
256        'image/jpeg' => '.jpg',
257        'image/png'  => '.png',
258        'image/gif'  => '.gif'}->{$mimetype};
259
260    require Image::Size;
261    my ( $w, $h, $id ) = Image::Size::imgsize(\$image);
262
263    require MT::FileMgr;
264    my $fmgr = MT::FileMgr->new('Local');
265
266    my $save_path  = '%s/support/uploads/';
267    my $local_path =
268      File::Spec->catdir( MT->instance->static_file_path, 'support', 'uploads' );
269    $local_path =~ s|/$||
270      unless $local_path eq '/';    ## OS X doesn't like / at the end in mkdir().
271    unless ( $fmgr->exists($local_path) ) {
272        $fmgr->mkpath($local_path);
273    }
274    my $filename = substr($image_url, rindex($image_url, '/'));
275    if ( $filename =~ m!\.\.|\0|\|! ) {
276        return undef;
277    }
278    my ($base, $uploaded_path, $ext) = File::Basename::fileparse($filename, '\.[^\.]*');
279    $ext = $def_ext if $def_ext;  # trust content type higher than extension
280
281    # Find unique name for the file.
282    my $i = 1;
283    my $base_copy = $base;
284    while ($fmgr->exists(File::Spec->catfile($local_path, $base . $ext))) {
285        $base = $base_copy . '_' . $i++;
286    }
287
288    my $local_relative = File::Spec->catfile($save_path, $base . $ext);
289    my $local = File::Spec->catfile($local_path, $base . $ext);
290    $fmgr->put_data( $image, $local, 'upload' );
291
292    require MT::Asset;
293    my $asset_pkg = MT::Asset->handler_for_file($local);
294    return undef if $asset_pkg ne 'MT::Asset::Image';
295
296    my $asset;
297    $asset = $asset_pkg->new();
298    $asset->file_path($local_relative);
299    $asset->file_name($base.$ext);
300    my $ext_copy = $ext;
301    $ext_copy =~ s/\.//;
302    $asset->file_ext($ext_copy);
303    $asset->blog_id(0);
304
305    my $original = $asset->clone;
306    my $url = $local_relative;
307    $url  =~ s!\\!/!g;
308    $asset->url($url);
309    $asset->image_width($w);
310    $asset->image_height($h);
311    $asset->mime_type($mimetype);
312
313    $asset->save
314        or return undef;
315
316    MT->run_callbacks(
317        'api_upload_file.' . $asset->class,
318        File => $local, file => $local,
319        Url => $url, url => $url,
320        Size => length($image), size => length($image),
321        Asset => $asset, asset => $asset,
322        Type => $asset->class, type => $asset->class,
323    );
324    MT->run_callbacks(
325        'api_upload_image',
326        File => $local, file => $local,
327        Url => $url, url => $url,
328        Size => length($image), size => length($image),
329        Asset => $asset, asset => $asset,
330        Height => $h, height => $h,
331        Width => $w, width => $w,
332        Type => 'image', type => 'image',
333        ImageType => $id, image_type => $id,
334    );
335
336    $asset;
337}
338
339sub _url_hash {
340    my ($url) = @_;
341
342    if (eval { require Digest::MD5; 1; }) {
343        return Digest::MD5::md5_hex($url);
344    }
345    return substr $url, 0, 255;
346}
347
348sub _get_root {
349    my $class = shift;
350    my ($blog) = @_;
351    my $path = MT->config->CGIPath;
352    if ($path =~ m!^/!) {
353        # relative path, prepend blog domain
354        my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|;
355        $path = $blog_domain . $path;
356    }
357    $path .= '/' unless $path =~ m!/$!;
358    $path;
359}
360
3611;
Note: See TracBrowser for help on using the browser.