Everything there is to know about the current auth/identity in TG2

39 views
Skip to first unread message

Jorge Vargas

unread,
Jun 12, 2009, 1:36:00 AM6/12/09
to turbo...@googlegroups.com
Hello,

Two things up front
1- I hate how this "public" api is presented to us
2- I'm tired of answering the same question over and over on IRC so
for now this is how things go I'll close with what I think we should
do for tg2.1

Keep in mind they are a lot of "inconsistencies" in the API and some
weird design choices that a limiting it by several factors.

So first quickstart a project and paste this into a template.

You will find the first error (1). tg.identity is None when you are
not logged in, this pulls from repoze.who and I don't think it's such
a bad idea as it should be None when you have no user. But it's
horrible for those cases where we need the user and/or group object.

The next error (2) is how clumsy the
tg.predicates.is_anonymous().is_met(tg.request.environ) transformation
is implemented, I won't go over this again.
http://trac.turbogears.org/ticket/2205 Draw your own conclusions.

But at least we have the more decent form tg.predicates.is_anonymous()
at least for now.

<body>
<p py:if='tg.predicates.is_anonymous().is_met(tg.request.environ)'>Anonymous
</p>

<p> The methods below work due to a flag set by TG </p>
<p py:if='tg.predicates.is_anonymous()'>Anonymous </p>
<p py:if='tg.predicates.not_anonymous()'>Not Anonymous </p>

<h1>WARNING: methods below will only work if you are logged in! </h1>
<h2> This is due to tg.identity being None if no auth is present </h2>

<h3> From repoze.who </h3>
<div>User Object: ${tg.identity['repoze.who.userid']}</div>

<h3>From repoze.what</h3>
<div>User Object: ${tg.identity['user']}</div>
<div>Groups List of Strings: ${tg.identity['groups']}</div>
<div>Groups List of Objects: ${tg.identity['user'].groups}</div>

<div>Permissions List of Strings: ${tg.identity['permissions']}</div>
<div>Permissions List of Objects: ${tg.identity['user'].permissions}</div>
<div>First Group: ${tg.identity['groups'][0]}</div>
<div>First Permission: ${tg.identity['permissions'][0]}</div>

<h3> These are real object </h3>

<div>User Object: ${str(type(tg.identity['user']))}</div>
<div>Groups List Of Strings: ${str(type(tg.identity['groups']))}</div>
<div>Groups List Of Objects: ${str(type(tg.identity['user'].groups))}</div>
<div>Groups First Object: ${str(type(tg.identity['user'].groups[0]))}</div>
<div>Permissions List Of String:
${str(type(tg.identity['permissions']))}</div>
<div>Permissions List Of Objects:
${str(type(tg.identity['user'].permissions))}</div>

</body>

So that was a bunch, how about doing the same thing in the controller?
well it's totally different.

@expose('identitytest.templates.index')
def index(self):
"""Handle the front-page."""
from repoze.what.predicates import has_permission
from tg import request
yes_or_no = has_permission('manage').is_met(request.environ)
print yes_or_no # outputs = the real "boolean object"

yes_or_no = has_permission('manage')
print yes_or_no # outputs =
<repoze.what.predicates.has_permission object at 0xa63666c>
#Why? don't know r.what works that way.
#there is a flag you can set but it's not set at the
controller level, need to ask Gustavo

identity = request.environ.get('repoze.who.identity')
# from here you can do everything in the template example as
tg.identity is the equivalent
return dict()

As you can tell there is no "short form" (3) and you need to import
items from 2 classes (4) and in order to get the rather simply
tg.identity variable from the templates you get a really weird call to
the request.environ.


So bottom line that is how things are now, if someone wants to
transform this into a tutorial be my guest. What I really want to do
is provide a sane API to fix 1,2,3,4 and probably others what you may
have. I'll leave the fix for another email, too tired right now.

Gustavo Narea

unread,
Jun 13, 2009, 9:07:46 AM6/13/09
to turbo...@googlegroups.com
Hello, Jorge et al.

Here we go once again...

It's not "how repoze.what works". It's all about how Python works.

In Python, an object can act as a boolean if its __nonzero__/__bool__ method
is set accordingly. For TG2 users, a horrible error-prone monkey-patch has
been implemented and enabled by default by popular demand:
http://trac.turbogears.org/ticket/2205

With it, the __non_zero__ method is set on every predicate, so if you want to
get its truth value, *Python* *mandates* that you use bool() *unless* it's in
the context of a boolean evaluation:
"""
yes_or_not = bool(has_permission('manage'))

if has_permission('manage'): # <-- equals: "bool(has_permission('manage'))"
do_something()
"""

The "officially supported and recommended" (aka "my") way to do this is 1)
disable that monkey-patch and 2) Use the is_met()/not_met() functions
provided by repoze.what-pylons -- they do return a bool:
"""
yes_or_not = is_met(has_permission('manage'))

if is_met(has_permission('manage')):
do_something()
"""

