package Catalyst::Authentication::Credential::OpenID; use warnings; use strict; use base "Class::Accessor::Fast"; __PACKAGE__->mk_accessors(qw/ realm debug secret openid_field consumer_secret ua_class ua_args extension_args errors_are_fatal extensions trust_root flatten_extensions_into_user /); our $VERSION = "0.19"; use Net::OpenID::Consumer; use Catalyst::Exception (); sub new { my ( $class, $config, $c, $realm ) = @_; my $self = { %{ $config }, %{ $realm->{config} } }; bless $self, $class; # 2.0 spec says "SHOULD" be named "openid_identifier." $self->{openid_field} ||= "openid_identifier"; my $secret = $self->{consumer_secret} ||= join("+", __PACKAGE__, $VERSION, sort keys %{ $c->config } ); $secret = substr($secret,0,255) if length $secret > 255; $self->secret($secret); # If user has no preference we prefer L::PA b/c it can prevent DoS attacks. my $ua_class = $self->{ua_class} ||= eval "use LWPx::ParanoidAgent" ? "LWPx::ParanoidAgent" : "LWP::UserAgent"; my $agent_class = $self->ua_class; eval "require $agent_class" or Catalyst::Exception->throw("Could not 'require' user agent class " . $self->ua_class); $c->log->debug("Setting consumer secret: " . $secret) if $self->debug; return $self; } sub authenticate { my ( $self, $c, $realm, $authinfo ) = @_; $c->log->debug("authenticate() called from " . $c->request->uri) if $self->debug; my $field = $self->openid_field; my $claimed_uri = $authinfo->{ $field }; # Its security related so we want to be explicit about GET/POST param retrieval. $claimed_uri ||= $c->req->method eq 'GET' ? $c->req->query_params->{ $field } : $c->req->body_params->{ $field }; my $csr = Net::OpenID::Consumer->new( ua => $self->ua_class->new(%{$self->ua_args || {}}), args => $c->req->params, consumer_secret => $self->secret, ); if ( $self->extension_args ) { $c->log->warn("The configuration key 'extension_args' is ignored; use 'extensions'"); } my %extensions = ref($self->extensions) eq "HASH" ? %{ $self->extensions } : ref($self->extensions) eq "ARRAY" ? @{ $self->extensions } : (); if ( $claimed_uri ) { my $current = $c->uri_for("/" . $c->req->path); # clear query/fragment... my $identity = $csr->claimed_identity($claimed_uri); unless ( $identity ) { if ( $self->errors_are_fatal ) { Catalyst::Exception->throw($csr->err); } else { $c->log->error($csr->err . " -- $claimed_uri"); return; } } for my $key ( keys %extensions ) { $identity->set_extension_args($key, $extensions{$key}); } my $check_url = $identity->check_url( return_to => $current . '?openid-check=1', trust_root => $self->trust_root || $current, delayed_return => 1, ); $c->res->redirect($check_url); $c->detach(); } elsif ( $c->req->params->{'openid-check'} ) { if ( my $setup_url = $csr->user_setup_url ) { $c->res->redirect($setup_url); return; } elsif ( $csr->user_cancel ) { return; } elsif ( my $identity = $csr->verified_identity ) { # This is where we ought to build an OpenID user and verify against the spec. my $user = +{ map { $_ => scalar $identity->$_ } qw( url display rss atom foaf declared_rss declared_atom declared_foaf foafmaker ) }; # Dude, I did not design the array as hash spec. Don't curse me [apv]. for my $key ( keys %extensions ) { my $vals = $identity->signed_extension_fields($key); $user->{extensions}->{$key} = $vals; if ( $self->flatten_extensions_into_user ) { $user->{$_} = $vals->{$_} for keys %{$vals}; } } my $user_obj = $realm->find_user($user, $c); if ( ref $user_obj ) { return $user_obj; } else { $c->log->debug("Verified OpenID identity failed to load with find_user; bad user_class? Try 'Null.'") if $self->debug; return; } } else { $self->errors_are_fatal ? Catalyst::Exception->throw("Error validating identity: " . $csr->err) : $c->log->error( $csr->err); } } return; } 1; __END__ =head1 NAME Catalyst::Authentication::Credential::OpenID - OpenID credential for Catalyst::Plugin::Authentication framework. =head1 BACKWARDS COMPATIBILITY CHANGES =head2 EXTENSION_ARGS v EXTENSIONS B: The extensions were previously configured under the key C. They are now configured under C. C is no longer honored. As previously noted, L, I have not tested the extensions. I would be grateful for any feedback or, better, tests. =head2 FATALS The problems encountered by failed OpenID operations have always been fatals in the past. This is unexpected behavior for most users as it differs from other credentials. Authentication errors here are no longer fatal. Debug/error output is improved to offset the loss of information. If for some reason you would prefer the legacy/fatal behavior, set the configuration variable C to a true value. =head1 SYNOPSIS In MyApp.pm- use Catalyst qw/ Authentication Session Session::Store::FastMmap Session::State::Cookie /; Somewhere in myapp.conf- default_realm openid class OpenID ua_class LWP::UserAgent Or in your myapp.yml if you're using L instead- Plugin::Authentication: default_realm: openid realms: openid: credential: class: OpenID ua_class: LWP::UserAgent In a controller, perhaps C- sub openid : Local { my($self, $c) = @_; if ( $c->authenticate() ) { $c->flash(message => "You signed in with OpenID!"); $c->res->redirect( $c->uri_for('/') ); } else { # Present OpenID form. } } And a L