The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package OAuth::Lite2::Signer;

use strict;
use warnings;

use MIME::Base64 qw(encode_base64);
use String::Random;
use URI;
use Carp ();
use Params::Validate;

use OAuth::Lite2::Signer::Algorithms;

=head1 NAME

OAuth::Lite2::Signer - OAuth 2.0 signature (DEPRECATED)

=head1 SYNOPSIS

    my $signed_params = OAuth::Lite2::Signer->sign(
        secret    => q{my_token_secret},
        algorithm => q{hmac-sha256},
        method    => q{GET},
        url       => q{http://example.org/protected/resource},
    );

=head1 DESCRIPTION

DEPRECATED. This is for old version of OAuth 2.0 draft specification.

This is for client to generate signed request,
or for server to verify received request.

=head1 METHODS

=head2 sign( %params )

Returns the hash reference that includes parameters for OAuth 2.0 signed request.

    my $signed_params = OAuth::Lite2::Signer->sign(
        secret    => q{my_token_secret},
        algorithm => q{hmac-sha256},
        method    => q{GET},
        url       => q{http://example.org/protected/resource},
    );

=over 4

=item secret

Access token secret.

=item algorithm

The algorithm what you make signature with.

=item method

HTTP method of the request.

=item url

URL of the request.

=item debug_nonce

Optional. If you omit this, nonce string is automatically generate random string.

=item debug_timestamp

Optional. If you omit this, current timestamp is set.

=back

=cut

sub sign {
    my $class = shift;

    my %args = Params::Validate::validate(@_, {
        secret          => 1,
        algorithm       => 1,
        method          => 1,
        url             => 1,
        debug_nonce     => { optional => 1 },
        debug_timestamp => { optional => 1 },
    });

    my $uri = URI->new($args{url});
    Carp::croak "invalid uri scheme: " . $args{url}
        unless ($uri->scheme eq 'http' || $uri->scheme eq 'https');

    my $params = {
        nonce     => $args{debug_nonce}     || $class->_gen_nonce(),
        timestamp => $args{debug_timestamp} || $class->_gen_timestamp(),
        algorithm => $args{algorithm},
    };

    my $string = $class->normalize_string(%$params,
        method => $args{method},
        host   => $uri->host,
        port   => $uri->port || 80,
        url    => $args{url},
    );

    my $algorithm =
        OAuth::Lite2::Signer::Algorithms->get_algorithm(lc $args{algorithm})
            or Carp::croak "Unsupported algorithm: " . $args{algorithm};

    my $signature = encode_base64($algorithm->hash($args{secret}, $string));
    chomp $signature;
    $params->{signature} = $signature;
    return $params;
}

=head2 normalize_string( %params )

Returns normalized string according to the specification.

=over 4

=item host

host part of the url.

=item port

If you omit this, 80 is set by default.

=item nonce

Random string.

=item timestamp

unix timestamp.

=item algorithm

name of hmac hash algorithm.

=item method

HTTP method of the request.

=item url

URL of the request.

=back

=cut

sub normalize_string {
    my ($class, %args) = @_;
    $args{port} ||= 80;
    return join(",",
        $args{timestamp},
        $args{nonce},
        $args{algorithm},
        uc($args{method}),
        sprintf(q{%s:%d}, $args{host}, $args{port}),
        $args{url},
    );
}

sub _gen_nonce {
    my ($class, $digit) = @_;
    $digit ||= 10;
    my $random = String::Random->new;
    return $random->randregex( sprintf '[a-zA-Z0-9]{%d}', $digit );
}

sub _gen_timestamp {
    my $class = shift;
    return time();
}

=head2 verify( %params )

Verify a signed request.

    unless ( OAuth::Lite2::Signer->verify( %params ) ) {
        $app->error("Invalid request");
    }

=over 4

=item signature

'signature' parameter of the received request.

=item secret

The access token secret.

=item algorithm

'algorithm' parameter of the received request.

=item method

HTTP method of the received request.

=item url

URL of the received request.

=item nonce

'nonce' parameter of the received request.

=item timestamp

'timestamp' parameter of the received request.

=back

=cut

sub verify {
    my $class = shift;

    my %args = Params::Validate::validate(@_, {
        secret          => 1,
        algorithm       => 1,
        method          => 1,
        url             => 1,
        nonce           => 1,
        timestamp       => 1,
        signature       => 1,
    });

    my $uri = URI->new($args{url});

    my $params = {
        nonce     => $args{nonce},
        timestamp => $args{timestamp},
        algorithm => $args{algorithm},
    };

    my $string = $class->normalize_string(%$params,
        method => $args{method},
        host   => $uri->host,
        port   => $uri->port || 80,
        url    => $args{url},
    );

    my $algorithm =
        OAuth::Lite2::Signer::Algorithms->get_algorithm($args{algorithm})
            or return 0;

    my $signature = encode_base64($algorithm->hash($args{secret}, $string));
    chomp $signature;

    return ($args{signature} eq $signature);
}


=head1 AUTHOR

Lyo Kato, E<lt>lyo.kato@gmail.comE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 by Lyo Kato

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut

1;