#!/usr/bin/perl
#
# DW::OAuth
#
# OAuth Request Token
#
# 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::Request;
use strict;
use warnings;

use LJ::Utils qw(rand_chars);
use Digest::SHA qw/sha1 sha256/;
use MIME::Base64::URLSafe;

use DW::OAuth;

# NOTE: This is not db-backed at all, and is memcache only.
# These are so short lived that there is no point to put them in the database.

sub from_token {
    my ( $class, $token ) = @_;
    return undef unless $token;
    return undef unless DW::OAuth->validate_token($token);

    {
        my $ar  = LJ::MemCache::get( [ $token, "oauth_request_token:" . $token ] );
        my $row = $ar ? LJ::MemCache::array_to_hash( "oauth_request", $ar ) : undef;
        return $class->new_from_row($row) if $row;
    }

    return undef;
}

sub want {
    my ( $class, $thing ) = @_;

    return undef unless $thing;
    return $thing if ref $thing eq $class;
    return $class->from_token($thing);
}

sub _make_simple {
    my $rv = 0;

    map { $rv += $_ } unpack( "LLLLL", $_[0] );

    return sprintf( "%08i", $rv % 100000000 );
}

sub new {
    my ( $class, $consumer, %opts ) = @_;

    my $c = DW::OAuth::Consumer->want($consumer);
    $opts{consumer_id} = $c->id if $c;

    # Required.
    die "Invalid consumer" unless $opts{consumer_id};

    $opts{callback} ||= 'oob';

    # Set some default options:
    if ( $opts{callback} eq 'oob' ) {
        $opts{simple_verifier} = 1 unless defined $opts{simple_verifier};
    }

    my ( $token, $secret ) = DW::OAuth->make_token_pair('request');

    $opts{token}  = $token;
    $opts{secret} = $secret;

    $opts{createtime} = time();

    my $verifier_string = sha1( $opts{token} . LJ::rand_chars(32) );
    if ( $opts{simple_verifier} ) {

        # this is a roundabout way to get a good 8-digit number
        # these don't have to be unique, just very hard to guess.
        $opts{verifier} = _make_simple($verifier_string);
    }
    else {
        $opts{verifier} = urlsafe_b64encode($verifier_string);
    }

    # change token into a simple token if that's requested
    if ( $opts{simple_token} ) {
        $opts{token} = _make_simple( $opts{token} );
    }

    delete $opts{user_id};

    my $rv = $class->new_from_row( \%opts );
    $rv->save;
    return $rv;
}

sub new_from_row {
    my ( $class, $row ) = @_;

    my $c = bless $row, $class;

    return $c;
}

sub save {
    my $c = $_[0];

    # This is intentionally only good for 600 seconds.
    my $expire = time() + 600;
    my $ar     = LJ::MemCache::hash_to_array( "oauth_request", $c );
    LJ::MemCache::set( [ $c->token, "oauth_request_token:" . $c->token ], $ar, $expire );
}

sub consumer_id {
    return $_[0]->{consumer_id};
}

sub consumer {
    return DW::OAuth::Consumer->from_id( $_[0]->consumer_id );
}

sub userid {
    if ( exists $_[1] ) {
        $_[0]->{userid} = $_[1];
    }
    else {
        return $_[0]->{userid};
    }
}

sub user {
    return $_[0]->userid ? LJ::load_userid( $_[0]->userid ) : undef;
}

sub token {
    return $_[0]->{token};
}

sub secret {
    return $_[0]->{secret};
}

sub createtime {
    return $_[0]->{createtime};
}

sub verifier {
    return $_[0]->{verifier};
}

sub callback {
    return $_[0]->{callback};
}

sub usable {
    my $r = $_[0];
    my $c = $r->consumer;

    return 0 unless $c;
    return 0 if exists $_[1] && $c->id != $_[1]->id;

    return 0 unless $c->usable;
    return 0 if $c->invalidatedtime && $r->createtime <= $c->invalidatedtime;
    return 1;
}

sub used {
    return $_[0]->{used} || 0;
}

sub delete {
    LJ::MemCache::delete( [ $_[0]->token, "oauth_request_token:" . $_[0]->token ] );
    $_[0]->{used} = 1;
}

1;
