REST resource permissions

636 views
Skip to first unread message

Chuck Finley

unread,
Feb 29, 2012, 9:25:04 PM2/29/12
to mojol...@googlegroups.com

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!

byterock

unread,
Mar 1, 2012, 9:02:50 AM3/1/12
to Mojolicious
Hmm good question!

Looking into this myself.

So far I do not think there is any great majik to be gained from MOJO
(or any restful server)

So far I am using the old stand by 'Role' and 'Privilege' pattern

ie
1) Each user is given a Role
2) Each Role is given a set of 'Privileges'


So in your case Role A would have the 'Privs'

resource1-CRUD
resource2-CRUD
resource3-R

and no 'privs' for 'resource4'

Implementation could be as simple as

package Bla::Resource4;

use strict;
use warnings;
use v5.10;

use base 'Mojolicious::Controller';

sub show {
my $self = shift;
my $profile = Bla::User->get($self->session('id'));
$self->redirect_to('Not_Allowed');
unless($profile->priv('resource4-R'));
...

sub delete {
my $self = shift;
my $profile = Bla::User->get($self->session('id'));
$self->redirect_to('Not_Allowed');
unless($profile->priv('resource4-CRUD'));
...

You can make this a fine grained as you like and even have it on a
user by user basis.

I myself prefer only restrictive 'privs' so I have fewer to deal
with.

Hope this helps

Chuck Finley

unread,
Mar 1, 2012, 12:04:28 PM3/1/12
to mojol...@googlegroups.com

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.

Abhijit Menon-Sen

unread,
Mar 1, 2012, 6:21:23 PM3/1/12
to mojol...@googlegroups.com
At 2012-02-29 21:25:04 -0500, cfmw...@gmail.com wrote:
>
> 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.

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

byterock

unread,
Mar 2, 2012, 6:33:50 AM3/2/12
to Mojolicious
I like it.

A good way to implement role~priv pattern.

My only comment would be how well does this scale up to say 500+ pages
and say 6 routes per page?

I guess you could break it up into different route controllers?

cheers
John


On Mar 1, 6:21 pm, Abhijit Menon-Sen <a...@toroid.org> wrote:

Roland Lammel

unread,
Mar 2, 2012, 8:16:38 AM3/2/12
to mojol...@googlegroups.com
I actually thought about implementing Mojolicious::Plugin::Authorization which should provide something like allow_roles (and/or allow_privilege if using privileges per role).

From my experience it is mostly ok to limit authorization per route endpoint, but on some occassions mure subtle control is required. This requires to have a helper for that too (like assert_roles, assert_privileges). That should allow most of the scenarios for authorization. Of course callbacks would be used, to actually get roles/privileges from the logged in user object.

### For the routing

my $auth = $r->bridge->to('auth#allow_users');
my $authorized = $auth->allow_roles('catfriend');
$authorized->get('/cats/show')->to('cats#show_cats');

### In your action handler
sub show_cats {
    ...
    ### Assert will throw unauthorized exception
    $self->assert_privileges(['show','cats']);
    $self->render(text => 'We have these secret cats: lady, bunny, foobar');
}

Packaging it up in a plugin might help mojo to give some guidance on how to handle that.
If I get something together I'll post it for review.

Cheers

+rl
--
Roland Lammel
QuikIT - IT Lösungen - flexibel und schnell
Web: http://www.quikit.at
Phone: +43 (676) 9737845
Email: r...@quikit.at

"Enjoy your job, make lots of money, work within the law. Choose any two."


Scott Wiersdorf

unread,
Mar 1, 2012, 12:04:10 AM3/1/12
to mojol...@googlegroups.com
On Wed, Feb 29, 2012 at 09:25:04PM -0500, Chuck Finley wrote:
> 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

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>

Chuck Finley

unread,
Mar 2, 2012, 11:26:14 AM3/2/12
to Scott Wiersdorf, mojol...@googlegroups.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!

byterock

unread,
Mar 29, 2012, 1:25:37 PM3/29/12
to Mojolicious


Well after playing around a bunch and pulling out what little hair I
have left I finally got something that works really well for me at
least

have a look at

https://github.com/byterock/mojolicious-plugin-authorization/blob/master/lib/Mojolicious/Plugin/Authorization.pm

I have yet to stick it in CPAN as it is just a first draft.

I sort of just implemented what Ben van Staveren did with
http://search.cpan.org/~madcat/Mojolicious-Plugin-Authentication-1.21/

It is an empty shell type api that you must supply 4 sub to that you
then use to 'Authorize' the current session.

Any comments would be great

Ed W

unread,
Mar 29, 2012, 6:53:50 PM3/29/12
to mojol...@googlegroups.com
On 29/03/2012 18:25, byterock wrote:
> I sort of just implemented what Ben van Staveren did with
> http://search.cpan.org/~madcat/Mojolicious-Plugin-Authentication-1.21/
>

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

Roland Lammel

unread,
Mar 29, 2012, 7:16:59 PM3/29/12
to mojol...@googlegroups.com
Hi there,

Funny thing is I started exactly the same some weeks ago, but was distracted with other things soon after.

https://github.com/lammel/mojolicious-plugin-authorization/blob/master/lib/Mojolicious/Plugin/Authorization.pm

I tried to have easy to grasp words also for the conditions (authorize_role, authorize_roles) and also provide helpers for assertion in code (assert_role, assert_roles), same should be there for privileges.

As some apps won't need privileges (roles surely suffice for simple apps), you just use the roles part ad provide a sub for check_user_role (to get the role for a user).

Don't know if shorter names would work too but I think "has" and "is" are a little to generic as condition names.

Nice work btw.


Cheers

+rl
--
Roland Lammel
QuikIT - IT Lösungen - flexibel und schnell
Web: http://www.quikit.at
Phone: +43 (676) 9737845
Email: r...@quikit.at

"Enjoy your job, make lots of money, work within the law. Choose any two."


Ben van Staveren

unread,
Mar 29, 2012, 9:26:21 PM3/29/12
to mojol...@googlegroups.com
Hey Ed,

I utterly and completely forgot about that one! :(

Let me fix that up :)

--
Ben van Staveren
phone: +62 81 70777529
email: benvans...@gmail.com

Ben van Staveren

unread,
Mar 29, 2012, 9:45:18 PM3/29/12
to mojol...@googlegroups.com
I like this one, especially since I was considering rolling something like
this into the Authentication module (where it really has no place since it's
not about authentication but authorization), and I didn't have time for it..
thank god! At least now there's a proper plugin that does it :)

--

Ben van Staveren

unread,
Mar 29, 2012, 9:47:03 PM3/29/12
to mojol...@googlegroups.com
Hello folks,

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:

--

byterock

unread,
Apr 2, 2012, 3:18:05 PM4/2/12
to Mojolicious
Hi Rolan sorry I did not see your code on github.

Mine is of course a little more generic, (well dumb really as I leave
all the guts up to the user) than you invocation.

The main reason for the sort names was it makes one's code very easy
to read in a natural way.

Looking at some of the other comments I think I will make it required
only one or both of the 'has_priv' and 'is_role' code stubs. That way
one could use it with or without roles.

I should be able to get it up an CPAN later this week if you have no
objections Rolan.

cheers John

On Mar 29, 7:16 pm, Roland Lammel <r...@quikit.at> wrote:
> Hi there,
>
> Funny thing is I started exactly the same some weeks ago, but was
> distracted with other things soon after.
>
> https://github.com/lammel/mojolicious-plugin-authorization/blob/maste...
>
> I tried to have easy to grasp words also for the conditions
> (authorize_role, authorize_roles) and also provide helpers for assertion in
> code (assert_role, assert_roles), same should be there for privileges.
>
> As some apps won't need privileges (roles surely suffice for simple apps),
> you just use the roles part ad provide a sub for check_user_role (to get
> the role for a user).
>
> Don't know if shorter names would work too but I think "has" and "is" are a
> little to generic as condition names.
>
> Nice work btw.
>
> Cheers
>
> +rl
> --
> Roland Lammel
> QuikIT - IT Lösungen - flexibel und schnell
> Web:http://www.quikit.at
> Phone: +43 (676) 9737845
> Email: r...@quikit.at
>
> "Enjoy your job, make lots of money, work within the law. Choose any two."
>
>
>
>
>
>
>
> On Thu, Mar 29, 2012 at 19:25, byterock <byter...@hotmail.com> wrote:
>
> > Well after playing around a bunch and pulling out what little hair I
> > have left I finally got something that works really well for me at
> > least
>
> > have a look at
>
> >https://github.com/byterock/mojolicious-plugin-authorization/blob/mas...

Roland Lammel

unread,
Apr 2, 2012, 4:35:59 PM4/2/12
to mojol...@googlegroups.com
It's a free world, so no objections of course.

I'll try to use it when it's released and see if it fits my project too. If not be prepared for some pull request ;-)


Cheers

+rl
--
Roland Lammel
QuikIT - IT Lösungen - flexibel und schnell
Web: http://www.quikit.at
Phone: +43 (676) 9737845
Email: r...@quikit.at

"Enjoy your job, make lots of money, work within the law. Choose any two."


byterock

unread,
May 4, 2012, 3:17:51 PM5/4/12
to Mojolicious
Well it is all cleaned up and should be on CPAN in a day or so

https://github.com/byterock/mojolicious-plugin-authorization

Cheers
John
Reply all
Reply to author
Forward
0 new messages