Hi all.
I'm pretty new to Mojolicious and so far I love it. I'm writing a REST API and I was wondering what is the best way to restrict/allow CRUD operations on specific resources based on different user roles.
Let's say I have a role: 'A'.
I want to give read/write access to users with role 'A' to:
/api/resource1
/api/resource2
But read-only access to:
/api/resource3
And restrict any access to:
/api/resource4
Thanks in advance!
I found something for ruby that does this here: https://github.com/stffn/declarative_authorization and thought there was something similar for Mojolicious (it would be very nice). If someone else doesn't have other ideas on how to do this I will probably do what byterock suggested.
Thank you for the suggestion, I appreciate it very much.
--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To post to this group, send email to mojol...@googlegroups.com.
To unsubscribe from this group, send email to mojolicious...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/mojolicious?hl=en.
I try to keep my authorization code in one place as far as possible,
instead of sprinkling «if ($user->can('…')) …» checks across my code
base. (Sometimes I can't avoid adding a specific check to an action,
but it's an exception.)
To this end, I have routing shortcuts like 'allow_roles' and 'allow_if',
which I use to construct authorisation bridges when defining routes. For
example,
my $auth = $r->bridge->to('auth#allow_users');
my $accounts = $auth->allow_roles([qw/admin acctadmin acctmaint/]);
my $a = $accounts->waypoint('/accounts')->to('accounts#index');
$a->get('/list')->to('accounts#list');
$a->post('/import')->to('accounts#import');
("admin", "acctadmin", etc. are roles assigned to a user.)
This can make it harder to define and think about your routes (because
you want to minimise the number of bridges), but it's easier to review
the code and ensure that you're doing the right thing, and you don't
have to worry about forgetting to check permissions in some action.
I know some people don't get along with this sort of thing, but I'm
quite happy with it.
-- ams
P.S. Here's a longer, unedited example from one of my projects:
# Bid-related routes, open to any customer and (bid) administrator.
my $bid_common = $auth->allow_if(sub {
$_[1]->{customer_id} ||
$_[1]->has_any_role('admin', 'bidadmin', 'bidmaint')
});
my $bids = $bid_common->waypoint('/bids')->to('bids#index');
my $check = $bids->bridge->to('portfolios#check');
$check->get('/list')->to('bids#list');
$check->get('/list_submitted')->to('bids#list_submitted');
$check->get('/create')->to('bids#form');
$check->post('/create')->to('bids#create');
$check->get('/archive')->to('bids-archive#archive');
$check->get('/list_archived')->to('bids-archive#list_archived');
$check->waypoint('/no-bids')->to('bids#no_bids')
->route('/list')->to('bids#list_no_bids');
$check->route('/save-no-bids')->via('post')->to('bids#save_no_bids');
# Only an approver can approve bids
my $approver = $bids->allow_roles([qw/approver/]);
my $sms = $approver->bridge->to('confirm#by_token');
$sms->post('/approve')->to('bids#approve');
# Only the owner of a bid can change it
my $cbids = $bids->bridge('/:bid_id', bid_id => $id)->to('bids#allow_owner');
$cbids->get('/update')->to('bids#form');
$cbids->post('/update')->to('bids#update');
$cbids->post('/delete')->to('bids#delete');
$cbids->route->to('bids#display_bare');
# The following are available only to internal users (i.e. any sort
# of administrator), but the last couple of actions are not open to
# "bidmaint"
my $bidmaint = $bids->allow_if(sub {$_[1]->is_internal});
$bidmaint->get('/overview')->to('bids#overview');
$bidmaint->waypoint('/calendar')->to('bids-calendar#index')
->route('/list')->to('bids-calendar#list');
$bidmaint->get('/mov')->to('bids#mov');
$bidmaint->get('/volumes')->to('rpo#details');
$bidmaint->get('/breakdown')->to('bids#breakdown');
my $bidadmin = $bidmaint->allow_roles([qw/admin bidadmin/]);
$bidadmin->post('/export')->to('bids-export#index');
$bidadmin->post('/internal_approve')->to('bids#internal_approve');
…and here's the corresponding output from «myapp routes»:
+/ *
+/bids * bids
+/ *
+/list GET list
+/list_submitted GET list_submitted
+/create GET create
+/create POST create
+/archive GET archive
+/list_archived GET list_archived
+/no-bids * nobids
+/list * list
+/save-no-bids POST savenobids
+/ *
+/ *
+/approve POST approve
+/:bid_id * bid_id
+/update GET update
+/update POST update
+/delete POST delete
+/ *
+/ *
+/overview GET overview
+/calendar * calendar
+/list * list
+/mov GET mov
+/volumes GET volumes
+/breakdown GET breakdown
+/ *
+/export POST export
+/internal_approve POST internal_approve
One way would be to check the role in each action you want to
protect. First you might define $self->role as a helper:
helper role => sub { return $role }; ## lite-app version
Then in each handler check the role.
sub resource3_handler {
my $self = shift;
if( $self->role eq 'A' ) {
if( $self->method ne 'GET' ) {
$self->render(status => 403);
return;
}
}
...
}
sub resource4_handler {
my $self = shift;
if( $self->role eq 'A' ) {
$self->render(status => 403);
return;
}
...
}
You could also put the logic in a single under() route that all routes
use.
Scott
--
Scott Wiersdorf
<scott.w...@gmail.com>
Thank you all for your suggestions. You've given me a lot to think about. I really like the idea of the Authorization Mojolicious plugin. It would not only help me, but also the entire community. I'm looking forward to see if that solution is implemented. Thank you all!
I sent Ben a pull request for some updates. I think this simplifies the
auth code nicely (basically everything revolves around accessing
current_user)
https://github.com/ewildgoose/Mojolicious-Plugin-Authentication
For authorization inspiration my favourite rails projects are:
https://github.com/ryanb/cancan
https://github.com/DocSavage/rails-authorization-plugin
They both vary in whether they put the auth into the model or a separate
auth model. However, I like the basic idea of security being a function
of the user object and the model record (obviously only where that level
of granularity is needed...)
Good luck
Ed W
I utterly and completely forgot about that one! :(
Let me fix that up :)
--
Ben van Staveren
phone: +62 81 70777529
email: benvans...@gmail.com
--
As Ed mentioned he put in a pull request that I merged and then totally forgot
about (been too busy with work and my kid), but it's been merged and a new
version of the plugin has been released to CPAN.
A few things changed that might break existing code but I've tried to put in
some warnings about deprecated options and helpers that will be around until
the next release.
Enjoy :)
On 03/30/2012 05:53 AM, Ed W wrote:
--