Skip to content

Instantly share code, notes, and snippets.

@alecs
Last active December 14, 2015 07:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alecs/5051186 to your computer and use it in GitHub Desktop.
Save alecs/5051186 to your computer and use it in GitHub Desktop.
twproxy.pl
#!/usr/bin/env perl
use strict;
use warnings;
#use 5.008;
our $HOSTURI = '#';
our $CONSUMER_KEY = '#';
our $CONSUMER_SECRET = '#';
our $SECRET = '#';
our $CALLBACK_URL = '#';
our $TWITTER_API = 'https://api.twitter.com/1';
our $TWITTER_OAUTH_API = 'https://api.twitter.com';
our $TWITTER_SEARCH_API = 'https://search.twitter.com';
our $DEBUG = 'error';
package Model;
use Mojo::ByteStream 'b';
use ORLite {
file => 'user.db',
cleanup => 'VACUUM',
create => sub {
my $dbh = shift;
$dbh->do(
"CREATE TABLE user (
username TEXT NOT NULL UNIQUE PRIMARY KEY,
password TEXT NOT NULL,
ctime INTEGER NOT NULL,
oauth_token TEXT,
oauth_token_secret TEXT,
user_id TEXT,
screen_name TEXT
);"
);
}, # end of create => sub
};
sub createUpdateUser {
my $class = shift;
my ($username, $password, $args) = @_;
my $oauth_token = $args->{'oauth_token'} || 'NULL';
my $oauth_token_secret = $args->{'oauth_token_secret'} || 'NULL';
my $user_id = $args->{'user_id'};
my $screen_name = $args->{'screen_name'};
if (not Model::User->count('WHERE username=?', $username)) {
Model::User->create(
username => $username,
password => b($SECRET . $password)->md5_sum,
ctime => time(),
oauth_token => $oauth_token,
oauth_token_secret => $oauth_token_secret,
user_id => $user_id,
screen_name => $screen_name,
);
}
else {
$class->do(
'UPDATE user
SET password = ?,
oauth_token = ?,
oauth_token_secret = ?
WHERE username = ?;',
{},
b($SECRET . $password)->md5_sum,
$args->{'oauth_token'},
$args->{'oauth_token_secret'},
$username
);
}
}
sub getUserInfo {
my ($class, $username, $password) = @_;
my $user;
eval '$user = Model::User->load($username)';
return undef if $@;
return $user->password eq b($SECRET . $password)->md5_sum
? $user
: undef;
}
sub validate {
my $class = shift;
my ($username, $password, $repassword, $reg) = @_;
return 'username must not be characters of a-z, 0-9, A-Z and _ !'
unless $username =~ /^[a-zA-Z0-9][a-z0-9A-Z_]/
and (length $username <= 32);
return 'password invalid!' unless $password =~ /^\S{6,32}$/;
return 're-password invalid!'
if (defined $repassword && $repassword !~ /^\S{6,32}$/);
return 'passwords don\'t match!'
if (defined $repassword) && ($password ne $repassword);
return undef;
}
package OAuth;
use Data::Dumper;
use Mojo::ByteStream 'b';
use Net::OAuth;
use Mojo::UserAgent;
$Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A;
sub makeOAuthRequest {
my ($class, $type, %args) = @_;
#local $Net::OAuth::SKIP_UTF8_DOUBLE_ENCODE_CHECK = 1
# if exists $args{'extra_params'};
$args{'consumer_key'} = $CONSUMER_KEY;
$args{'consumer_secret'} = $CONSUMER_SECRET;
$args{'request_method'} ||= 'POST';
$args{'signature_method'} ||= 'HMAC-SHA1';
$args{'callback'} ||= $CALLBACK_URL;
$args{'nonce'} ||= b($SECRET . time())->md5_sum;
$args{'timestamp'} = time();
my $request =
$args{'from_url'}
? Net::OAuth->request($type)->from_url($args{'from_url'}, %args)
: Net::OAuth->request($type)->new(%args);
$request->sign;
return $request;
}
sub requestToken {
my $class = shift;
my $request = $class->makeOAuthRequest('request token',
request_url => "$TWITTER_OAUTH_API/oauth/request_token",);
my $client = Mojo::UserAgent->new->inactivity_timeout(10);
my $tx = $client->post($request->to_url);
if (my $res = $tx->success) {
my %tokens = map { split /=/ } (split /&/, $res->body);
if ( exists $tokens{'oauth_token'}
and exists $tokens{'oauth_token_secret'})
{
return {result => \%tokens};
}
}
return {error => "requestToken() error" . Dumper $tx };
}
sub getAuthToken {
my ($class, $tokens) = @_;
my $url =
"$TWITTER_OAUTH_API/oauth/authorize?oauth_token=" . $tokens->{'oauth_token'};
my $client = Mojo::UserAgent->new->inactivity_timeout(10);
my $tx = $client->get($url);
if (my $res = $tx->success) {
my $html = $res->body;
my ($authenticity_token) = $html =~ /\bform_authenticity_token = \W([a-f0-9]{40})\W\;/;
return {result => $authenticity_token} if $authenticity_token;
}
return {error => "getAuthToken() error" . Dumper $tx };
}
sub getCallbackUrl {
my $class = shift;
my ($username, $password, $authenticity_token,
$oauth_token, $oauth_token_secret
) = @_;
my $client = Mojo::UserAgent->new->inactivity_timeout(10);
my $tx = $client->post_form(
"$TWITTER_OAUTH_API/oauth/authorize",
'UTF-8',
{ 'session[username_or_email]' => $_[0],
'session[password]' => $_[1],
'authenticity_token' => $_[2],
'oauth_token' => $_[3],
}
);
if (my $res = $tx->success) {
my $html = $res->body;
my ($callbackUrl) = $html =~ /\brefresh" content="\d;url=([^"\s]+?)">/;
return {result => [$oauth_token_secret, $callbackUrl]}
if $callbackUrl;
}
return {error => "getCallbackUrl() error" . Dumper $tx };
}
sub getAccessToken {
my ($class, $oauth_token_secret, $callbackUrl) = @_;
my $request = $class->makeOAuthRequest(
'access token',
from_url => $callbackUrl,
consumer_key => $CONSUMER_KEY,
consumer_secret => $CONSUMER_SECRET,
request_url => "$TWITTER_OAUTH_API/oauth/access_token",
token_secret => $oauth_token_secret,
);
my $client = Mojo::UserAgent->new->inactivity_timeout(10);
my $tx = $client->post($request->to_url);
if (my $res = $tx->success) {
my %tokens = map { split /=/ } (split /&/, $res->body);
return {result => \%tokens} if exists $tokens{'oauth_token_secret'};
}
return {error => "getAccessToken() error" . Dumper $tx };
}
package main;
use Data::Dumper;
use Mojolicious::Lite;
use Mojo::ByteStream 'b';
use Mojo::UserAgent;
use Mojo::Headers;
### dispatchers
any '/api/(*path)' => sub {
my $self = shift;
$self->session(expires => 1);
#my $ua = $self->req->headers->user_agent;
#print "User agent: $ua\n";
warn Dumper $self->req;
my $headers = $self->req->headers;
# my $clen = $headers->content_length;
# my $ctype = $headers->content_type;
my $authz = $headers->header('authorization');
# WATCH !
#if (!$authz)
#{
# $self->res->code(403);
#}
# print "Content len:".$clen."\n";
# print "Content type:".$ctype."\n";
# print "Auth:".$authz."\n";
my $authString = $self->req->headers->authorization =~ /^Basic (\S{1,128})$/
if $self->req->headers->authorization;
my ($username, $password) = split /:/, b($authString || "")->b64_decode;
my $user = Model->getUserInfo($username, $password);
# using stash('path') allows us to get the second
# part of the path, after api
#
# using url->path gets the full path.
#my $path = $self->req->url->path;
my $path = $self->stash('path');
# we can also get user with:
# $self->req->url->query->param('user');
my $useree = $self->req->param('user');
my $params = $self->req->params;
print "Params:".$params."\n";
print "Path:".$path."\n";
print "User:".$useree."\n";
my $no_auth_request;
if ($user && $user->oauth_token) {
$no_auth_request = 0;
}
elsif($path !~ /^\/?(?:search|trend)/) {
$self->res->code(401);
return $self->render_text("Forbidden!");
}
else { $no_auth_request = 1 }
my $request_method = $self->req->method;
my $query_params = $self->req->query_params->to_string || "";
my $body_params = $self->req->body_params->to_string || "";
my $url = $path =~ /^\/?(?:search|trend)/ ? $TWITTER_SEARCH_API : $TWITTER_API;
$url = $query_params ? "$url/$path?$query_params" : "$url/$path";
my %args = map { split /=/ } (split /&/, $body_params);
for my $key (keys %args) {
$args{$key} = b($args{$key})->url_unescape->decode('UTF-8');
$args{$key} =~ s/\+/ /g;
}
my $request = OAuth->makeOAuthRequest(
'protected resource',
request_method => $request_method,
request_url => $url,
token => $user->oauth_token,
token_secret => $user->oauth_token_secret,
nonce => b(rand(1) . $username. time() )->md5_sum,
extra_params => \%args,
) unless $no_auth_request;
my $client = Mojo::UserAgent->new->inactivity_timeout(10);
my $tx;
# warn Dumper \%args;
if ($request_method eq 'GET') {
$tx = $client->build_tx($request_method => $url);
}
elsif ($request_method eq 'POST') {
$tx = $client->build_form_tx($url, \%args);
}
$tx->req->headers->header('Authorization' => $request->to_authorization_header)
unless $no_auth_request;
$self->res->headers->header('Content-type' => 'application/json');
$client->process($tx);
warn Dumper $tx->res->body;
return $self->render_text($tx->res->body);
} => 'api';
get '/' => 'index';
# OAuth process
get '/oauth' => sub {
my $self = shift;
#return
# unless $self->helper( basic_auth => realm => $BASIC_AUTH );
my $res = OAuth->requestToken;
return $self->render_text($res->{'error'})
if exists $res->{'error'};
my $tokens = $res->{'result'};
$res = OAuth->getAuthToken($tokens);
return $self->render_text($res->{'error'})
if exists $res->{'error'};
my $authenticity_token = $res->{'result'};
$self->session(
oauth_token => $tokens->{'oauth_token'},
oauth_token_secret => $tokens->{'oauth_token_secret'},
authenticity_token => $authenticity_token,
);
$self->render('oauth');
} => 'oauth';
post '/oauth' => sub {
my $self = shift;
#return
# unless $self->helper( basic_auth => realm => $BASIC_AUTH );
my $username = $self->session('username') || $self->param('username');
my $password = $self->param('password');
my $error = Model->validate($username, $password, undef, 1);
return $self->render_text($error) if $error;
my $res = OAuth->getCallbackUrl(
$username, $password,
$self->session('authenticity_token'),
$self->session('oauth_token'),
$self->session('oauth_token_secret'),
);
return $self->render_text($res->{'error'})
if exists $res->{'error'};
$res = OAuth->getAccessToken(@{$res->{'result'}});
return $self->render_text($res->{'error'})
if exists $res->{'error'};
Model->createUpdateUser($username, $password, $res->{'result'});
$self->redirect_to("/echo");
} => 'oauth';
get '/echo' => sub {
my $self = shift;
$self->session(expires => 1);
return $self->render_text("OK! Now set $HOSTURI as your twitter API!");
} => 'echo';
app->secret(b($SECRET . time())->md5_sum);
app->log->path("/tmp/platter-$$.log");
app->log->level($DEBUG);
app->start;
__DATA__
@@ layouts/main.html.ep
<!DOCTYPE html>
<html>
<head>
<title>Platter</title>
</head>
<body>
<div id="main">
<%= content %>
</div>
<div id="footer">
<hr>
</div>
</body>
</html>
@@ index.html.ep
%layout 'main';
<p>This is another twitter api proxy server written in Perl.</p>
@@ oauth.html.ep
% layout 'main';
<div id="oauth">
<form id="oauth-form" action="<%= url_for %>" method="POST">
<input type="hidden" name="authenticity_token" value="<%= session 'authenticity_token' %>">
<input type="hidden" name="oauth_token" value="<%= session 'oauth_token' %>">
<label for="username">Twitter Username:</label>
<input type="text" id="username" name="username" value="<%= session 'username' %>"><br>
<label for="password">Twitter Password:</label>
<input type="password" id="password" name="password" value=""><br>
<input type="submit" name="oauth" value="Get Twitter OAuth Token!">
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment