[Grok-dev] Make self.request.principal to be an instance of MyOwnPrincipalInfo (my own class)

Hector Blanco white.lists at gmail.com
Tue Jan 25 12:43:12 EST 2011


Ok, I got it working... But I'd like to check your opinions... I don't
know if what I'm doing is right or not... secure or not... For the
moment, the login, logout... not allowing a user to access pages if
it's not logged in... All that seems to be working fine but I'm not
100% certain that what I've done is a good idea or it will be a
direct threat to the security of my server, the global warming and the
existance of Santa Claus... I'm very newbie and there are a lot of
concepts I don't fully understand.

Please, allow me to explain:

For the moment, I have everything in my app.py file. I'll try to
register the components later and try to move all this to separate
files.

I have implemented or extended three different elements involved in
the authentication process:

1) The "info" for the principal:

class MyOwnPrincipalInfo(grok.Adapter):
	grok.context(User.User)
	grok.implements(IPrincipalInfo)
	grok.name("MyOwnPrincipalInfo")

	def __init__(self, user):
		if isinstance(user, User.User) and (user.id):
			self.id = str(user.id)
			self.title = user.userName
			self.description = str(user.firstName) + " " + str(user.lastName)
			self.credentialsPlugin = None
			self.authenticatorPlugin = None
		else:
			raise TypeError("Unable to provide a PrincipalInfo from a %s" % type(user))

2) I have also extended (not implemented interfaces, but extended) the
principal itself, to which I have added a method "test" to see if I
could execute it (it's extended from
zope.pluggableauth.factories.Principal)

class MyOwnPrincipal(Principal):
	grok.name("MyOwnPrincipal")
	def __init__(self, id, title=u'', description=u'', info=None):
		super(MyOwnPrincipal, self).__init__(id, title, description)
		self.info = None #I'll deal with this later

	def test(self):
		log.debug("::Test > Checkpoint!")

I don't know if just "extending" it's a good idea, but when I checked
the AuthenticatedPrincipalFactory (in zope.plugabbleauth.factories)
and I saw what it did, it didn't seem to check for other
implementations of Principal... it just did:

class AuthenticatedPrincipalFactory(object):
    def __call__(self, authentication):
        principal = Principal(authentication.prefix + self.info.id,
                   self.info.title,
                   self.info.description)
        #notify...
        return Principal

That's why I also had to create MyOwnPrincipalFactory (detailed below)

and 3) And I have implemented my own version of the factory that
returns Principals of the MyOwnPrincipal class:

class MyOwnPrincipalFactory(grok.MultiAdapter):
	grok.context(Server)
	grok.adapts(IPrincipalInfo, IBrowserRequest)
	grok.implements(IAuthenticatedPrincipalFactory)

	def __init__(self, info, request):
		self.info = info
		self.request = request

	def __call__(self, authentication):
		principal = MyOwnPrincipal(authentication.prefix + self.info.id,
								self.info.title,
								self.info.description,
								self.info)
		#The line above is the key! :-)
		notify(AuthenticatedPrincipalCreated(
			authentication, principal, self.info, self.request))
		return principal

I don't know... maybe extending the
zope.pluggableauth.factories.AuthenticatedPrincipalFactory would have
been enough... or even better idea...

Anyway... At least I've been able to, finally, do the following in a view:

class Test(grok.View):
	grok.context(Server)
	grok.require('server.ViewSite')

	def render(self):
		print("::Test > Got self.request.principal: %s (of type %s)" %
		   (self.request.principal, type(self.request.principal)))
		print("::Test > Got vars(self.request.principal): %s" %
		   vars(self.request.principal))
		print("::Test > Got dir(self.request.principal): %s" %
		   dir(self.request.principal))
		print("::Test > Trying to execute self.request.principal.test(): ")
		self.request.principal.test()
		print("::Test > If you're reading this, it's been successful!")

Which produces this output:
::Test > Got self.request.principal: Principal('3') (of type <class
		'server.app.InvPrincipal'>)
