I have looked over this code and I can not understand the weirdness it exhibits. For a lack of understanding all I know
$c->res->redirect('qbo/home');
is being ignored, in favor of the redirect in the following if else condition. In other words, I always end up at the OAuthentication website.
If I block comment out the else condition I end up where I want to go qbo/home
sub index :Path :Args(0) {
my ($self, $c) = #_;
# Check to see if we have QBO::OAuth object in our user's session
# Create new object in session if we don't already have one
if(!($c->session->{qbo})) {
$c->log->info('Creating QBO::OAuth, save in user session');
$c->session->{qbo} = QBO::OAuth->new(
consumer_key => 'qyprddKpLkOclitN3cJCJno1fV5NzcT',
consumer_secret => 'ahwpSghVOzA142qOepNHoujyuHQFDbEzeGbZjEs3sPIc',
);
}
# Now we set our object variable to the session old or new
my $qbo = $c->session->{qbo};
######### GOTO 'qbo/home' ##########
$c->res->redirect('qbo/home');
####################################
if($c->req->params->{oauth_token}) {
$c->log->info('Now Redirect to access_endpoint');
# Get realmId and save it to our QBO::OAuth object in user session
$qbo->realmId($c->req->params->{realmId});
# Call QBO::OAuth->request_access_token
my $r = $qbo->request_access_token($c->req->params->{oauth_verifier});
$c->res->redirect('qbo/home');
} else {
my $callback = 'http://www.example.com/qbo';
# Request a token
my $r = $qbo->request_token($callback);
if($qbo->has_token) {
#Continue on down, Redirect to auth_user_endpoint
$c->res->redirect($qbo->auth_user_endpoint . '?oauth_token=' . $qbo->token);
}
}
}
Seems I am missing some basic fundamental about how this works. Any clues appreciated
From the fine manual...
This is a convenience method that sets the Location header to the redirect destination, and then sets the response status. You will want to return or $c->detach() to interrupt the normal processing flow if you want the redirect to occur straight away.
Note also the warning on that manual page about redirecting to a relative URL - you shouldn't do it. For your use-case, I'd recommend getting into the habit of using:
return $c->res->redirect($c->uri_for('qbo/home'));
or
$c->res->redirect($c->uri_for('qbo/home')) && $c->detach();
depending on your preference.
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've currently got a small script running that sends a 401 to the client, upon cancelling and not providing user details the script will return nothing.
I'd like to send a redirect to the page they have come from instead.
The main subroutine looks like this;
#!usr/bin/perl
use strict;
use CGI;
sub checkAuth {
my ($user, $pass) = &getAuthUsers(); # Get the user and pass of already authenticated users.
unless ($user) {
&sendAuthenticationHeader(); # Send 401
}
# Check user against DB and return 1 for success.
if ( &checkUser($user, $pass) eq 'Y') { return 1 };
else { # This is the redirect I'm trying to issue.
my $cgi = CGI->new();
print $cgi->redirect($ENV{HTTP_REFERER}); # Redirect to the referer url
exit;
}
}
Unfortunately whenever I try to send new headers it's just received as plain text.
Any help is appreciated, thanks in advance.
sendAuthenticationHeader() emits a header with a 401 status code.
print $cgi->redirect($ENV{HTTP_REFERER}); emits a header with a 302 status code. Of course, since you've already emitted a header, this gets treated as the body.
There's no point to return a 401 if you want to redirect. Change your code to
sub checkAuth {
my ($user, $pass) = getAuthUsers();
if (!$user || !checkUser($user, $pass)) {
print CGI::redirect($ENV{HTTP_REFERER});
exit;
}
}
Notes:
Removed incorrect &. Don't tell Perl to ignore the prototype of subs. Address the underlying issue instead if required.
The return value of checkUser is boolean, so it should return either a true or a false value (e.g. 0 or 1), not two true values (e.g. N or Y). The above code assumed you fixed this.
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
To optimize my Perl application I need to work with async HTTP requests, so I can handle other operations once the HTTP response is finish. So I believe my only option is to work with HTTP::Async module. This works fine for simple requests, but I need to catch cookie header from one response and send it with next one, so I need to read headers. My code is:
...
$async->add($request);
while ($response = $async->wait_for_next_response)
{
threads->yield(); yield();
}
$cookie = $response->header('Set-Cookie');
$cookie =~ s/;.*$//;
$request->header('Cookie' => $cookie);
...
but it's not working, as it ends with an error Can't call method "header" on an undefined value. Obviously $response is undef. How can I catch headers before $response gets undef?
while ($response = $async->wait_for_next_response)
{
threads->yield(); yield();
}
Is guaranteed not to finish until $response is false. The only false value wait_for_next_response will return is undef. You need to either extract the cookie inside the loop, or cache the last good response inside the loop.
Something like
my $last_response;
while ($response = $async->wait_for_next_response)
{
$last_response = $response;
threads->yield(); yield();
}
should work, although I'm not sure you need the loop at all. It's hard to tell without a complete program.
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/