You'd write a few more characters, but it's safer.

Noone should ever expect an instance of a class (e.g., is_anonymous,
has_permission) to return a bool because object.__init__() calls cannot return
a value. Unless, of course, the class extends Python's bool, which can't
obviously happen in a reusable software like repoze.what.

So it all comes down to the way Python works. *Any* attempt to override this
behavior in the core of repoze.what will result in a unportable software.
Hence repoze.what-pylons exists.


> identity = request.environ.get('repoze.who.identity')
> # from here you can do everything in the template example as
> tg.identity is the equivalent
> return dict()
>
> As you can tell there is no "short form" (3) and you need to import
> items from 2 classes (4) and in order to get the rather simply
> tg.identity variable from the templates you get a really weird call to
> the request.environ.

If you want to do it the "raw" way, indeed you have to pass the environ. But
you can use the is_met() function and make it available in the template:
<p py:if="is_met(tg.predicates.is_anonymous())">Anonymous</p>

Or if you use the monkey-patch enabled by default, you could even write:
<p py:if="tg.predicates.is_anonymous()">Anonymous</p>

As you could see, I didn't have to use the environ in the samples above.

Cheers.
--
Gustavo Narea <xri://=Gustavo>.
| Tech blog: =Gustavo/(+blog)/tech ~ About me: =Gustavo/about |


Jorge Vargas

unread,
Jun 14, 2009, 2:32:55 PM6/14/09
to turbo...@googlegroups.com

I'm sorry but this is circular logic to me. It is a monkey patch
because you decided not to enable it by default, because it's
error-prone (to monkey patch), because someone (I know this predates
r.what) decided the predicates where classes which forced the
__nonzero__ call. It all boils down to the fact that the current
internal state of the system forces ALL of those decisions on us. If
you implement it half way (aka booleanize_predicates) you are creating
several other problems (your error-phone point and the ugly API).

> With it, the __non_zero__ method is set on every predicate, so if you want to
> get its truth value, *Python* *mandates* that you use bool() *unless* it's in
> the context of a boolean evaluation:
> """
> yes_or_not = bool(has_permission('manage'))
>
> if has_permission('manage'):  #  <-- equals: "bool(has_permission('manage'))"
>    do_something()
> """
>
> The "officially supported and recommended" (aka "my") way to do this is 1)
> disable that monkey-patch and 2) Use the is_met()/not_met() functions
> provided by repoze.what-pylons -- they do return a bool:

which gets you a horrible api.

> """
> yes_or_not = is_met(has_permission('manage'))
>
> if is_met(has_permission('manage')):
>    do_something()
> """
>
> You'd write a few more characters, but it's safer.
>

how is the other way unsafe?

> Noone should ever expect an instance of a class (e.g., is_anonymous,
> has_permission) to return a bool because object.__init__() calls cannot return
> a value. Unless, of course, the class extends Python's bool, which can't
> obviously happen in a reusable software like repoze.what.
>
> So it all comes down to the way Python works. *Any* attempt to override this
> behavior in the core of repoze.what will result in a unportable software.
> Hence repoze.what-pylons exists.
>
>
>>         identity = request.environ.get('repoze.who.identity')
>>         # from here you can do everything in the template example as
>> tg.identity is the equivalent
>>         return dict()
>>
>> As you can tell there is no "short form" (3) and you need to import
>> items from 2 classes (4) and in order to get the rather simply
>> tg.identity variable from the templates you get a really weird call to
>> the request.environ.
>
> If you want to do it the "raw" way, indeed you have to pass the environ. But
> you can use the is_met() function and make it available in the template:
>    <p py:if="is_met(tg.predicates.is_anonymous())">Anonymous</p>
>
> Or if you use the monkey-patch enabled by default, you could even write:
>    <p py:if="tg.predicates.is_anonymous()">Anonymous</p>
>
> As you could see, I didn't have to use the environ in the samples above.
>

ok that's one example I forgot to add, thanks :)

Gustavo Narea

unread,
Jun 14, 2009, 7:16:38 PM6/14/09
to turbo...@googlegroups.com
Hola, Jorge et al.

Jorge said:
> On Sat, Jun 13, 2009 at 9:07 AM, Gustavo Narea<m...@gustavonarea.net> wrote:
> > Hello, Jorge et al.
> >
> > Jorge said:
> >> The next error (2) is how clumsy the
> >> tg.predicates.is_anonymous().is_met(tg.request.environ) transformation
> >> is implemented, I won't go over this again.
> >> http://trac.turbogears.org/ticket/2205 Draw your own conclusions.
> >>
> >> But at least we have the more decent form tg.predicates.is_anonymous()
> >> at least for now.
> >>
> >> <body>
> >> <p
> >> py:if='tg.predicates.is_anonymous().is_met(tg.request.environ)'>Anonymou

> >>s </p>

As I've said before, the problem is not that __non_zero__ is set dynamically
(i.e., monkey-patched). The problem is that __nonzero__ should not be set.

