=head1 NAME EPrints::DataObj::OpenID =cut package EPrints::DataObj::OpenID; use Digest::SHA; use MIME::Base64; @ISA = qw( EPrints::DataObj ); use strict; sub get_dataset_id { "openid" } sub get_system_field_info { my( $self ) = @_; return ( { name => "openidid", type => "counter", sql_counter => "openidid" }, { name => "op_endpoint", type => "id", sql_index => 1 }, { name => "expires", type => "int", sql_index => 1 }, { name => "assoc_handle", type => "id", sql_index => 1 }, { name => "mac_key", type => "id", sql_index => 0 }, { name => "response_nonce", type => "id", sql_index => 1 }, ); } sub new_by_op_endpoint { my( $class, $repo, $op_endpoint, $assoc_handle ) = @_; return $repo->dataset( $class->get_dataset_id )->search(filters => [ { meta_fields => [qw( op_endpoint )], value => $op_endpoint }, { meta_fields => [qw( response_nonce )], value => "", match => "EX" }, ])->item( 0 ); } sub new_by_assoc_handle { my( $class, $repo, $op_endpoint, $assoc_handle ) = @_; return $repo->dataset( $class->get_dataset_id )->search(filters => [ { meta_fields => [qw( op_endpoint )], value => $op_endpoint }, { meta_fields => [qw( assoc_handle )], value => $assoc_handle } ])->item( 0 ); } sub new_by_response_nonce { my( $class, $repo, $op_endpoint, $response_nonce ) = @_; return $repo->dataset( $class->get_dataset_id )->search(filters => [ { meta_fields => [qw( op_endpoint )], value => $op_endpoint }, { meta_fields => [qw( response_nonce )], value => $response_nonce } ])->item( 0 ); } sub create_by_op_endpoint { my( $class, $repo, $op_endpoint ) = @_; my $ua = LWP::UserAgent->new; my $uri = URI->new( $op_endpoint ); $uri->query_form( $uri->query_form, 'openid.ns' => 'http://specs.openid.net/auth/2.0', 'openid.mode' => 'associate', 'openid.assoc_type' => 'HMAC-SHA256', 'openid.session_type' => 'no-encryption', ); my $r = $ua->get( $uri ); my %kv = EPrints::DataObj::OpenID->parse_key_value( $r->content ); if( !$r->is_success || $kv{error} ) { die "$kv{error_code}: $kv{error}"; } if( !$kv{expires_in} || $kv{expires_in} > 86400 ) { $kv{expires_in} = 86400; } return $class->create_from_data( $repo, { op_endpoint => $op_endpoint, expires => time() + $kv{expires_in}, assoc_handle => $kv{assoc_handle}, mac_key => $kv{mac_key}, }); } =item EPrints::DataObj::OpenID->cleanup() =cut sub cleanup { my( $class, $repo ) = @_; my $now = time(); $repo->dataset( $class->get_dataset_id )->search( filters => [{ meta_fields => [qw( expires )], value => "-$now" }] )->map(sub { $_[2]->remove }); } =item @kvs = EPrints::DataObj::OpenID->parse_key_value( $content ) =cut sub parse_key_value { my( $class, $cnt ) = @_; my @kvs; for(split /\r?\n/, $cnt) { push @kvs, split /\s*:\s*/, $_, 2; } return @kvs; } =item %attr = EPrints::DataObj::OpenID->retrieve_attributes( REPOSITORY ) Retrieve all attributes defined using OpenID Attribute Extensions. =cut sub retrieve_attributes { my( $class, $repo ) = @_; my $prefix = ''; for($repo->param()) { if( $repo->param( $_ ) eq 'http://openid.net/srv/ax/1.0' ) { /^openid\.ns\.([^\.]+)$/; $prefix = $1; last; } } my @kv; for($repo->param()) { next unless /^openid\.$prefix\.(.+)$/; push @kv, $1 => $repo->param( $_ ); } return @kv; } =item $ok = $openid->verify( [ @extra_keys ] ) =cut sub verify { my( $self, @extra_keys ) = @_; my $repo = $self->{session}; my $mac_key = $self->value( "mac_key" ); my $sig = $repo->param( "openid.sig" ) || ''; my $signed = $repo->param( "openid.signed" ) || ''; my @signed = split /,/, $signed; my %signed = map { $_ => 1 } @signed; return if !@signed; # see http://idmanagement.gov/documents/ICAM_OpenID20Profile.pdf foreach my $key (qw( op_endpoint return_to response_nonce assoc_handle claimed_id identity ), @extra_keys) { return if !$signed{$key}; } my $data = ''; foreach my $key (@signed) { $data .= join(':', $key, scalar($repo->param( 'openid.'.$key )))."\n"; } $mac_key = MIME::Base64::decode_base64( $mac_key ); my $digest = MIME::Base64::encode_base64( Digest::SHA::hmac_sha256( $data, $mac_key ), '' ); return $digest eq $sig; } sub auth_uri { my( $self, %q ) = @_; $q{'openid.ns'} = 'http://specs.openid.net/auth/2.0'; $q{'openid.mode'} ||= 'checkid_setup'; $q{'openid.assoc_handle'} ||= $self->value( 'assoc_handle' ); $q{'openid.claimed_id'} ||= 'http://specs.openid.net/auth/2.0/identifier_select'; $q{'openid.identity'} ||= 'http://specs.openid.net/auth/2.0/identifier_select'; my @q = map { $_ => $q{$_} } sort keys %q; my $uri = URI->new( $self->value( "op_endpoint" ) ); $uri->query_form( $uri->query_form, @q ); return $uri; } 1; =head1 COPYRIGHT =for COPYRIGHT BEGIN Copyright 2000-2011 University of Southampton. =for COPYRIGHT END =for LICENSE BEGIN This file is part of EPrints L. EPrints is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. EPrints is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with EPrints. If not, see L. =for LICENSE END