::Test > Got vars(self.request.principal): {'info': None,
		'description': 'test firstName test lastName', 'id': '3', 'groups':
		[], 'title': u'test'}
::Test > Got dir(self.request.principal): ['__class__', '__delattr__',
'__dict__', '__doc__', '__format__', '__getattribute__', '__hash__',
'__implemented__', '__init__', '__module__', '__new__',
'__providedBy__', '__provides__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'allGroups', 'description',
'grokcore.component.directive.name', 'groups', 'id', 'info', 'test',
'title']
::Test > Trying to execute self.request.principal.test():
::Test > Checkpoint!
::Test > If you're reading this, it's been successful!

Any recommendations, ideas or suggestions will be very appreciated...
(what? I'm scared... )

Thank you everyone!

2011/1/25 Hector Blanco <white.lists at gmail.com>:
> Thank you very much. It's been a great, great explanation. It has
> allowed me to understand a bit better the interfaces, adapters and all
> that. And, oh, well... It helped me realize that my idea won't
> probably work... :D :D (it's sad, but I will sleep better at nights).
>
> The adaptation worked properly. The process I followed is:
>
> I made MyOwnPrincipalInfo implementing the interface IPrincipalInfo so
> it can be used in the zope.pluggableauth mechanism as a good
> (suitable) "PrincipalInfo" thing.
>
> To achieve that, I had to move the MyOwnPrincipalInfo to app.py
> because I was getting ComponentLookupErrors (I guess if I want it in
> another file, I might have to register it somewhere, as I had to with
> the permissions thing:
> https://mail.zope.org/pipermail/grok-dev/2011-January/011028.html
> )
>
> But well... the way I have it now is:
>
> ================ app.py ==========
> class MyOwnPrincipalInfo(grok.Adapter):
>        grok.context(User.User)
>        # My 'user' class, which doesn't have much to see
>        # with the pluggable auth mechanism
>        grok.implements(IPrincipalInfo)
>        grok.name("MyOwnPrincipalInfo")
>
>        def __init__(self, user):
>                if isinstance(user, User.User) and (user.id):
>                        self.id = str(user.id)
>                        self.title = user.userName
>                        self.description = str(user.firstName) + " " + str(user.lastName)
>                else:
>                        raise TypeError("Unable to provide a PrincipalInfo from a %s" % type(user))
>
>        def test(self):
>                log.debug("::test > Checkpoint!")
>
> ------
> So I can use that in my own authenticator plugin (a few lines later in
> the same app.py)
>
> class UserAuthenticatorPlugin(grok.GlobalUtility):
>        grok.provides(IAuthenticatorPlugin)
>        grok.implements(IAuthenticatorPlugin)
>        grok.name('UserAuthenticatorPlugin')
>        grok.context(Server)
>
>        def __init__(self):
>                super(UserAuthenticatorPlugin, self).__init__()
>
>        def authenticateCredentials(self, credentials):
>                if isinstance(credentials, dict):
>                        if (("login" in credentials) and ("password" in credentials)):
>                                user = self.getAccount(credentials['login'])
>                                if user and (user.checkPassword(credentials['password'])):
>                                        myOwnPrincipalInfo = component.getAdapter(user, IPrincipalInfo,
>                                                name="MyOwnPrincipalInfo")
>                                        log.debug("::UserAuthenticatorPlugin > getAccount > Got principal
>                                                %s" % myOwnPrincipalInfo)
>                                        return myOwnPrincipalInfo
>                return None
>
>
> But here's when I realized I all that setup won't work (and well... I
> might be wrong... I hope I'm wrong, but this is what I understand it
> happens):
>
> The pluggable authentication module
> (~/.buildout/eggs/zope.pluggableauth-1.2-py2.6.egg/zope/pluggableauth/authentication.py)
> does this:
> -------------
>    def authenticate(self, request):
>        authenticatorPlugins = [p for n, p in self.getAuthenticatorPlugins()]
>        for name, credplugin in self.getCredentialsPlugins():
>            credentials = credplugin.extractCredentials(request)
>            for authplugin in authenticatorPlugins:
>                if authplugin is None:
>                    continue
>                info = authplugin.authenticateCredentials(credentials)
>                if info is None:
>                    continue
>                info.credentialsPlugin = credplugin
>                info.authenticatorPlugin = authplugin
>                principal = component.getMultiAdapter((info, request),
>                    interfaces.IAuthenticatedPrincipalFactory)(self)
>                principal.id = self.prefix + info.id
>                return principal
>        return None
> -------------
> The line:
>     info = authplugin.authenticateCredentials(credentials)
>
> Is the one that calls my plugin's "authenticateCredentials" method. At
> that point, "info" is (finally!!, what I wanted), an instance of
> MyOwnPrincipalInfo class.
>
> Then that principalInfo is used in the method def getPrincipal(self,
> id) of the same "authentication.py" file. And does this:
>
> principal = interfaces.IFoundPrincipalFactory(info)(self)
>
> I guessed (better said... I supposed) that it follows a kind of
> 'abstract factory' pattern. I went to check the implementation of that
> Factory () and I saw this:
>
> ~/.buildout/eggs/zope.pluggableauth-1.2-py2.6.egg/zope/pluggableauth/factories.py:
> class AuthenticatedPrincipalFactory(object):
>    # Yadda, yadda, yadda...
>    def __call__(self, authentication):
>        principal = Principal(authentication.prefix + self.info.id,
>                              self.info.title,
>                              self.info.description)
>        notify(interfaces.AuthenticatedPrincipalCreated(
>            authentication, principal, self.info, self.request))
>        return principal
>
> It's not returning anything with 'MyOwnPrincipalInfo' inside but it's
> just the required fields from said "info" instance and creating a
> principal with them (Principal which is what "comes" in the request).
>
> If I create a "Test View" (in my app.py) to sneak in the
> request.principal, such as:
>
> class Test(grok.View):
>        grok.context(Server)
>        grok.require('server.ViewSite')
>
>        def render(self):
>                print("::Test > Got self.request.principal: %s (of type %s)" %
> (self.request.principal, type(self.request.principal)))
>                print("::Test > Got vars(self.request.principal): %s" %
> vars(self.request.principal))
>                print("::Test > Got dir(self.request.principal): %s" %
> dir(self.request.principal))
>
> This is the output:
>
> ::Test > Got self.request.principal: Principal('3') (of type <class
> 'zope.pluggableauth.factories.Principal'>)
> ::Test > Got vars(self.request.principal): {'description': 'test
> firstName test lastName', 'id': '3', 'groups': [], 'title': u'test'}
> ::Test > Got dir(self.request.principal): ['__class__', '__delattr__',
> '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__',
> '__implemented__', '__init__', '__module__', '__new__',
> '__providedBy__', '__provides__', '__reduce__', '__reduce_ex__',
> '__repr__', '__setattr__', '__sizeof__', '__str__',
> '__subclasshook__', '__weakref__', 'allGroups', 'description',
> 'groups', 'id', 'title']
>
> So I guess I have to adapt the Principal, as well... (something like
> Jan-Wijbrand Kolman already explained a few mails ago in this very
> same thread)
> https://mail.zope.org/pipermail/grok-dev/2011-January/010991.html
>
> There might be hope for not having to adapt that, thoug...
>
> Going back to the file
> ~/.buildout/eggs/zope.pluggableauth-1.2-py2.6.egg/zope/pluggableauth/factories.py:
> and its "AuthenticatedPrincipalFactory" class:
>
> The init looks like like this:
>
> class AuthenticatedPrincipalFactory(object):
>    component.adapts(interfaces.IPrincipalInfo, IRequest)
>    interface.implements(interfaces.IAuthenticatedPrincipalFactory)
>
>    def __init__(self, info, request):
>        self.info = info
>        self.request = request
>
> So my belowed "info" thing is inside the Factory object... Now I have
> to see if there's a way to grab the instance of
> AuthenticatedPrincipalFactory and access its "info" field.
>
> Will keep posting...
> :D
>
> Thank you everyone for sheding some light to all this! (and well... if
> you can shed some more... it'd be great!)
>
>
>
> 2011/1/25 Jan-Wijbrand Kolman <janwijbrand at gmail.com>:
>> Hi,
>>
>> On 1/24/11 20:45 PM, Hector Blanco wrote:
>>> Do I have to "grok"/register... my PrincipalInfo somewhere?
>>>
>>> Right now I have it like this:
>>>
>>> ========= MyOwnPrincipalInfo.py ===========
>>>
>>> class MyOwnPrincipalInfo(grok.MultiAdapter):
>>>       grok.adapts(IPrincipalInfo, IBrowserRequest)
>>>       grok.implements(IPrincipalInfo)
>>>
>>>       def __init__(self, user):
>>>               super(MyOwnPrincipalInfo, self).__init__() #Just in case
>>>               #more initialization code
>>>
>>>       def test(self):
>>>               log.debug("::test>  Checkpoint!")
>>>
>>> And then, in app.py I have set up an AuthenticatorPlugin like this:
>>>
>>> ============ app.py ============
>>> class UserAuthenticatorPlugin(grok.GlobalUtility):
>>>       grok.provides(IAuthenticatorPlugin)
>>>       grok.implements(IAuthenticatorPlugin)
>>>       grok.name('UserAuthenticatorPlugin')
>>>       grok.context(Server)
>>>
>>>       def __init__(self):
>>>               super(UserAuthenticatorPlugin, self).__init__()
>>>
>>>       def authenticateCredentials(self, credentials):
>>>               if isinstance(credentials, dict):
>>>                       if (("login" in credentials) and ("password" in credentials)):
>>>                               user = self.getAccount(credentials['login'])
>>>                               if user and (user.checkPassword(credentials['password'])):
>>>                                       return component.getMultiAdapter((user, ), IPrincipalInfo)
>>>
>>>               return None
>>> ================
>>
>> In app.py you have `getMultiAdapter((user, ), IPrincipalInfo)`, however,
>> the `MyOwnPrincipalInfo` multi adapter class expects *two* objects to
>> adapt, as defined in the `grok.adapts()` directive: an object providing
>> `IPrincipalInfo` and an object providing `IBrowserRequest`. That's why
>> it is called a *multi adapter* :)
>>
>> The `grok.implements()` then tells the component architecture what
>> interface will be provided by instances of this adapter class.
>>
>> So, I think you need to rewrite the adapter class to this:
>>
>>   class MyOwnPrincipalInfo(grok.MultiAdapter):
>>     grok.adapts(INTERFACE_OR_CLASS_FOR_THE_USER_OBJECT, IBrowserRequest)
>>     grok.implements(IPrincipalInfo)
>>
>> where INTERFACE_OR_CLASS_FOR_THE_USER_OBJECT is the interface, or class
>> of the user objects you want to adapt.
>>
>> The `getMultiAdapter` call then would look like this::
>>
>>   getMultiAdapter((user, request), IPrincipalInfo)
>>
>> But, you say, there's no request in scope in the
>> `authenticateCredentials` call to use for the multi adapter lookup. So,
>> maybe you do not really need the request in the `MyOwnPrincipalInfo`
>> adapter class?
>>
>> In that case you might able to do this::
>>
>>   class MyOwnPrincipalInfo(grok.Adapter):
>>     grok.context(INTERFACE_OR_CLASS_FOR_THE_USER_OBJECT)
>>     grok.implements(IPrincipalInfo)
>>
>>     def __init__(self, user):
>>       # do your stuff
>>
>> And instead of the `getMultiAdapter` call, you do a `getAdapter(user,
>> IPrincipalInfo)` call, which has a elegant shortcut::
>>
>>   IPrincipalInfo(user)
>>
>> HTH, let us know how you fare.
>>
>> regards, jw
>>
>> _______________________________________________
>> Grok-dev mailing list
>> Grok-dev at zope.org
>> https://mail.zope.org/mailman/listinfo/grok-dev
>>
>


More information about the Grok-dev mailing list