i have a problem making simple API request to the Yammer (https://www.yammer.com/api_doc.html). I need to get https://www.yammer.com/api/v1/groups.xml (Groups: A list of groups).
I'm trying to use Net::OAuth::Simple. Here is my Yammer.pm:
package Yammer;
use strict;
use base qw(Net::OAuth::Simple);
sub new {
my $class = shift;
my %tokens = #_;
return $class->SUPER::new( tokens => \%tokens,
urls => {
authorization_url => "https://www.yammer.com/oauth/authorize",
request_token_url => "https://www.yammer.com/oauth/request_token",
access_token_url => "https://www.yammer.com/oauth/access_token",
},
protocol_version => '1.0a',
);
}
sub view_restricted_resource {
my $self = shift;
my $url = shift;
return $self->make_restricted_request( $url, 'GET' );
}
sub update_restricted_resource {
my $self = shift;
my $url = shift;
my %extra_params = #_;
return $self->make_restricted_request($url, 'POST', %extra_params);
}
1;
And here is my main program:
use Yammer;
# Get the tokens from the command line, a config file or wherever
my %tokens = (
consumer_key => 'Baj7MciMhmnDTwj6kaOV5g',
consumer_secret => 'ejFlGBPtXwGJrxrEnwGvdRyokov1ncN1XxjmIm34M',
callback => 'https://www.yammer.com/oauth/authorize',
);
my $app = Yammer->new(%tokens);
# Check to see we have a consumer key and secret
unless ($app->consumer_key && $app->consumer_secret) {
die "You must go get a consumer key and secret from App\n";
}
# If the app is authorized (i.e has an access token and secret)
# Then look at a restricted resourse
if ($app->authorized) {
my $response = $app->view_restricted_resource;
print $response->content."\n";
exit;
}
# Otherwise the user needs to go get an access token and secret
print "Go to " . $app->get_authorization_url( callback => 'https://www.yammer.com/oauth/authorize?rand=' . rand() ) . "\n";
print "Then hit return after\n";
<STDIN>;
my ($access_token, $access_token_secret) = $app->request_access_token($_);
I'm getting messages like
Go to
https://www.yammer.com/oauth/authorize?oauth_token=2sxBkKW1F1iebF2TT5Y7g&callback=https%3A%2F%2Fwww.yammer.com%2Foauth%2Fauthorize%3Frand%3D0.0045166015625
And authorizing application on this URL. After that i see message like:
You have successfully authorized the
following application: 2GIS_yammer
To complete the authorization go back
to the 2GIS_yammer application and
enter the following code:
869A
But what next? Where i must enter this number? How to perform request i need?
Thanks.
Roman
probably the number that you get after the authorization step is the oauth_verifier string that needs to be sent along with REQUEST token in order to get ACCESS token.
This is mandatory part of oAuth 1.0a implementations (which I think is the most common implementation used now, because 2.0 is still a draft and there aren't many libraries that implement it).
I guess that you don't send callback URL to the provider, and he doesn't know where to redirect the user after authorization. When the provider doesn't know a callback URL, he cannot redirect the user back to your (consumer) application.
In that case the specification says that it should print the verifier string on the screen, so you (the user) can take it manually and give it to your (consumer) application , and so to build the request for ACCESS TOKEN.
If you DO provide callback URL (in your first request for REQUEST token), then most probably you will not get the screen with this number, but instead, you (the user) will be redirected to the callback URL with it automatically.
E.g. if your callback url is: http://myapp.com/oauth/callback, then the provider will redirect the user to your callback url with proper values in the query string.
redirect: http://myapp.com/oauth/callback?oauth_token=xxxx&oauth_verifier=yyyy
Then your application should take the verifier string and add it as a parameter to the request for ACCESS TOKEN (as you have done previously with the other parameters like nonce, timestamp, oauth_token, etc.)
As a response to this last request (with oauth_verifier string included) you should get ACCESS TOKEN.
Here is a good explanation about the oauth_verifier string and why it was introduced in the protocol:
http://hueniverse.com/2009/04/explaining-the-oauth-session-fixation-attack/
Related
The Mojolicious framework states next:
Any aspect of the application (helpers, plugins, routes, etc.) can be introspected from Test::Mojo through the application object.
But when helper, for example, $c->current_user deals with session it fails.
The session data is not available and I can not access it from test:
$t->app->session # {}
Thus $t->app->current_user fails too.
How to access session data from test?
UPD The test
use Mojo::Base -strict;
use Mojolicious::Lite;
use Test::More;
use Test::Mojo;
get '/set_session' => sub {
my $c = shift;
$c->session->{ user_id } = 1;
$c->render( text => $c->session->{ user_id } );
};
get '/get_session' => sub {
my $c = shift;
$c->render( text => $c->session->{ user_id } );
};
my $t = Test::Mojo->new;
$t->get_ok( '/set_session' )->status_is(200);
is $t->app->session->{ user_id }, 1, 'Session available from test script';
$t->get_ok( '/get_session' )->status_is(200)
->content_is( 1 );
done_testing();
UPD test result
ok 1 - GET /set_session
ok 2 - 200 OK
not ok 3 - Session available from test script
# Failed test 'Session available from test script'
# at t/session.t line 22.
# got: undef
# expected: '1'
ok 4 - GET /get_session
ok 5 - 200 OK
ok 6 - exact match for content
1..6
# Looks like you failed 1 test of 6.
UPD
It seems that Mojo::Test object should save session object in addition to the request and response objects from the previous transaction
To test helpers in context of last request I write next role:
package Test::Mojo::Role::Helper;
use Mojo::Base -role;
sub helper {
my( $t ) = #_;
$t->tx->req->cookies( #{ $t->tx->res->cookies } );
$t->app->build_controller( $t->tx );
}
1;
Then use it as next:
use Test::Mojo;
my $t = Test::Mojo->with_roles( '+Helper' )->new( 'MyApp' );
$t->post_ok( '/login', json => { contact => $user_c, password => $user_p } )
->status_is( 200 );
is $t->helper->uid, 1, "Authorized user has identificator";
is $t->helper->role, 'user', "Authorized user has 'user' privilege";
UPD More robust solution
package Test::Mojo::Role::Helper;
use Mojo::Base -role;
my $req_context; # Here is controller object
sub helper { $req_context }
sub hook_context {
my( $t ) = #_;
$t->app->hook( after_dispatch => sub{ $req_context = shift });
$t;
}
1;
The testing is same with next small difference. When application is constructed we should hook to after_dispatch event:
my $t = Test::Mojo
->with_roles( '+Helper' )
->new( 'App' )
->hook_context;
The Test::Mojo class does not give you direct access to the session contents. The test class represents a client of your Mojolicious application, and the client does not have direct access to the session cookie either (well, it's just base64-encoded JSON so it's not exactly secret, but still …).
The “proper” way to test the session is to check that the app behaves correctly regarding the session, not just to check that the session was set to some value. That's effectively what your /get_session endpoint does. Of course you shouldn't just add such an endpoint for testing, but consider how the session fits into your requirements. E.g. as a BDD-style scenario:
Feature: the secret page
there is a secret page that should be only visible to logged-in users.
Background:
Given a user "test:test123"
Given a new client
Scenario: users cannot see the page when they are not logged in
When I visit the /secret page
Then I get a 404 response
Scenario: users can see the page after logging in
Given I log in as "test:test123"
When I visit the /secret page
Then I see "this is the secret"
The $t->app->session does not contain the session because the session data is loaded into the controller's stash. This only exists for the duration of the request. In particular app->session is merely a helper that delegates to the current controller, not a primary method of the application.
If you really need to peek into the session cookie, this might be the least insane way to do it, short of inflating a controller object:
my ($session) = grep { $_->name eq $t->app->sessions->cookie_name } $t->ua->cookie_jar->all->#*;
$session = $session->value =~ s/--[^-]+$//r; # strip signature
$session =~ tr/-/=/;
$session = $t->app->sessions->deserialize->(Mojo::Util::b64_decode $session);
I'm trying to get away from Basic Auth in my Mojolicious application. I am able to detect the absence of a session key and redirect to a login page. The login page then posts to my application and I authenticate to a back end process. That back end process is returning success and then my mojo app sets the session like thus:
$self->session( user => $name, groups => $groups );
in debugging this, $name and $group are both defined and valid. I then wish to redirect into the "protected" space of my app. The redirect lands in the right place but then fails to detect the $self->session('user') (is undef when debugging) I end up redirecting back to login repeatedly.
I'll include snippets of the setup below. What am I missing?
MyApp.pm
my $r = $self->routes;
$r->route('/verify')->via('post')->to('util-auth#verify')->name('verify');
$r->route('/login')->via('get')->to('util-auth#login')->name('login');
my $app = $r->under('/myapp')->to('util-auth#check');
$app->route('/foo')->via('get')->to('controller-api#foo')->name('foo');
MyApp::Util::Auth
sub verify {
my $self = shift;
my $name = $self->param('username');
my $pass = $self->param('password');
my $dest = "/myapp/foo"; # in the protected area
if ( $self->authenticate($name, $pass) ) {
my $groups = $self->get_groups($name);
$self->session(
user => $name,
groups => $groups,
);
}
else {
$self->flash( message => "invalid login..." );
}
$self->redirect_to($dest);
}
sub login {
my $self = shift;
$self->render(); # renders the login form
}
sub check {
my $self = shift;
my $user = $self->session('user');
return 1 if defined $user;
$self->redirect_to('/login');
return 0;
}
I was having a similar problem and I ended up putting these in stash. I think session is string based, mainly because a cookie is set with session info.
Why your verify function accept name, pass via #_ variable?
May be need to use $self->param('name') and $self->param('pass')?
See working example here:
https://gist.github.com/Logioniz/bdf6f22c00fc51798c43
Hello I have put together a script for a twitter timeline it works apart from i dont know how to authorize my twitter api key my led sign is just saying "Bad Authentication data"
here is my code
#!/usr/bin/perl
require LWP::UserAgent;
use JSON;
my $lwpua = LWP::UserAgent->new;
my $uagent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.6) Gecko/20060728 Firefox/1.5.0.6";
my #header = ( 'Referer' => 'http://api.twitter.com/', 'User-Agent' => $uagent );
my $twuser = '<twitter_name>';
my $twurl = "http://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=$twuser";
my $response = $lwpua->get( $twurl, #header );
my $return = $response->content;
my $json = JSON->new->allow_nonref;
my $json_text = $json->decode($return);
my #tweets = #{$json_text};
my $message;
foreach $tweet (#tweets) {
$message .= $tweet->{text} . "\n";
}
use Device::MiniLED;
my $sign = Device::MiniLED->new( devicetype => "sign" );
$sign->addMsg(
data => "$message",
effect => "scroll",
speed => 4
);
$sign->send( device => "/dev/ttyUSB0" );
1;
First: use strict; and use warnings;. Even if you're the awesomest programmer ever, this should be your first port of call if you're having problems. (And everyone makes typos).
Secondly: $json_text is a hash ref, not an array ref. You probably want to use values or similar.
Thirdly: Bad Authentication Data is a twitter api error, not a code error. You need to authorize it with oAuth, and you're doing no twitter auth at all. From: https://dev.twitter.com/overview/api/response-codes
215 - Typically sent with 1.1 responses with HTTP code 400. The method requires authentication but it was not presented or was wholly invalid.
E.g. you can't do what you're doing without authenticating. I think what you need is this:
https://dev.twitter.com/oauth/overview/application-owner-access-tokens
Specifically - the 'easy answer' is generate an account specific authentication token, and send that in your request.
Once you have done this this web page on Twitter:
https://dev.twitter.com/oauth/tools/signature-generator/
allows you to generate a (time limited) example command that you could use to fetch your timeline. But you'll most likely need to build authentication into your script in order to do what you're trying to do. (user_timeline.json is a restricted access API). (There's a 'test oAuth' button on the app webpage).
Reading through the docs on creating authentication tokens, makes me thing that installing Net::Twitter or Net::Twitter::Lite might be the way to go.
First follow the instructions here: https://dev.twitter.com/oauth/overview/application-owner-access-tokens
Specifically, on https://apps.twitter.com/ you need to:
create an application
generate a token (from the application page).
(Under 'keys and access tokens' on the app specific page).
This will give you the 4 things you need to speak to twitter:
a consumer key
a consumer secret
an access token
an access token secret
By default, your access token will be read only. This is fine for what you want to do.
The process for turning them into Twitter auth is a bit complicated and involves RSA-HMAC encryption. So just let Net::Twitter do it for you: (I've removed my keys, because I'm not quite daft enough to post the equivalent of my Twitter password)
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Net::Twitter;
my $twitter = Net::Twitter->new(
traits => [qw/API::RESTv1_1/],
consumer_key => 'long_string_of_stuff',
consumer_secret =>
'long_string_of_stuff',
access_token => '12345-long_string_of_stuff',
access_token_secret =>
'long_string_of_stuff',
ssl => 1,
);
my $tweets = $twitter->user_timeline();
print Dumper \$result;
my $message;
foreach my $tweet ( #{$tweets} ) {
$message .= $tweet->{text} . "\n";
}
print $message;
Tested this with my account, and it prints a list of my recent tweets, which I think was what you wanted?
I have a CGI server side script that accepts GET and POST, with login parameters.
I want to test it to make sure it is not vulnerable. So the plan is to use Perl LWP, and send login parameters in GET and POST, and compare the results. the interface has been changed, so that only in POST we can send user-name and password in session cookies ( not sure if that is a great idea ) , so how do i test it ? Here is what i have so far:
#!/usr/bin/perl
use LWP;
print "This is libwww-perl-$LWP::VERSION\n";
# Create a user agent object
use LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->agent("MyApp/0.1 ");
# Create a request
#my $req = HTTP::Request->new(POST => 'http://search.cpan.org/search');
#my $req = HTTP::Request->new(GET => 'https://qa.co.net:443/cgi-bin/n-cu.cgi');
my $req = HTTP::Request->new(GET => 'https://qa.co.net:443/cgi-bin/n-cu.cgi?mode=frameset&JScript=1&remote_user&login=foo&password=foo HTTP/1.1');
$req->content_type('application/x-www-form-urlencoded');
$req->content('query=libwww-perl&mode=dist');
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
# Check the outcome of the response
if ($res->is_success) {
print $res->content;
#print $res->code;
#print $res->message;
}
else {
print $res->status_line, "\n";
}
This is not going to do it, since it does not have the session cookie stuff. But might be a good start though. Is this the right way to test the GET and POST ?
Here is what was implemented in the cgi:
#cr_login for POST && login for GET -- leave GET param as it used to be.
if ($m eq 'GET' && defined($req->param('login'))) {
$msg = 'parameter "login" is invalid for this request type.';
+ my $seclog = $event_logging_directory . '/invalid_request.log';
+ open(S, ">>$seclog") or die $!;
+ my $logmsg = sprintf("%4d-%02d-%02d %02d:%02d:%02d",Today_and_Now())
+ . "|mode:" . $req->param('mode')
+ . "|login:" . $req->param('login')
+ . "|remote_addr:" . $ENV{REMOTE_ADDR}
+ . "|$msg\n";
+ print S $logmsg;
and :
POST request to n-cu.cgi should use parameter "cr_login". If the parameter "login" is passed in a post request, it should throw error and return to login screen.
GET request to n-cu.cgi should use the parameter "login". If the parameter "cr_login" is passed in a post request, it should throw error and return to login screen.
so here is how we do it:
Keep the session cookie and context alive :
my $browser = LWP::UserAgent->new(keep_alive => 10);
$browser->cookie_jar( {} );
$browser->agent('Mozilla/8.0');
#$browser->ssl_opts({ verify_hostname => 0 });
$browser->show_progress(1);
and later: print the response
print "Cookies:\n", Dumper($browser->cookie_jar()), "\n\n";
my $content = $response->as_string;
print "$content\n";
Sending password in a cookie? Nope.
Disallow GET for /login.
POST username and password to /login, over SSL.
In CGI, the GET/POST is indicated via the REQUEST_METHOD environment variable.
You cannot stop determined people from issuing a GET request to your server, but you can refuse to process it like so (untested code - you have to fill in details):
if ($ENV{REQUEST_METHOD} ne 'POST') {
# issue a redirect to a suitable error page, then return.
}
my $q = CGI->new();
my $user = $q->params('username');
my $password = $q->params('password');
my $encrypted_password = my_password_encryptor($password);
unless ( can_log_in($user, $encrypted_password) ) {
# issue an error message - redirect&return or fall-through...
}
else {
$session->set_user_logged_in();
}
Most people do not roll their own authentication or session handling. They mostly use one from CPAN, or one included with the larger app framework. If you're doing CGI, you can use CGI::Session.
You might give CGI::Application and/or its offspring a look. Those authors have already solved a bunch of the problems that you're encountering.
I would like to have my Plack app try several different means of authorizing the user. Specifically, check if the user is already authorized via a session cookie, then check for Digest authentication and then fall back to Basic.
I figured I could just enable a bunch of Auth handlers in the order I wanted them to be checked (Session, Digest, Basic). Unfortunately, the way that Plack::Middleware::Auth::Digest and Plack::Middleware::Auth::Basic are written they both return 401 if digest or basic auth doesn't exist, respectively.
How is this normally dealt with in Plack?
I do not have an implementation but I think I have the approach. You can do this "in-line" with Plack::Middleware::Conditional. So it would look like this but you'll have to fill in the missing conditions/tests. I didn't see an easy/obvious way but I suspect you might. Since you have the $env to pass around you should be able to set/check HTTP_/session stuff in the order you want and keep the state for the next handler to know if it should be enabled or not.
use Plack::Builder;
my $app = sub {
[ 200,
[ "Content-Type" => "text/plain" ],
[ "O HAI, PLAK!" ]
];
};
builder {
enable "Session::Cookie";
enable_if { my $env = shift;
# I don't know...
} "Auth::Digest",
realm => "Secured", secret => "BlahBlah",
authenticator => sub { $_[0] eq $_[1] };
enable_if { my $env = shift;
# I don't know...
} "Auth::Basic",
authenticator => sub { $_[0] eq $_[1] };
$app;
};
I think you are going to need to write your own middleware, since ideally (based on a very quick read of RFC 2617) when not authenticated you would return a WWW-Authenticate header with both Basic and Digest challenges (with Basic first, for user agents that only understand Basic).