How do I add more than one over method to a mojolicious route?
Asked Answered
G

3

6

I have the following code:

$r->find('user')->via('post')->over(authenticated => 1);

Given that route I can get to the user route passing through the authenticated check that is setup using Mojolicious::Plugin::Authentication.

I want add another 'over' to that route.

$r->find('user')->via('post')->over(authenticated => 1)->over(access => 1);

That appears to override authenticated 'over' though.

I tried breaking up the routes with names like:

 my $auth = $r->route('/')->over(authenticated => 1)
     ->name('Authenticated Route');

 $access = $auth->route('/user')->over(access => 1)->name('USER_ACCESS');

That didn't work at all though. Neither of the 'over's are being accessed.

My routes are things like /user, /item, set up using MojoX::JSON::RPC::Service. So, I don't have things like /user/:id to set up sub routes.( not sure that matters ) All routes are like /user, sent with parameters.

I've got a condition like:

$r->add_condition(
    access => sub {
        # do some stuff
    },
);

that is the 'access' in $r->route('/user')->over(access => 1);

In short, the routes work fine when using:

$r->find('user')->via('post')->over(authenticated => 1);

But I'm unable to add a 2nd route.

So, what am I missing in setting up these routes with multiple conditions? Is it possible to add multiple conditions to a single route /route_name?

Gavette answered 9/8, 2012 at 15:48 Comment(2)
I've noticed the same thing where I was implementing RBAC. I wanted access grants based on privileges to behave like a tree, which would mean chaining overs. Didn't work out. I guess that's why they gave us bridges. :)Norling
My problem was that I have the route in an 'add_condition' route modifier, as shown in my code above. So, I was unable to bridge to them. I guess I could move the condition into a module, as a function, and use a bridge. As it is, I placed it in a before_dispatch hook.Gavette
A
2

You can just use both conditions in over like in this test:

use Mojolicious::Lite;

# dummy conditions storing their name and argument in the stash
for my $name (qw(foo bar)) {
    app->routes->add_condition($name => sub {
        my ($route, $controller, $to, @args) = @_;
        $controller->stash($name => $args[0]);
    });
}

# simple foo and bar dump action
sub dump {
    my $self = shift;
    $self->render_text(join ' ' => map {$self->stash($_)} qw(foo bar));
}

# traditional route with multiple 'over'
app->routes->get('/frst')->over(foo => 'yo', bar => 'works')->to(cb => \&dump);

# lite route with multiple 'over'
get '/scnd' => (foo => 'hey', bar => 'cool') => \&dump;

# test the lite app above
use Test::More tests => 4;
use Test::Mojo;

my $t = Test::Mojo->new;

# test first route
$t->get_ok('/frst')->content_is('yo works');
$t->get_ok('/scnd')->content_is('hey cool');

__END__
1..4
ok 1 - get /frst
ok 2 - exact match for content
ok 3 - get /scnd
ok 4 - exact match for content

Works fine here with Mojolicious 3.38 on perl 5.12.1 - @DavidO is right, maybe bridges can do the job better. :)

Annabellannabella answered 5/9, 2012 at 15:17 Comment(0)
C
0

In my case I use two under methods:

$r =  $app->routes;
$guest =  $r->under->to( 'auth#check_level' );
$user  =  $r->under->to( 'auth#check_level', { required_level => 100 } );
$admin =  $r->under->to( 'auth#check_level', { required_level => 200 } );

$guest->get( '/' )->to( 'main#index' );
$user->get( '/user' )->to( 'user#show' );
$super_admin =  $admin->under->to( 'manage#check_level', { super_admin => 100 } );
$super_admin->get( '/delete_everything' )->to( 'system#shutdown' );

In this example when any of routes match some under will be called

'/' -> auth#check_level -> main_index
'/user' -> auth#check_level { required_level => 100 } -> 'user#show'
'/delete_everything' -> auth#check_level { required_level => 200 } -> 'manage#check_level', { super_admin => 100 } -> 'system#shutdown'

As you can see before target action in your controller will be run another action called: auth#check_level and manage#check_level

In each those extra actions you just compare stash->{ required_level } with session->{ required_level } you have set when authorize user

package YourApp::Controller::Manage;

sub check_level {
    my $self =  shift;

    my $user_have =  $self->session->{ required_level };
    my $we_require =  $self->stash->{ required_level };
    # 'system#shutdown' will be called if user has required level
    return 1   if  $user_have >= $we_require; 


    $self->redirect_to( '/you_have_no_access_rights' );
    return 0; #This route will not match. 'system#shutdown' will not be called
}

PS Of course I may use cb or just CODEREF which are "close same" to controller action:

$r->under({ cb => \&YourApp::Controller::auth::check_level });
$r->under( \&YourApp::Controller::auth::check_level ); # "same"

But I prefer ->to( 'controller#action' ) syntax. It looks much better

Combo answered 4/12, 2016 at 19:3 Comment(0)
M
0

What if we use this approach?

# register condition
$r->add_condition(
    chain => sub {
        my ($route, $controller, $captures, $checkers) = @_; 

        for my $checker (@$checkers) {
            return 0 unless $checker->($route, $controller, $captures);
        }

        return 1;
    },
);

# ...
# example of using
$r->get('/')->over(chain => [\&checker1, \&checker2])->to(cb => \&foo)->name('bar');
Mcguinness answered 23/10, 2017 at 23:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.