Abstracting permissions with Yesod

194 views
Skip to first unread message

Felipe Almeida Lessa

unread,
Jan 16, 2012, 7:14:13 AM1/16/12
to web-devel, yeso...@googlegroups.com
Hello, everyone!

I'm going to try creating a blog and my first post is dedicated to how
I am writing my isAuthorized function:

http://blog.felipe.lessa.nom.br/?p=7

I'd be thrilled if you could tell me if you like the idea! Something
similar could be done on Yesod 0.10, either on yesod-core or on the
scaffold. I didn't try writing some generic code yet because I'd like
to gather comments here first.

Cheers, =)

--
Felipe.

Piyush P Kurur

unread,
Jan 16, 2012, 7:35:23 AM1/16/12
to yeso...@googlegroups.com
On Mon, Jan 16, 2012 at 10:14:13AM -0200, Felipe Almeida Lessa wrote:
> Hello, everyone!
>
> I'm going to try creating a blog and my first post is dedicated to how
> I am writing my isAuthorized function:
>
> http://blog.felipe.lessa.nom.br/?p=7
>
> I'd be thrilled if you could tell me if you like the idea! Something
> similar could be done on Yesod 0.10, either on yesod-core or on the
> scaffold.

Type safe authorization eh ? That is really good.

Michael Snoyman

unread,
Jan 16, 2012, 7:59:23 AM1/16/12
to yeso...@googlegroups.com, web-devel

I like it. I could imagine a setup like this:

class YesodPermissions master where
data Permission master
hasPermissionTo :: ...
...

isAuthorizedPermissions :: YesodPermissions master => Route master ->
Bool -> GHandler sub master AuthorizationResult

It could sit next to YesodBreadcrumbs in Yesod.Core as a "you don't
*have* to use it, but it's really convenient to do so."

Michael

Felipe Almeida Lessa

unread,
Jan 24, 2012, 4:17:45 PM1/24/12
to yeso...@googlegroups.com, web-devel
I've created an issue on GitHub [1] so that we don't forget about this.

Cheers!

[1] https://github.com/yesodweb/yesod/issues/236

--
Felipe.

Bryan Richter

unread,
Feb 2, 2012, 3:37:20 PM2/2/12
to yeso...@googlegroups.com, web-devel
Hi Felipe,

I like it a lot!

I like it so much that I thought about it a really long time and ended
up writing a "blog" of my own in response. :)

Please excuse this abuse of gists, but I have no other convenient way
of creating a post with markdown and haskell markup[1] readily
available to me:

https://gist.github.com/1725600

-Bryan

[1] Github Pages doesn't seem to use Github-flavored markdown!

Felipe Almeida Lessa

unread,
Feb 3, 2012, 7:17:44 AM2/3/12
to yeso...@googlegroups.com, web-devel
On Thu, Feb 2, 2012 at 6:37 PM, Bryan Richter <bryan....@gmail.com> wrote:
> I like it a lot!

That's great to hear =).

> I like it so much that I thought about it a really long time and ended
> up writing a "blog" of my own in response. :)
>
> Please excuse this abuse of gists, but I have no other convenient way
> of creating a post with markdown and haskell markup[1] readily
> available to me:
>
> https://gist.github.com/1725600

If I'm correctly understanding your concerns (and please correct me if
I'm wrong), your qualm is only with the name "Permission" which you
think that should be "Credential". When you say that many actions may
be satisfied by the same crendential, you're absolutely right. On my
project we have 6 times more routes than permissions ;-). So perhaps
I'm already doing what you're proposing but using another name? Alas,
you could say "permission to modify box" =).

Regarding the foldM, I'm just not used to monadic folds enough to see
them in the heat of battle =).

Thanks for sharing your thoughts!

--
Felipe.

Bryan Richter

