root/branches/release-26/lib/MT/Auth/OpenID.pm @ 1174

Revision 1174, 10.9 kB (checked in by bchoate, 23 months ago)

Updated copyright year for source.

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