mourningdove/cgi-bin/DW/OAuth/Consumer.pm
2026-05-24 01:03:05 +00:00

383 lines
9.1 KiB
Perl

#!/usr/bin/perl
#
# DW::OAuth
#
# OAuth Consumer
#
# Authors:
# Andrea Nall <anall@andreanall.com>
#
# Copyright (c) 2012 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#
package DW::OAuth::Consumer;
use strict;
use warnings;
use DW::OAuth;
sub from_token {
my ( $class, $token ) = @_;
return undef unless $token;
return $LJ::REQUEST_CACHE{oauth_consumer}{$token} if $LJ::REQUEST_CACHE{oauth_consumer}{$token};
return undef unless DW::OAuth->validate_token($token);
{
my $consumer_id = LJ::MemCache::get( [ $token, "oauth_consumer_token:" . $token ] );
return $class->from_id($consumer_id) if $consumer_id;
}
return $class->_load_raw( 'token', $token );
}
sub from_id {
my ( $class, $id ) = @_;
return undef unless $id;
return $LJ::REQUEST_CACHE{oauth_consumer}{$id} if $LJ::REQUEST_CACHE{oauth_consumer}{$id};
{
my $ar = LJ::MemCache::get( [ $id, "oauth_consumer:" . $id ] );
my $row = $ar ? LJ::MemCache::array_to_hash( "oauth_consumer", $ar ) : undef;
return $class->new_from_row($row) if $row;
}
return $class->_load_raw( 'consumer_id', $id );
}
sub want {
my ( $class, $thing ) = @_;
return undef unless $thing;
return $thing if ref $thing eq $class;
return $class->from_id($thing) if int($thing) eq $thing;
return $class->from_token($thing);
}
sub want_id {
my ( $class, $thing ) = @_;
return undef unless $thing;
return $thing->consumer_id if ref $thing eq $class;
return $thing if $thing =~ /^\d+$/;
return undef;
}
sub tokens_for_user {
my ( $class, $u ) = @_;
my $userid = LJ::want_userid($u);
return [] unless $userid;
my @ret;
my @ids;
my $memkey = [ $userid, "user_oauth_consumer:" . $userid ];
my $data = LJ::MemCache::get($memkey);
if ($data) {
@ids = @$data;
}
else {
my $dbr = LJ::get_db_reader() or die "Failed to get database";
my $sth = $dbr->prepare("SELECT consumer_id FROM oauth_consumer WHERE userid = ?")
or die $dbr->errstr;
$sth->execute($userid) or die $dbr->errstr;
while ( my ($id) = $sth->fetchrow_array ) {
push @ids, $id;
}
LJ::MemCache::set( $memkey, \@ids );
}
foreach my $id (@ids) {
push @ret, $class->from_id($id);
}
return \@ret;
}
sub _clear_user_tokens {
LJ::MemCache::delete( [ $_[1], "user_oauth_consumer:" . $_[1] ] );
}
sub _delete_cache {
my $c = $_[0];
DW::OAuth::Consumer->_clear_user_tokens( $c->{userid} );
LJ::MemCache::delete( [ $c->id, "oauth_consumer:" . $c->id ] );
LJ::MemCache::delete( [ $c->token, "oauth_consumer_token:" . $c->token ] );
delete $LJ::REQUEST_CACHE{oauth_consumer}{ $c->token };
}
sub _load_raw {
my ( $class, $key, $val ) = @_;
my $dbh = LJ::get_db_writer() or die "Failed to get database";
my $sth = $dbh->prepare("SELECT * FROM oauth_consumer WHERE $key = ?") or die $dbh->errstr;
$sth->execute($val) or die $dbh->errstr;
my $row = $sth->fetchrow_hashref;
return $row ? $class->new_from_row($row) : undef;
}
sub new {
my ( $class, %opts ) = @_;
$opts{userid} = $opts{u}->userid if $opts{u};
# Required.
die "Missing required parameter" unless $opts{userid} && $opts{name} && $opts{website};
my ( $token, $secret ) = $class->make_token_pair( \%opts );
$opts{token} = $token;
$opts{secret} = $secret;
my $dbh = LJ::get_db_writer() or die 'Failed to get database';
my $id = LJ::alloc_global_counter('U') or die 'Failed to alloc counter';
$dbh->do(
"INSERT INTO oauth_consumer (consumer_id, userid, name, website, token, secret, createtime) VALUES (?,?,?,?,?,?,?)",
undef,
$id,
$opts{userid},
$opts{name},
$opts{website},
$opts{token},
$opts{secret},
time()
) or die $dbh->errstr;
$class->_clear_user_tokens( $opts{userid} );
return $class->from_id($id);
}
sub make_token_pair {
my ( $self, $data ) = @_;
$data = $self if ref $self;
return DW::OAuth->make_token_pair('consumer');
}
sub new_from_row {
my ( $class, $row ) = @_;
my $c = bless $row, $class;
# These can change, we need to store the original value so in case it changes, we can invalidate the right memcache key later.
for my $item (qw(token userid)) {
$c->{_orig}{$item} = $c->{$item};
}
my $expire = time() + 1800;
my $ar = LJ::MemCache::hash_to_array( "oauth_consumer", $c );
LJ::MemCache::set( [ $c->id, "oauth_consumer:" . $c->id ], $ar, $expire );
LJ::MemCache::set( [ $c->token, "oauth_consumer_token:" . $c->token ], $c->id );
$LJ::REQUEST_CACHE{oauth_consumer}{ $c->token } = $c;
$LJ::REQUEST_CACHE{oauth_consumer}{ $c->id } = $c;
return $c;
}
sub id {
$_[0]->{consumer_id};
}
sub owner {
if ( defined $_[1] ) {
$_[0]->{changed}{userid} = 1;
return $_[0]->{userid} = $_[1]->userid;
}
else {
return LJ::load_userid( $_[0]->{userid} );
}
}
sub ownerid {
if ( defined $_[1] ) {
$_[0]->{changed}{userid} = 1;
return $_[0]->{userid} = $_[1];
}
else {
return $_[0]->{userid};
}
}
sub token {
return $_[0]->{token};
}
sub secret {
return $_[0]->{secret};
}
sub reissue_token_pair {
my ( $token, $secret ) = $_[0]->make_token_pair;
$_[0]->{token} = $token;
$_[0]->{secret} = $secret;
$_[0]->{changed}{token} = 1;
$_[0]->{changed}{secret} = 1;
$_[0]->invalidatedtime( time() );
return $_[0]->save;
}
sub name {
if ( defined $_[1] ) {
$_[0]->{changed}{name} = 1;
return $_[0]->{name} = $_[1];
}
else {
return $_[0]->{name};
}
}
sub website {
if ( defined $_[1] ) {
$_[0]->{changed}{website} = 1;
return $_[0]->{website} = $_[1];
}
else {
return $_[0]->{website};
}
}
sub createtime {
return $_[0]->{createtime};
}
sub updatetime {
if ( exists $_[1] ) {
$_[0]->{changed}{updatetime} = 1;
return $_[0]->{updatetime} = $_[1];
}
else {
return $_[0]->{updatetime};
}
}
sub invalidatedtime {
if ( exists $_[1] ) {
$_[0]->{changed}{invalidatedtime} = 1;
return $_[0]->{invalidatedtime} = $_[1];
}
else {
return $_[0]->{invalidatedtime};
}
}
sub approved {
if ( defined $_[1] ) {
$_[0]->{changed}{approved} = 1;
return $_[0]->{approved} = $_[1];
}
else {
return $_[0]->{approved};
}
}
sub active {
if ( defined $_[1] ) {
$_[0]->{changed}{active} = 1;
return $_[0]->{active} = $_[1];
}
else {
return $_[0]->{active};
}
}
sub why_unusable {
my $self = $_[0];
my $u = $self->owner;
return 'no_user' unless $u;
return 'user_inactive' if $u->is_inactive;
return 'not_person' unless $u->is_person;
return 'sysbanned' if LJ::sysban_check( 'oauth_consumer', $u->user );
return 'no_approve' unless $_[0]->approved;
return 'inactive' unless $_[0]->active;
return 'unknown' unless $self->usable;
return undef;
}
sub usable {
my $self = $_[0];
my $u = $self->owner;
return 0 unless $u;
return 0 if $u->is_inactive || !$u->is_person;
return 0 if LJ::sysban_check( 'oauth_consumer', $u->user );
return ( $_[0]->approved && $_[0]->active ) ? 1 : 0;
}
sub save {
my $c = $_[0];
my $changed = $c->{changed};
return unless $changed;
my @sets;
my @bindparams;
while ( my ( $k, $v ) = each %$changed ) {
next unless $v;
push @sets, "$k=?";
push @bindparams, $c->{$k};
}
return 1 unless @sets;
my $dbh = LJ::get_db_writer();
return 0 unless $dbh;
{
local $" = ",";
$dbh->do( "UPDATE oauth_consumer SET @sets WHERE consumer_id = ?",
undef, @bindparams, $c->id );
return 0 if $dbh->err;
}
if ( $changed->{userid} ) {
DW::OAuth::Consumer->_clear_user_tokens( $c->{userid} );
DW::OAuth::Consumer->_clear_user_tokens( $c->{_orig}{userid} );
}
LJ::MemCache::delete( [ $c->id, "oauth_consumer:" . $c->id ] );
unless ( $c->{_orig}{token} eq $c->{token} ) {
LJ::MemCache::delete(
[ $c->{_orig}{token}, "oauth_consumer_token:" . $c->{_orig}{token} ] );
LJ::MemCache::delete( [ $c->token, "oauth_consumer_token:" . $c->token ] ); # just in case
delete $LJ::REQUEST_CACHE{oauth_consumer}{ $c->{_orig}{token} };
$LJ::REQUEST_CACHE{oauth_consumer}{ $c->token } = $c;
}
$c->{_orig}{token} = $c->{token};
delete $c->{changed};
return 1;
}
sub delete {
my $c = $_[0];
my $tokens = DW::OAuth::Access->tokens_for_consumer($c);
my $dbh = LJ::get_db_writer();
return 0 unless $dbh;
foreach my $token (@$tokens) {
$token->delete or return 0;
}
$dbh->do( "DELETE FROM oauth_consumer WHERE consumer_id = ?", undef, $c->id ) or return 0;
$c->_delete_cache;
return 1;
}
1;