unread,
Feb 3, 2012, 12:11:42 PM2/3/12
to yeso...@googlegroups.com, web-devel
On Fri, Feb 3, 2012 at 04:17, Felipe Almeida Lessa
<felipe...@gmail.com> wrote:
> On Thu, Feb 2, 2012 at 6:37 PM, Bryan Richter <bryan....@gmail.com> wrote:
> If I'm correctly understanding your concerns (and please correct me if
> I'm wrong), your qualm is only with the name "Permission" which you
> think that should be "Credential".  When you say that many actions may
> be satisfied by the same crendential, you're absolutely right.  On my
> project we have 6 times more routes than permissions ;-).  So perhaps
> I'm already doing what you're proposing but using another name?  Alas,
> you could say "permission to modify box" =).
>

Yep, that's pretty much it. And given how much longer you've used your
system than I have, I'm prepared to be okay with just using
'Permission'. :)

Just for reference, though I fear this horse might be quite dead, here
is the motivating scenario that led me to 'Credential'. Names have
been changed to protect the innocent.

In my app are two model objects, Persona and Group. Person represents
a user's on-site persona (so a user can log in but still not be fully
registered), and each Group has a private, shared, facebook-style
wall. There are three relevant routes: NewPersonaR, (GroupR gid), and
DefaultGroupR. On the first, a user can set up their persona
(including joining groups), the second has a Group's wall, and the
third is a convenience route that redirects to the first Group a user
is a member of.

The authorization rules are:
(1.) To access (GroupR gid), one must be a member of said group.
(2.) Accessing DefaultGroupR will redirect to NewPersonaR if the user
has no group memberships. (Users aren't group members, only Personas
are.)
(3.) A user must be logged in to access NewPersonaR.

so:

permissionsRequiredFor r _ = case r of
NewPersonaR -> ModifyPersona -- rather opaque
DefaultGroupR -> ? -- "HaveAGroup"? "LookAtSomeGroup"?
(GroupR gid) -> ? -- "InGroup"? "RWAccessGroup?"

I couldn't think of good names for the last two. However, this was
pretty easy to come up with, and strikes me as much more natural:

credentialsRequiredFor NewPersonR _ = LoggedIn
credentialsRequiredFor DefaultGroupR _ = InSomeGroup
credentialsRequiredFor (GroupR gid) _ = InGroup gid

Can you maybe suggest better names for Permissions I could use in this scenario?

Thanks again for sparking this discussion.

Felipe Almeida Lessa

unread,
Feb 3, 2012, 12:21:47 PM2/3/12
to yeso...@googlegroups.com, web-devel
On Fri, Feb 3, 2012 at 3:11 PM, Bryan Richter <bryan....@gmail.com> wrote:
> Yep, that's pretty much it. And given how much longer you've used your
> system than I have, I'm prepared to be okay with just using
> 'Permission'. :)

I don't have any strong opinions either way, Credentials may be a
better name. (Also, I'm not a native English speaker.)

> credentialsRequiredFor NewPersonR _    = LoggedIn
> credentialsRequiredFor DefaultGroupR _ = InSomeGroup
> credentialsRequiredFor (GroupR gid)   _  = InGroup gid
>
> Can you maybe suggest better names for Permissions I could use in this scenario?

These are good enough for me. Use whatever name that floats your boat =).

Cheers!

--
Felipe.

Arthur Clemens

unread,
Aug 27, 2012, 4:52:56 AM8/27/12
to yeso...@googlegroups.com, web-devel
Did something change in Yesod?
This part doesn't seem to work:

isAuthorized route isWrite = do
  mauth <- maybeAuth
  runDB $ mauth `isAuthorizedTo` permissionsRequiredFor route isWrite

Returns

