#!/usr/bin/perl
#
# DW::Controller::OAuth::Protocol
#
# Web-facing OAuth ( Protocol Methods )
#
# Authors:
#      Andrea Nall <anall@andreanall.com>
#
# Copyright (c) 2013 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::Controller::OAuth::Protocol;

use strict;
use warnings;
use DW::Routing;
use DW::Request;

use DW::OAuth;
use DW::OAuth::Consumer;
use DW::OAuth::Request;
use DW::OAuth::Access;

use LJ::JSON;

use DW::Controller;

# User facing
DW::Routing->register_string( "/oauth/authorize", \&authorize_handler, app => 1, );

# API Callbacks
DW::Routing->register_string(
    "/oauth/request_token", \&request_token_handler,
    app    => 1,
    format => "plain"
);
DW::Routing->register_string(
    "/oauth/access_token", \&access_token_handler,
    app    => 1,
    format => "plain"
);

# Authorization test endpoint
DW::Routing->register_string(
    "/oauth/test", \&test_handler,
    app    => 1,
    format => "json"
);

# None of the methods here call controller() intentionally

sub request_token_handler {
    my $r = DW::Request->get;

    $r->content_type("text/plain");

    my $args = $r->did_post ? $r->post_args : $r->get_args;

    my ( $request, $consumer ) = DW::OAuth->get_request('request token');

    if ( !defined $request ) {
        $r->status_line("400 Bad Request");
        $r->print("Could not find/decode request");
    }
    elsif ( !$request ) {
        $r->status_line("401 Unauthorized");
        $r->print($consumer);    # also error
    }
    else {
        # Service Provider sends Request Token Response
        my $request_token = DW::OAuth::Request->new(
            $consumer,
            callback        => $request->callback,
            simple_token    => $args->{simple_token} ? 1 : 0,
            simple_verifier => $args->{simple_verifier} ? 1 : 0,
        );
        $r->status_line("200 OK");
        my $response = Net::OAuth->response("request token")->new(
            token              => $request_token->token,
            token_secret       => $request_token->secret,
            callback_confirmed => 'true',
            protocol_version   => Net::OAuth::PROTOCOL_VERSION_1_0A,
        );

        # FIXME: Callbacks here
        $r->print( $response->to_post_body );
    }

    return $r->OK;
}

sub access_token_handler {
    my $r = DW::Request->get;

    $r->content_type("text/plain");

    my ( $request, @rest ) = DW::OAuth->get_request('access token');

    if ( !defined $request ) {
        $r->status_line("400 Bad Request");
        $r->print("Could not find/decode request");
    }
    elsif ( !$request ) {
        $r->status_line("401 Unauthorized");
        $r->print( $rest[0] );
    }
    else {
        my ( $consumer, $token ) = @rest;

        # Service Provider sends Request Token Response
        my $access = DW::OAuth::Access->new($token);
        $access->reissue_token unless $access->token_valid;

        # Get rid of the request token
        $token->delete;

        $r->status_line("200 OK");
        my $response = Net::OAuth->response("access token")->new(
            token              => $access->token,
            token_secret       => $access->secret,
            callback_confirmed => 'true',
            protocol_version   => Net::OAuth::PROTOCOL_VERSION_1_0A,
            extra_params       => {
                dw_username => $access->user->username,
                dw_userid   => $access->user->id,
            },
        );

        $r->print( $response->to_post_body );
    }

    return $r->OK;
}

sub authorize_handler {
    my $r = DW::Request->get;

    my $did_post = $r->did_post;
    my $args     = $did_post ? $r->post_args : $r->get_args;

    # Because I want to be able to give the user instructions even if they must log in.
    my $anonymous = 1;
    $anonymous = 0 if $args->{allow} || $args->{deny};

    my ( $ok, $rv ) = controller( anonymous => $anonymous, form_auth => 1 );
    return $rv unless $ok;

    my $request  = DW::OAuth::Request->from_token( $args->{oauth_token} );
    my $consumer = $request ? $request->consumer : undef;

    # even though $rv->{u} *should* be set, doesn't hurt to check it.
    if ( $consumer && $args->{allow} && $rv->{u} && $did_post ) {
        $request->userid( $rv->{u}->userid );
        $request->save;

        if ( $request->callback ne 'oob' ) {
            my $response = Net::OAuth->response("user auth")->new(
                token            => $request->token,
                verifier         => $request->verifier,
                protocol_version => Net::OAuth::PROTOCOL_VERSION_1_0A,
            );

            $r->content_type("text/html");
            $r->header_out( 'Location', $response->to_url( $request->callback ) );
            return $r->REDIRECT;
        }
    }
    elsif ( $consumer && $args->{deny} && $did_post ) {
        $request->delete;

        if ( $request->callback ne 'oob' ) {
            my $response = Net::OAuth->response("user auth")->new(
                token            => $request->token,
                verifier         => '',
                protocol_version => Net::OAuth::PROTOCOL_VERSION_1_0A,
            );

            $r->content_type("text/html");
            $r->header_out( 'Location', $response->to_url( $request->callback ) );
            return $r->REDIRECT;
        }
    }

    return DW::Template->render_template(
        'oauth/authorize.tt',
        {
            %$rv,
            request     => $request,
            consumer    => $consumer,
            oauth_token => $args->{oauth_token},
            args        => $args,
        }
    );
}

sub test_handler {
    my $r = DW::Request->get;

    my $err = sub {
        my ( $err, %rest ) = @_;
        $r->print( to_json( { ok => 0, error => $err, %rest } ) );
        return $r->OK;
    };

    my ( $res, $u ) = DW::OAuth->user_for_protected_resource;

    return $err->("not_attempted") unless defined $res;
    return $err->($u) unless $res;

    $r->print( to_json( { ok => 1, username => $u->user, userid => $u->id } ) );
    return $r->OK;
}

1;
