New macro {ifAuthorized} to check links in advance

8 years ago

juzna.cz
Member | 249
+
0
-

TL;DR

1/ classic
Add new macro {ifAuthorized destination [,] params} to check if user will be authorized for given action (i.e. the link won't cause 403 – ForbiddenRequestException)/

{ifAuthorized like! $item->id}
  <a n:href="like! $item->id">Like</a>
{/ifAuthorized}

2/ smart (and little bit magic)
{ifAuthorized} to check all link within this block

{ifAuthorized}                           {* magic happens here *}
  <a n:href="like! $item->id">Like</a>   {* this link will be checked *}
{/ifAuthorized}

or

<a n:ifAuthorized n:href="like! $item->id">Like</a>    {* nice, huh? *}

Le introduction

You have some actions/signals only for authorized users; e.g. like signal similar to what we know from facebook. This is validated in Presenter::checkRequirements (which needs refacroting) and typically uses method annotations like @User (implemeneted in nette) or @User(role=admin) (possible extension). This is good and secure.

Now, you have a template where you display an item from catalog, and display the like link only if user is authorized to use it. You have to write the autorization rules again in the template.

{if $user->isLoggedIn()}
  <a n:href="like! $item->id">Like</a>
{/if}

or

<a n:if="$user->isLoggedIn()" n:href="like! $item->id">Like</a>

So far, it's good.

Le problem

Now you want to make a change, that the like signal will be allowed only for special users, lets say with role editor or admin. You update annotation of given signal, and you have to update all the templates which point to this signal. What if the conditions become more complicated? And what if you forget it somewhere? You've got the same information in far many places now. That's not DRY.

{if $user->isInRole('editor') || $user->isInRole('admin')}
  <a n:href="like! $item->id">Like</a>
{/if}

or

<a n:if="$user->isInRole('editor') || $user->isInRole('admin')" n:href="like! $item->id">Like</a>

Not so nice anymore :/

Le {ifAuthorized ...} macro

Ok, let's make it nicer. We can have a new macro which would create the link and validate it's authorization requirements. If the target action won't be allowed, we simply won't show the link.

And this will be completely modular, all the checks will be done by the same method (checkRequirements) as if the request would get really executed. Thus you don't need to implement it twice. All the authorization information will be in one place, in annotations of signals/actions.

{ifAuthorized like! $item->id}
  <a n:href="like! $item->id">Like</a>
{/ifAuthorized}

or

<a n:ifAuthorized="like! $item->id" n:href="like! $item->id">Like</a>

Much nicer, huh? If the user is authorized to like an item, then we will show the link to like the item. Clear, straightforward, not repeating the authorization conditions.

But, we still repeat some code: ` like! $item->id` appears twice, that is still not nice.

Le smart {ifAuthorized} macro

Okay, let's omit the duplicated information. Macro will automatically check the link within. It may seem a little bit magical now, so be careful.

{ifAuthorized}
  <a n:href="like! $item->id">Like</a>
{/ifAuthorized}

or

<a n:ifAuthorized n:href="like! $item->id">Like</a>

Hmm, I don't have to repeat the links twice now, great. But it seems magical: the code now says “if authorized” and you may reply “yeah, authorized what???”. It's upto you, if you don't want to repeat the conditions, you don't have to. But you surely can write them twice, if you don't like the magic.

Le more advanced stuff

You can add conditions not only to an anchor, but to any piece of code. If you love wrapping your anchors like christmas presentrs, you'll use:

<div n:ifAuthorized>
  <span>
    <span>
      <a n:href="like! $item->id">Like</a>
    </span>
  </span>
</div>

You can use it also for other elements, not just links (if it makes sence somewhere):

<button n:ifAuthorized="like! $item->id" onclick="handleThisWithJavaScript();">Like</button>

here of course, you have to write down the target action since it's not used anywhere within the element.

Le implementation

First of all, we need method checkRequirements() to do what it says it does.
Then, there is a pull request and works fine in my project.

8 years ago

HosipLan
Moderator | 4693
+
0
-

Le lajk.

It's awesome, I just don't like the name {ifAuthorized}{ifAllowed} ? or just {allowed} ?

Last edited by HosipLan (2012-03-17 23:48)

8 years ago

kukulich
Member | 40
+
0
-

+1

8 years ago

David Grudl
Nette Core | 6886
+
0
-

Le nice!

8 years ago

Schmutzka
Moderator | 1154
+
0
-

Fantastic!

Le problem could be solved by something like this (dunno how/if possible to implement):

{* detect if array 'roles' found, if so, it contains allowed roles *}
{allowed roles => [editor, admin]}
  <a n:href="like! $item->id">Like</a>
{/allowed}

Last edited by Schmutzka (2012-03-25 04:03)

8 years ago

juzna.cz
Member | 249
+
0
-

That's exactly what I don't want! You've got the authorization conditions in two places now – in the template and in presenter (it has to be in presenter to prevent malicious requests).

8 years ago

Schmutzka
Moderator | 1154
+
0
-

Sry, I see now. Much better.