Foundation.hs:109:20:
    Couldn't match expected type `(UserId, User)'
                with actual type `Entity val0'


Arthur

Felipe Almeida Lessa

unread,
Aug 27, 2012, 9:15:01 AM8/27/12
to yeso...@googlegroups.com, web-devel
To fix this bug you'll need to change all '(UserId, User)'s into
'Entity User's. For example:

hasPermissionTo :: Entity User
-> Permission
-> YesodDB sub Blog AuthResult
(Entity _ user) `hasPermissionTo` Post = ...

etc. I'm not sure if there's anything else missing.

Cheers, =)

--
Felipe.

Arthur Clemens

unread,
Aug 27, 2012, 9:55:22 AM8/27/12
to yeso...@googlegroups.com, web-devel
Thanks, that works.

Arthur

Arthur Clemens

unread,
Sep 9, 2012, 6:36:58 AM9/9/12
to yeso...@googlegroups.com, web-devel
It would be nice if there was the possibility to give an entire site section permissions. What I have now very easily creates bugs, exactly what you don't want in restricted site parts:

data Permission = Admin | Public

permissionsRequiredFor :: Route App -> Bool -> [Permission]
permissionsRequiredFor AdminR                 False = [Admin]
permissionsRequiredFor AdminCategoriesR       False = [Admin]
permissionsRequiredFor AdminAddCategoryR      False = [Admin]
permissionsRequiredFor (AdminEditCategoryR _) False = [Admin]
permissionsRequiredFor (AdminCategoryR _)     False = [Admin]
permissionsRequiredFor AdminEmailsR           False = [Admin]
permissionsRequiredFor AdminProductsR         False = [Admin]
permissionsRequiredFor (AdminProductR _)      False = [Admin]
permissionsRequiredFor AdminAddProductR       False = [Admin]
permissionsRequiredFor (AdminEditProductR _)  False = [Admin]
permissionsRequiredFor _          _    = []

--
Arthur

Michael Snoyman

unread,
Sep 9, 2012, 6:39:21 AM9/9/12
to yeso...@googlegroups.com, web-devel
Starting with Yesod 1.1, hierarchical routes are now available. So you
can do something like:

/admin AdminR:
/product/#ProductId AdminProductR GET POST
...

And then

permissionsRequiredFor AdminR{} False = [Admin]

Michael

Arthur Clemens

unread,
Sep 9, 2012, 7:15:38 AM9/9/12
to yeso...@googlegroups.com, web-devel
/admin AdminR:
   /product/#ProductId AdminProductR GET POST
   ...

So if I have:

/admin AdminR:
    / AdminRootR GET
    /categories AdminCategoriesR GET

and the handlers are specified as:

getAdminRootR :: Handler RepHtml
getAdminRootR = do
    ..

getAdminCategoriesR :: Handler RepHtml
getAdminCategoriesR = do
    ..

what more is needed to create links?
Currently I use <a href=@{AdminRootR}> but that results in 

Handler/Admin.hs:14:22:
    Not in scope: data constructor `AdminRootR'

Arthur

Michael Snoyman

unread,
Sep 9, 2012, 7:40:38 AM9/9/12
to yeso...@googlegroups.com, web-devel
You'll need the AdminR constructor: AdminR AdminRootR. If you use
-ddump-splices when compiling, GHC will spit out the generated
code/data types, which should give a good idea of what's going on
under the surface.

Michael

Arthur Clemens

unread,
Sep 9, 2012, 10:48:41 AM9/9/12
to yeso...@googlegroups.com, web-devel
You'll need the AdminR constructor: AdminR AdminRootR.

It doesn't work in the scaffolded site, whereas it works as expected in a single file site like https://github.com/yesodweb/yesod/wiki/Hierarchical-routes-and-breadcrumbs

I created a new scaffolded site, used add-handler to create Handler/Admin.hs, and changed the route in config/routes to

/admin AdminR:
    / AdminRootR GET

yesod devel results in Application.hs:27:1: Not in scope: data constructor `AdminRootR'

changing that to

/admin AdminRootR GET

runs without errors.


If you use
-ddump-splices when compiling, GHC will spit out the generated
code/data types, which should give a good idea of what's going on
under the surface.

I can't, it results in 

Import.hs:10:2: lexical error at character 'i'

Using ghc -cpp -ddump-splices main.hs 

results in Model.hs:13:1: Parse error: naked expression at top level

Arthur

Michael Snoyman

unread,
Sep 10, 2012, 2:56:26 AM9/10/12
to yeso...@googlegroups.com, web-devel
On Sun, Sep 9, 2012 at 5:48 PM, Arthur Clemens <arthur...@gmail.com> wrote:
You'll need the AdminR constructor: AdminR AdminRootR.

It doesn't work in the scaffolded site, whereas it works as expected in a single file site like https://github.com/yesodweb/yesod/wiki/Hierarchical-routes-and-breadcrumbs

I created a new scaffolded site, used add-handler to create Handler/Admin.hs, and changed the route in config/routes to

/admin AdminR:
    / AdminRootR GET

yesod devel results in Application.hs:27:1: Not in scope: data constructor `AdminRootR'