From a generic perspective, not specifically about repoze.what:

The only safe way to evaluate a condition represented by an object that is
shared among threads, is to make this object stateless and evaluate it by
passing other objects which represent the context of the evaluation.

If you want to skip the step where you pass the context, so you could pass it
implicitly to save code (i.e., with thread-locals being the only way to do so,
AFAIK), you'll end up with a buggy software: This object won't be stateless
anymore, in spite of being used in many threads, which is absolutely
unreliable.


> > With it, the __non_zero__ method is set on every predicate, so if you

> > want to get its truth value, Python mandates that you use bool() unless


> > it's in the context of a boolean evaluation:
> > """
> > yes_or_not = bool(has_permission('manage'))
> >
> > if has_permission('manage'): # <-- equals:
> > "bool(has_permission('manage'))" do_something()
> > """
> >
> > The "officially supported and recommended" (aka "my") way to do this is
> > 1) disable that monkey-patch and 2) Use the is_met()/not_met() functions
> > provided by repoze.what-pylons -- they do return a bool:
>
> which gets you a horrible api.

"Para gustos, colores".

If it's a horrible API for you, I won't discuss it. It may a matter of taste
here, I think. I don't think it is, though.


> > """
> > yes_or_not = is_met(has_permission('manage'))
> >
> > if is_met(has_permission('manage')):
> > do_something()
> > """
> >
> > You'd write a few more characters, but it's safer.
>
> how is the other way unsafe?

I've just explained this above.

Jorge Vargas

unread,
Jun 14, 2009, 10:52:42 PM6/14/09
to turbo...@googlegroups.com
I believe you wanted to say the current implementation of non-zero,
there is nothing wrong with non-zero itself.

> If you want to skip the step where you pass the context, so you could pass it
> implicitly to save code (i.e., with thread-locals being the only way to do so,
> AFAIK), you'll end up with a buggy software: This object won't be stateless
> anymore, in spite of being used in many threads, which is absolutely
> unreliable.
>
>
how are thread locals buggy? if you simply pass the environ to each
call won't that fix all your issues with it? make each predicate take
the environ as first argument and make them functions that return a
boolean or raises NotAutorizedException. What is the flaw there?

Gustavo Narea

unread,
Jun 15, 2009, 12:02:09 PM6/15/09
to turbo...@googlegroups.com
Hi everyone.

No, I meant __nonzero__, however it's defined, is wrong. [1]


> > If you want to skip the step where you pass the context, so you could
> > pass it implicitly to save code (i.e., with thread-locals being the only
> > way to do so, AFAIK), you'll end up with a buggy software: This object
> > won't be stateless anymore, in spite of being used in many threads, which
> > is absolutely unreliable.
>
> how are thread locals buggy? if you simply pass the environ to each
> call won't that fix all your issues with it? make each predicate take
> the environ as first argument and make them functions that return a
> boolean or raises NotAutorizedException. What is the flaw there?

Noone but you said that thread locals were buggy. And the flaw is in the whole
proposal, because you cannot use functions, as I've told you in another
thread.

Cheers.

[1]
http://groups.google.com/group/turbogears/browse_thread/thread/f35ef3d347793682

Jorge Vargas

unread,
Jun 21, 2009, 8:35:16 PM6/21/09
to turbo...@googlegroups.com
you can not use functions if you keep the current

allow_only = foo()

but if you transform that into allow_only = autorrize(foo()) or
something similar that objection is gone.

Gustavo Narea

unread,
Jun 22, 2009, 9:39:56 AM6/22/09
to turbo...@googlegroups.com
Jorge said:
> you can not use functions if you keep the current
>
> allow_only = foo()
>
> but if you transform that into allow_only = autorrize(foo()) or
> something similar that objection is gone.

It's the exact same thing: authorize() will receive a True or a False, and it
cannot do anything useful with it.

Jorge Vargas

unread,
Jun 22, 2009, 1:59:25 PM6/22/09
to turbo...@googlegroups.com
On Mon, Jun 22, 2009 at 9:39 AM, Gustavo Narea<m...@gustavonarea.net> wrote:
>
> Jorge said:
>> you can not use functions if you keep the current
>>
>> allow_only = foo()
>>
>> but if you transform that into allow_only = autorrize(foo()) or
>> something similar that objection is gone.
>
> It's the exact same thing: authorize() will receive a True or a False, and it
> cannot do anything useful with it.

ahhh sorry I meant to say authorize(foo) for paramenters you can do
authorize(foo,'var','var')

Gustavo Narea

unread,
Jun 22, 2009, 2:17:16 PM6/22/09
to turbo...@googlegroups.com
Jorge said:
> ahhh sorry I meant to say authorize(foo) for paramenters you can do
> authorize(foo,'var','var')

Yeah, in that case, it'd certainly work. But to be honest I'd rather something
simpler, like this (for example):
http://groups.google.com/group/turbogears-trunk/browse_thread/thread/385f28ff4e880593

Reply all
Reply to author
Forward
0 new messages