209 lines
5.4 KiB
Perl
209 lines
5.4 KiB
Perl
# This code was forked from the LiveJournal project owned and operated
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
|
# currently be found at:
|
|
#
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
|
#
|
|
# In accordance with the original license, this code and all its
|
|
# modifications are provided under the GNU General Public License.
|
|
# A copy of that license can be found in the LICENSE file included as
|
|
# part of this distribution.
|
|
|
|
package LJ::OpenID;
|
|
|
|
use strict;
|
|
use Digest::SHA1 qw(sha1 sha1_hex);
|
|
use LJ::OpenID::Cache;
|
|
use Net::OpenID::Server;
|
|
use Net::OpenID::Consumer;
|
|
|
|
sub server {
|
|
my ( $get, $post ) = @_;
|
|
|
|
my %args = ( %{ $get || {} }, %{ $post || {} } );
|
|
|
|
return Net::OpenID::Server->new(
|
|
args => \%args,
|
|
|
|
get_user => \&LJ::get_remote,
|
|
is_identity => sub {
|
|
my ( $u, $ident ) = @_;
|
|
return LJ::OpenID::is_identity( $u, $ident, $get );
|
|
},
|
|
is_trusted => \&LJ::OpenID::is_trusted,
|
|
|
|
setup_url => "$LJ::SITEROOT/openid/approve",
|
|
|
|
server_secret => \&LJ::OpenID::server_secret,
|
|
secret_gen_interval => 3600,
|
|
secret_expire_age => 86400 * 14,
|
|
);
|
|
}
|
|
|
|
# Returns a Consumer object
|
|
# When planning to verify identity, needs GET
|
|
# arguments passed in
|
|
sub consumer {
|
|
my $get_args = shift || {};
|
|
|
|
# always use a paranoid useragent
|
|
my $ua = LJ::get_useragent(
|
|
role => "OpenID",
|
|
timeout => 10,
|
|
max_size => 1024 * 300,
|
|
);
|
|
|
|
my $cache = undef;
|
|
if ( !$LJ::OPENID_STATELESS && scalar(@LJ::MEMCACHE_SERVERS) ) {
|
|
$cache = LJ::OpenID::Cache->new;
|
|
}
|
|
|
|
my $csr = Net::OpenID::Consumer->new(
|
|
ua => $ua,
|
|
args => $get_args,
|
|
cache => $cache,
|
|
consumer_secret => \&LJ::OpenID::consumer_secret,
|
|
debug => $LJ::IS_DEV_SERVER || 0,
|
|
required_root => $LJ::SITEROOT,
|
|
);
|
|
|
|
return $csr;
|
|
}
|
|
|
|
sub consumer_secret {
|
|
my $time = shift;
|
|
return server_secret( $time - $time % 3600 );
|
|
}
|
|
|
|
sub server_secret {
|
|
my $time = shift;
|
|
my ( $t2, $secret ) = LJ::get_secret($time);
|
|
die "ASSERT: didn't get t2 (t1=$time)" unless $t2;
|
|
die "ASSERT: didn't get secret (t2=$t2)" unless $secret;
|
|
die "ASSERT: time($time) != t2($t2)\n" unless $t2 == $time;
|
|
return $secret;
|
|
}
|
|
|
|
sub is_trusted {
|
|
my ( $u, $trust_root, $is_identity ) = @_;
|
|
return 0 unless $u;
|
|
|
|
# we always look up $is_trusted, even if $is_identity is false, to avoid timing attacks
|
|
|
|
my $dbh = LJ::get_db_writer();
|
|
my ( $endpointid, $duration ) = $dbh->selectrow_array(
|
|
"SELECT t.endpoint_id, t.duration "
|
|
. "FROM openid_trust t, openid_endpoint e "
|
|
. "WHERE t.userid=? AND t.endpoint_id=e.endpoint_id AND e.url=?",
|
|
undef, $u->{userid}, $trust_root
|
|
);
|
|
return 0 unless $endpointid;
|
|
return 1;
|
|
}
|
|
|
|
sub is_identity {
|
|
my ( $u, $ident, $get ) = @_;
|
|
return 0 unless $u && $u->is_person;
|
|
|
|
# canonicalize trailing slash
|
|
$ident .= "/" unless $ident =~ m!/$!;
|
|
|
|
my $user = $u->user;
|
|
my $url = $u->journal_base . "/";
|
|
|
|
return 1
|
|
if $ident eq $url
|
|
||
|
|
|
|
# legacy:
|
|
$ident eq "$LJ::SITEROOT/users/$user/"
|
|
|| $ident eq "$LJ::SITEROOT/~$user/"
|
|
|| $ident eq "http://$user.$LJ::USER_DOMAIN/"
|
|
|| $ident eq "https://$user.$LJ::USER_DOMAIN/";
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub getmake_endpointid {
|
|
my $site = shift;
|
|
|
|
my $dbh = LJ::get_db_writer()
|
|
or return undef;
|
|
|
|
my $rv = $dbh->do( "INSERT IGNORE INTO openid_endpoint (url) VALUES (?)", undef, $site );
|
|
my $end_id;
|
|
if ( $rv > 0 ) {
|
|
$end_id = $dbh->{'mysql_insertid'};
|
|
}
|
|
else {
|
|
$end_id = $dbh->selectrow_array( "SELECT endpoint_id FROM openid_endpoint WHERE url=?",
|
|
undef, $site );
|
|
}
|
|
return $end_id;
|
|
}
|
|
|
|
sub add_trust {
|
|
my ( $u, $site ) = @_;
|
|
|
|
my $end_id = LJ::OpenID::getmake_endpointid($site)
|
|
or return 0;
|
|
|
|
my $dbh = LJ::get_db_writer()
|
|
or return undef;
|
|
|
|
my $rv = $dbh->do(
|
|
"REPLACE INTO openid_trust (userid, endpoint_id, duration, trust_time) "
|
|
. "VALUES (?,?,?,UNIX_TIMESTAMP())",
|
|
undef, $u->{userid}, $end_id, "always"
|
|
);
|
|
return $rv;
|
|
}
|
|
|
|
# From Digest::HMAC
|
|
sub hmac_sha1_hex {
|
|
unpack( "H*", &hmac_sha1 );
|
|
}
|
|
|
|
sub hmac_sha1 {
|
|
hmac( $_[0], $_[1], \&sha1, 64 );
|
|
}
|
|
|
|
sub hmac {
|
|
my ( $data, $key, $hash_func, $block_size ) = @_;
|
|
$block_size ||= 64;
|
|
$key = &$hash_func($key) if length($key) > $block_size;
|
|
|
|
my $k_ipad = $key ^ ( chr(0x36) x $block_size );
|
|
my $k_opad = $key ^ ( chr(0x5c) x $block_size );
|
|
|
|
&$hash_func( $k_opad, &$hash_func( $k_ipad, $data ) );
|
|
}
|
|
|
|
# Returns 1 if destination identity server
|
|
# is blocked
|
|
sub blocked_hosts {
|
|
my $csr = shift;
|
|
|
|
# uncomment this if you need to bypass this check for testing purposes
|
|
# return do { my $dummy = 0; \$dummy; } if $LJ::IS_DEV_SERVER;
|
|
|
|
my $tried_local_id = 0;
|
|
$csr->ua->blocked_hosts(
|
|
[
|
|
sub {
|
|
my $dest = shift;
|
|
|
|
if ( $dest =~ /(^|\.)\Q$LJ::DOMAIN\E$/i ) {
|
|
$tried_local_id = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
]
|
|
);
|
|
return \$tried_local_id;
|
|
}
|
|
|
|
1;
|