changing that to

/admin AdminRootR GET

runs without errors.



Just to confirm: does your `cabal` file state Yesod version 1.0 or 1.1?
 
If you use
-ddump-splices when compiling, GHC will spit out the generated
code/data types, which should give a good idea of what's going on
under the surface.

I can't, it results in 

Import.hs:10:2: lexical error at character 'i'

Using ghc -cpp -ddump-splices main.hs 

results in Model.hs:13:1: Parse error: naked expression at top level

Arthur


Sorry, I should have clarified: you can do something like:

    cabal configure --ghc-option=-ddump-splices

Then `cabal build` will pass in that option. You get the "naked expression" error because TemplateHaskell isn't enabled.

Michael

Arthur Clemens

unread,
Sep 10, 2012, 3:09:46 AM9/10/12
to yeso...@googlegroups.com, web-devel
> Just to confirm: does your `cabal` file state Yesod version 1.0 or 1.1?

I have yesod-1.1.0.2


Arthur

Michael Snoyman

unread,
Sep 10, 2012, 5:54:25 AM9/10/12
to yeso...@googlegroups.com, web-devel
On Mon, Sep 10, 2012 at 9:56 AM, Michael Snoyman <mic...@snoyman.com> wrote:


On Sun, Sep 9, 2012 at 5:48 PM, Arthur Clemens <arthur...@gmail.com> wrote:
You'll need the AdminR constructor: AdminR AdminRootR.

It doesn't work in the scaffolded site, whereas it works as expected in a single file site like https://github.com/yesodweb/yesod/wiki/Hierarchical-routes-and-breadcrumbs

I created a new scaffolded site, used add-handler to create Handler/Admin.hs, and changed the route in config/routes to

/admin AdminR:
    / AdminRootR GET

yesod devel results in Application.hs:27:1: Not in scope: data constructor `AdminRootR'

changing that to

/admin AdminRootR GET

runs without errors.



Just to confirm: does your `cabal` file state Yesod version 1.0 or 1.1? 
 
Actually, I bet I know what the problem is: the export list in Foundation.hs doesn't include the AdminR datatype. Try adding:

    AdminR (..)

Arthur Clemens

unread,
Sep 10, 2012, 6:09:59 AM9/10/12
to yeso...@googlegroups.com, web-devel

Actually, I bet I know what the problem is: the export list in Foundation.hs doesn't include the AdminR datatype. Try adding:

    AdminR (..)

Yes, now it works.
That's quite a gotcha.

Arthur

Michael Snoyman

unread,
Sep 10, 2012, 6:34:30 AM9/10/12
to yeso...@googlegroups.com, web-devel
I agree it's a gotcha, but I'm not sure there's anything we can do to avoid the issue. I suppose we could get rid of the export list in Foundation.hs, but that's not really a good practice in general. Best bet is likely to keep wiki documentation up to date. Which based on the Github newsfeeds, it seems you've been doing a very good job of, thank you. :)

Michael

Arthur Clemens

unread,
Sep 11, 2012, 5:14:55 PM9/11/12
to yeso...@googlegroups.com, web-devel
> I agree it's a gotcha, but I'm not sure there's anything we can do to avoid the issue. I suppose we could get rid of the export list in Foundation.hs, but that's not really a good practice in general. Best bet is likely to keep wiki documentation up to date. Which based on the Github newsfeeds, it seems you've been doing a very good job of, thank you. :)

I've added a note to the cookbook article about using hierarchical routes in a scaffolded site.

Arthur

Reply all
Reply to author
Forward
0 new messages