[Twisted-Python] Must avatarId always be a string?

http://twistedmatrix.com/documents/current/core/howto/cred.html says that the avatarId parameter to IRealm.requestAvatar must be a string, not even a Unicode string. I'm using LDAP for authentication, and the checker retrieves the full LDAP entry for the user as a side-effect of authentication. Until I read that doc I was happily passing back the tuple of LDAP details as an avatarId, and it all works perfectly well. Is it really wrong, and if so, how will it fail? And what should I do instead to pass back the details? Peter.

On 09/01/13 13:49, Peter Westlake wrote:
I remember discussing this on IRC with someone not long ago and he pointed me to this thread: http://twistedmatrix.com/pipermail/twisted-python/2010-September/022826.html I have faced a similar problem myself and after reading the code I've resolved to wilfully disregarding the documentation and passing tuples around, accepting that if it breaks, I get to keep both pieces. Cheers, Jan

On 9 January 2013 15:04, Jan Urbański <wulczer@wulczer.org> wrote:
I am not an expert in Twisted, but from my understanding, the "string" requirement is there to provide a plugable interface. So that you can have generic credentials checkers, working with generic realms. Having simple "strings" could also help with AvatarId serialization, in case you have the CredentialsChecker on one computer and the you will pass them over network/socket to a remote Realm. I have also asked over IRC and I got the good to go answer for using anything as AvatarID. As long as you are only using your custom credentials checkers and your custom realm, everything should be ok. I am using Objects as AvatarID without any problems. Cheers, -- Adi Roiban

On Jan 9, 2013, at 9:26 AM, Peter Westlake <peter.westlake@pobox.com> wrote:
I hope it's clear that just hard-coding your avatars and realms to work only with each other is a sub-optimal solution :). The architecture of cred is supposed to be that you can plug realms and checkers together so that a change to your authentication backend doesn't completely change your application. Of course, that architecture is flawed in the sense that a string is a bit too narrow of a communication channel to get information about the authenticated user from one to the other, especially in cases where the application needs information from a directory service to function. If you're interested in an improved, official way to deal with this use-case, the best way to do that would be to get involved and actively try to specify what you need. I've got similar use-cases at work, as you can see here: <http://trac.calendarserver.org/browser/CalendarServer/trunk/twistedcaldav/di...> so I'd be happy to talk to you about some ideas. The best way to predict the future is to invent it. :) -glyph

On 9 January 2013 20:02, Glyph <glyph@twistedmatrix.com> wrote:
[snip]
I hope it's clear that just hard-coding your avatars and realms to work only with each other is a sub-optimal solution :).
It is very clear :)
My AvatarID Object is just for data. Let me describe one of my usage/requirement: I have a portal with credentialsChecker for both OS accounts and application specific accounts. One can have user "john" both as a local account and/or an application account. If my credentialsChecker returns only 'john', the Realm will not know from where to get user's home folder, so the returned AvatarID needs to signal the "source" of avatarID login so that it can use the same source for getting account configuration. I know that a solution is to have unique ID across all system, but in my case, this is not possible, and I have a priority list. I can encode the source in the avatar id like: john@os or john@application, but I don't see why this is better than ('john', 'os') / ('john', 'application') ---- A formal description would be: There are N authentication services and for each authentication service, there is an associated account configuration service. When an account is allowed by authentication service X, the server will retrieve account configuration from the configuration service X. ----- Another use case: I have user X with password Y. If user X is authenticated from local LAN it gets avatar Z, otherwise it gets avatar W. Here a simple AvatarID is not enough, since I also need to pass the remote peer from the transport. I keep a reference to remote peer in the Avatar. Doing this I don't need to always pass the transport, and just use the avatar. I do this to keep track of "sessions" in logs. ----- Since I don't know much about Twisted, this might be just bad design/architecture and there is some way of doing this while still using a simple avatarid. ---- I am not sure I fully understood the idirectory.py example, but rather than verifying credentials for each resource, I prefer to validate the credentials once (do one authentication) and then have a different authorization process. In some cases the credentialsChecker can do authentication and authorization in the same step. In my usage, the credentialsChecker only does autentication, and then an initial authorization is done in the realm. ----- I can discuss this in private, write a wiki page or add more details, if required. Thanks! -- Adi Roiban

On Thu, Jan 10, 2013 at 12:27:04AM +0200, Adi Roiban wrote:
Can't you use `Portal.login` interfaces paramenter? I think you have two different entry points for local and application accounts, so: # login as local account portal.login('john', None, ILocalHomeFolder) # login as application account portal.login('john', None, IApplicationLogic) class Realm: def requestAvatar(avatarId, mind, *interfaces): getAvatar = AFactory(interfaces) avatar = getAvatar(avatarId, mind) return avatar
well actually tuples are not modifiable, so they are strings, then ('john', 'os') is an unique identifier across all system with no much more information than 'john@os'. The advantage of using plain string is that they don't break the interface.
take a look at mind parameter and at twisted.words.service on how you can use it.
just my 2c.

On 10 January 2013 12:00, Marco Giusti <marco.giusti@gmail.com> wrote: [snip]
I can not do portal.login('john', None, ILocalHomeFolder) since at log in time I don't know if john is an os or application account... The credential checker will find out what kind of account is John. [snip]
Later I would like to extract the provider from the AvatatID and doing (user, provider) = AvatarID.split('@', 2) is a bit more complicated and error prone. Thanks for the notes. Cheers -- Adi Roiban

On Thu, Jan 10, 2013 at 12:34:57PM +0200, Adi Roiban wrote:
This could be an overshoot but you can have two portals, use one as the default and the second one as a fallback. I think the code will be a little bit cleaner at the checker/realm side at the price of a little bit of complexity at the login side: portal1.login(credentials, None).addErrback(lambda _: portal2.login(credentials, peer)).addCallbacks(...) But I don't know if it is worth the candle. m.

On Wed, Jan 9, 2013, at 18:02, Glyph wrote
Indeed :-)
Here's my use case. The CredentialsChecker takes a login name, e.g. "pwest", and looks it up in LDAP. It gets back an LDAP record something like this: { 'distinguishedName': 'CN=Peter Westlake,OU=User Accounts,OU=EMEA,DC=example,DC=com', 'cn': 'Peter Westlake', 'name': 'Peter Westlake', 'sn': 'Westlake', 'mail': 'Peter.Westlake@example.com', 'givenName': 'Peter', 'sAMAccountName': 'pwest' } It passes the distinguishedName and the supplied password to the LDAP password checker function for authorization. At this point the correct thing to do would be to return "pwest" as the avatarId. But I've got all that other useful information available, and it seems a shame to have to get it again in the Realm, so I return the whole dictionary. Some points to note: 1. Converting the dict to a string would make the avatarId conform to the interface, but it still wouldn't be pluggable, because other checkers wouldn't return the extra information. This strikes me as a general problem. If the checker returns more than an avatarId, whether directly or through some official-sanctioned channel, it will only be interchangeable with other checkers that also return the extra information. 2. The application knows about LDAP, and uses it to find things like your manager and your email address. Some of this information is in the avatarId, but some of it isn't, so some LDAP calls will have to be made. This weakens the argument against duplicating the lookup. The correct thing to do in this case would undoubtedly be to accept that an LDAP call isn't very expensive, and repeat it in the Realm. In other words, my use case isn't very compelling. You have shamed me into changing it :-) In one way this is a good result, though it doesn't help with the design. Having made that change, I can use a password file or an in-memory database for testing, and write test scripts that don't need a real password. That's well worth the /completely unnoticeable/ expense of an extra LDAP call! BUT: This only works because it doesn't use the user's password for binding to LDAP. If it did, then either the password or the LDAP session would have to be made available to the Realm, and we're back at square 1. Peter.

On Jan 10, 2013, at 6:41 AM, Peter Westlake <peter.westlake@pobox.com> wrote:
One potentially interesting question here, as well, is "why sAMAccountName" as the avatarID? Isn't there a UID or UUID in there somewhere? In my application, all the user identifiers are opaque, and that makes a bit more sense, since those values don't change, although any other part of the user's record could change as information evolves over time. For example, if someone gets married and changes their name, they might find it annoying to have to log in with a username that references their maiden name all the time.
As other posters have already said, you can also have your realm and your checker share a reference to an object that caches credentials. This would also let you cache other calls that are made later, too.
It seems like the "shared caching reference" would solve this problem as well? -glyph

It's possible that in some of the cases discussed above, what you want is a custom *Portal*. For example, if you want to try a sequence of logins, and choose the first that succeeds: class MultiPortal(object): def __init__(self, portals): self.portals = portals @inlineCallbacks def login(self, *args, **kwargs): for portal in self.portals: try: return portal.login(*args, **kwargs) except: continue # Ran out of portals, failed to login: raise Unauthorized() Or, let's say you're writing a backend that is a combination checker and realm, where separating that two doesn't make sense. E.g. a backend for a POP3 server that is a proxy to another POP3 server. In this case there's no point in writing separate checker and realm, just write a new Portal: class POP3ProxyPortal(object): def __init__(self, endpoint): self.endpoint = endpoint @defer.inlineCallbacks def login(self, credentials, mind, *interfaces): pop3client = yield self.endpoint.connect(POP3ClientFactory()) yield pop3client.login(credentials.username, credentials.password) defer.returnValue((IMailbox, MailboxProxy(pop3client), pop3client.quit)) -- Itamar Turner-Trauring, Future Foundries LLC http://futurefoundries.com/ — Twisted consulting, training and support.

On Sun, Jan 13, 2013 at 9:08 AM, Itamar Turner-Trauring < itamar@futurefoundries.com> wrote:
Sigh. Make that: @inlineCallbacks def login(self, *args, **kwargs): for portal in self.portals: try: returnValue(yield portal.login(*args, **kwargs)) except: continue # Ran out of portals, failed to login: raise Unauthorized()

Interesting... those portals could be on different Realms too. It would also allow more than one checker to be tried per interface, which would allow (say) a credentials.IUsernamePassword to be tried against different checkers. Peter. On Sun, Jan 13, 2013, at 14:08, Itamar Turner-Trauring wrote: It's possible that in some of the cases discussed above, what you want is a custom Portal. For example, if you want to try a sequence of logins, and choose the first that succeeds: class MultiPortal(object): def __init__(self, portals): self.portals = portals @inlineCallbacks def login(self, *args, **kwargs): for portal in self.portals: try: return portal.login(*args, **kwargs) except: continue # Ran out of portals, failed to login: raise Unauthorized() Or, let's say you're writing a backend that is a combination checker and realm, where separating that two doesn't make sense. E.g. a backend for a POP3 server that is a proxy to another POP3 server. In this case there's no point in writing separate checker and realm, just write a new Portal: class POP3ProxyPortal(object): def __init__(self, endpoint): self.endpoint = endpoint @defer.inlineCallbacks def login(self, credentials, mind, *interfaces): pop3client = yield self.endpoint.connect(POP3ClientFactory()) yield pop3client.login(credentials.username, credentials.password) defer.returnValue((IMailbox, MailboxProxy(pop3client), pop3client.quit)) -- Itamar Turner-Trauring, Future Foundries LLC [1]http://futurefoundries.com/ — Twisted consulting, training and support. _______________________________________________ Twisted-Python mailing list [2]Twisted-Python@twistedmatrix.com [3]http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python References 1. http://futurefoundries.com/ 2. mailto:Twisted-Python@twistedmatrix.com 3. http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python

On Mon, Jan 14, 2013 at 6:27 AM, Peter Westlake <peter.westlake@pobox.com>wrote:
In the first case, yes. In the second case there'd be no realm or checker at all, just a (custom) portal. Which is possible since the code using the portal doesn't know about the existence of checkers or realms.

On Sun, Jan 13, 2013, at 6:39, Glyph wrote:
That would be a good design, yes, and I'll bear it in mind for future use. But for now I'm stuck with a system that has no way to change usernames, as I discovered the hard way when my original employer was acquired. To this day I have different logins for Unix and the rest of the corporate infrastructure. (In case someone asks: the Unix name would be easy enough to change, but it's used in a lot of places. One day, maybe.) The account name is the closest thing to a unique id that's available.
Yes, I think that's the right answer. It's certainly the right design in my case, and perhaps in the general case too. Thanks for looking into this, Peter.

On Jan 14, 2013, at 3:18 AM, Peter Westlake <peter.westlake@pobox.com> wrote:
I think that this is the right design for now, given the constraints of the current cred API; however, it's certainly possible we could think up an extension to the cred API that would make this case easier to deal with. It also leaves open some not-quite-trivial questions like "how do you know when to expire the cache, which are usually easy to work out for a particular application but do not generalize easily. I hope you don't give up on thinking about a nicer and more general API just because the immediate problem is solved :). Glad I could offer useful advice, though. -glyph

On Mon, Jan 14, 2013, at 22:58, Glyph wrote:
That was the main reason I didn't actually implement a cache :-) Plus I have a nice solution that works, and only costs one LDAP call.
Thank you! The main question left in my mind is about the degree of dependency between the checker and the realm if extra information is passed, by whatever method. If the realm expects the checker to pass it (for instance) an LDAP session, then it's pretty much committed to one particular checker. That means abandoning pluggability - which admittedly isn't very sensible in that case - and once you do that, simply passing back a complex structure as an avatarId seems as good a method as any. It's simple, and it works now. Likewise Itamar's special-purpose portal suggestion. Peter.

On Jan 14, 2013, at 5:10 PM, Peter Westlake <peter.westlake@pobox.com> wrote:
The problem is not so much that pluggability is no longer possible if the realm requires something that not all checkers can provide, but that the failure mode is incoherent. For example, checkers specify the credential interfaces that they can check specifically so that they can be slotted together with a protocol seamlessly; if a protocol offers credentials that can't be checked by any of the checkers on its portal, it will try not to offer those mechanisms to its peer. Ultimately, if it tries to shove the wrong credentials in, it will still get a sensible authentication failure, not a random exception. This is important because, for example, credentials checkers are pluggable via the --auth= option to certain twistd plugins; in the future, hopefully realms could be as well. If the realm could declare what it needed from the avatar ID (or, probably, we'd want to call it something other than "ID" if it does more than identify the avatar) and it could sensibly report errors when those things were not provided, then we could have a mechanism that nicely integrated everything. (Also, stuff like this is why we use zope.interface - it makes describing the way these bits fit together relatively straightforward.) -glyph

Am 14.01.2013 um 23:58 schrieb Glyph <glyph@twistedmatrix.com>:
I was under the impression that the IRealm.requestAvatar() is *always* called *once* after ICredentialsChecker.requestAvatarId() so I simply delete the data from the cache once my avatar is created (and it works fine). Am I presuming something I shouldn’t?

Am 09.01.2013 um 14:04 schrieb Jan Urbański <wulczer@wulczer.org>:
;)
Since my final approach wasn’t mentioned yet: I found it semantically weird to pass the full data in a variable called “avatarId”, so I went with a dict as a cache that both the checker and the realm get passed in on construction. Not pretty but in my eyes still prettier. :)

On 09/01/13 13:49, Peter Westlake wrote:
I remember discussing this on IRC with someone not long ago and he pointed me to this thread: http://twistedmatrix.com/pipermail/twisted-python/2010-September/022826.html I have faced a similar problem myself and after reading the code I've resolved to wilfully disregarding the documentation and passing tuples around, accepting that if it breaks, I get to keep both pieces. Cheers, Jan

On 9 January 2013 15:04, Jan Urbański <wulczer@wulczer.org> wrote:
I am not an expert in Twisted, but from my understanding, the "string" requirement is there to provide a plugable interface. So that you can have generic credentials checkers, working with generic realms. Having simple "strings" could also help with AvatarId serialization, in case you have the CredentialsChecker on one computer and the you will pass them over network/socket to a remote Realm. I have also asked over IRC and I got the good to go answer for using anything as AvatarID. As long as you are only using your custom credentials checkers and your custom realm, everything should be ok. I am using Objects as AvatarID without any problems. Cheers, -- Adi Roiban

On Jan 9, 2013, at 9:26 AM, Peter Westlake <peter.westlake@pobox.com> wrote:
I hope it's clear that just hard-coding your avatars and realms to work only with each other is a sub-optimal solution :). The architecture of cred is supposed to be that you can plug realms and checkers together so that a change to your authentication backend doesn't completely change your application. Of course, that architecture is flawed in the sense that a string is a bit too narrow of a communication channel to get information about the authenticated user from one to the other, especially in cases where the application needs information from a directory service to function. If you're interested in an improved, official way to deal with this use-case, the best way to do that would be to get involved and actively try to specify what you need. I've got similar use-cases at work, as you can see here: <http://trac.calendarserver.org/browser/CalendarServer/trunk/twistedcaldav/di...> so I'd be happy to talk to you about some ideas. The best way to predict the future is to invent it. :) -glyph

On 9 January 2013 20:02, Glyph <glyph@twistedmatrix.com> wrote:
[snip]
I hope it's clear that just hard-coding your avatars and realms to work only with each other is a sub-optimal solution :).
It is very clear :)
My AvatarID Object is just for data. Let me describe one of my usage/requirement: I have a portal with credentialsChecker for both OS accounts and application specific accounts. One can have user "john" both as a local account and/or an application account. If my credentialsChecker returns only 'john', the Realm will not know from where to get user's home folder, so the returned AvatarID needs to signal the "source" of avatarID login so that it can use the same source for getting account configuration. I know that a solution is to have unique ID across all system, but in my case, this is not possible, and I have a priority list. I can encode the source in the avatar id like: john@os or john@application, but I don't see why this is better than ('john', 'os') / ('john', 'application') ---- A formal description would be: There are N authentication services and for each authentication service, there is an associated account configuration service. When an account is allowed by authentication service X, the server will retrieve account configuration from the configuration service X. ----- Another use case: I have user X with password Y. If user X is authenticated from local LAN it gets avatar Z, otherwise it gets avatar W. Here a simple AvatarID is not enough, since I also need to pass the remote peer from the transport. I keep a reference to remote peer in the Avatar. Doing this I don't need to always pass the transport, and just use the avatar. I do this to keep track of "sessions" in logs. ----- Since I don't know much about Twisted, this might be just bad design/architecture and there is some way of doing this while still using a simple avatarid. ---- I am not sure I fully understood the idirectory.py example, but rather than verifying credentials for each resource, I prefer to validate the credentials once (do one authentication) and then have a different authorization process. In some cases the credentialsChecker can do authentication and authorization in the same step. In my usage, the credentialsChecker only does autentication, and then an initial authorization is done in the realm. ----- I can discuss this in private, write a wiki page or add more details, if required. Thanks! -- Adi Roiban

On Thu, Jan 10, 2013 at 12:27:04AM +0200, Adi Roiban wrote:
Can't you use `Portal.login` interfaces paramenter? I think you have two different entry points for local and application accounts, so: # login as local account portal.login('john', None, ILocalHomeFolder) # login as application account portal.login('john', None, IApplicationLogic) class Realm: def requestAvatar(avatarId, mind, *interfaces): getAvatar = AFactory(interfaces) avatar = getAvatar(avatarId, mind) return avatar
well actually tuples are not modifiable, so they are strings, then ('john', 'os') is an unique identifier across all system with no much more information than 'john@os'. The advantage of using plain string is that they don't break the interface.
take a look at mind parameter and at twisted.words.service on how you can use it.
just my 2c.

On 10 January 2013 12:00, Marco Giusti <marco.giusti@gmail.com> wrote: [snip]
I can not do portal.login('john', None, ILocalHomeFolder) since at log in time I don't know if john is an os or application account... The credential checker will find out what kind of account is John. [snip]
Later I would like to extract the provider from the AvatatID and doing (user, provider) = AvatarID.split('@', 2) is a bit more complicated and error prone. Thanks for the notes. Cheers -- Adi Roiban

On Thu, Jan 10, 2013 at 12:34:57PM +0200, Adi Roiban wrote:
This could be an overshoot but you can have two portals, use one as the default and the second one as a fallback. I think the code will be a little bit cleaner at the checker/realm side at the price of a little bit of complexity at the login side: portal1.login(credentials, None).addErrback(lambda _: portal2.login(credentials, peer)).addCallbacks(...) But I don't know if it is worth the candle. m.

On Wed, Jan 9, 2013, at 18:02, Glyph wrote
Indeed :-)
Here's my use case. The CredentialsChecker takes a login name, e.g. "pwest", and looks it up in LDAP. It gets back an LDAP record something like this: { 'distinguishedName': 'CN=Peter Westlake,OU=User Accounts,OU=EMEA,DC=example,DC=com', 'cn': 'Peter Westlake', 'name': 'Peter Westlake', 'sn': 'Westlake', 'mail': 'Peter.Westlake@example.com', 'givenName': 'Peter', 'sAMAccountName': 'pwest' } It passes the distinguishedName and the supplied password to the LDAP password checker function for authorization. At this point the correct thing to do would be to return "pwest" as the avatarId. But I've got all that other useful information available, and it seems a shame to have to get it again in the Realm, so I return the whole dictionary. Some points to note: 1. Converting the dict to a string would make the avatarId conform to the interface, but it still wouldn't be pluggable, because other checkers wouldn't return the extra information. This strikes me as a general problem. If the checker returns more than an avatarId, whether directly or through some official-sanctioned channel, it will only be interchangeable with other checkers that also return the extra information. 2. The application knows about LDAP, and uses it to find things like your manager and your email address. Some of this information is in the avatarId, but some of it isn't, so some LDAP calls will have to be made. This weakens the argument against duplicating the lookup. The correct thing to do in this case would undoubtedly be to accept that an LDAP call isn't very expensive, and repeat it in the Realm. In other words, my use case isn't very compelling. You have shamed me into changing it :-) In one way this is a good result, though it doesn't help with the design. Having made that change, I can use a password file or an in-memory database for testing, and write test scripts that don't need a real password. That's well worth the /completely unnoticeable/ expense of an extra LDAP call! BUT: This only works because it doesn't use the user's password for binding to LDAP. If it did, then either the password or the LDAP session would have to be made available to the Realm, and we're back at square 1. Peter.

On Jan 10, 2013, at 6:41 AM, Peter Westlake <peter.westlake@pobox.com> wrote:
One potentially interesting question here, as well, is "why sAMAccountName" as the avatarID? Isn't there a UID or UUID in there somewhere? In my application, all the user identifiers are opaque, and that makes a bit more sense, since those values don't change, although any other part of the user's record could change as information evolves over time. For example, if someone gets married and changes their name, they might find it annoying to have to log in with a username that references their maiden name all the time.
As other posters have already said, you can also have your realm and your checker share a reference to an object that caches credentials. This would also let you cache other calls that are made later, too.
It seems like the "shared caching reference" would solve this problem as well? -glyph

It's possible that in some of the cases discussed above, what you want is a custom *Portal*. For example, if you want to try a sequence of logins, and choose the first that succeeds: class MultiPortal(object): def __init__(self, portals): self.portals = portals @inlineCallbacks def login(self, *args, **kwargs): for portal in self.portals: try: return portal.login(*args, **kwargs) except: continue # Ran out of portals, failed to login: raise Unauthorized() Or, let's say you're writing a backend that is a combination checker and realm, where separating that two doesn't make sense. E.g. a backend for a POP3 server that is a proxy to another POP3 server. In this case there's no point in writing separate checker and realm, just write a new Portal: class POP3ProxyPortal(object): def __init__(self, endpoint): self.endpoint = endpoint @defer.inlineCallbacks def login(self, credentials, mind, *interfaces): pop3client = yield self.endpoint.connect(POP3ClientFactory()) yield pop3client.login(credentials.username, credentials.password) defer.returnValue((IMailbox, MailboxProxy(pop3client), pop3client.quit)) -- Itamar Turner-Trauring, Future Foundries LLC http://futurefoundries.com/ — Twisted consulting, training and support.

On Sun, Jan 13, 2013 at 9:08 AM, Itamar Turner-Trauring < itamar@futurefoundries.com> wrote:
Sigh. Make that: @inlineCallbacks def login(self, *args, **kwargs): for portal in self.portals: try: returnValue(yield portal.login(*args, **kwargs)) except: continue # Ran out of portals, failed to login: raise Unauthorized()

Interesting... those portals could be on different Realms too. It would also allow more than one checker to be tried per interface, which would allow (say) a credentials.IUsernamePassword to be tried against different checkers. Peter. On Sun, Jan 13, 2013, at 14:08, Itamar Turner-Trauring wrote: It's possible that in some of the cases discussed above, what you want is a custom Portal. For example, if you want to try a sequence of logins, and choose the first that succeeds: class MultiPortal(object): def __init__(self, portals): self.portals = portals @inlineCallbacks def login(self, *args, **kwargs): for portal in self.portals: try: return portal.login(*args, **kwargs) except: continue # Ran out of portals, failed to login: raise Unauthorized() Or, let's say you're writing a backend that is a combination checker and realm, where separating that two doesn't make sense. E.g. a backend for a POP3 server that is a proxy to another POP3 server. In this case there's no point in writing separate checker and realm, just write a new Portal: class POP3ProxyPortal(object): def __init__(self, endpoint): self.endpoint = endpoint @defer.inlineCallbacks def login(self, credentials, mind, *interfaces): pop3client = yield self.endpoint.connect(POP3ClientFactory()) yield pop3client.login(credentials.username, credentials.password) defer.returnValue((IMailbox, MailboxProxy(pop3client), pop3client.quit)) -- Itamar Turner-Trauring, Future Foundries LLC [1]http://futurefoundries.com/ — Twisted consulting, training and support. _______________________________________________ Twisted-Python mailing list [2]Twisted-Python@twistedmatrix.com [3]http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python References 1. http://futurefoundries.com/ 2. mailto:Twisted-Python@twistedmatrix.com 3. http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-python

On Mon, Jan 14, 2013 at 6:27 AM, Peter Westlake <peter.westlake@pobox.com>wrote:
In the first case, yes. In the second case there'd be no realm or checker at all, just a (custom) portal. Which is possible since the code using the portal doesn't know about the existence of checkers or realms.

On Sun, Jan 13, 2013, at 6:39, Glyph wrote:
That would be a good design, yes, and I'll bear it in mind for future use. But for now I'm stuck with a system that has no way to change usernames, as I discovered the hard way when my original employer was acquired. To this day I have different logins for Unix and the rest of the corporate infrastructure. (In case someone asks: the Unix name would be easy enough to change, but it's used in a lot of places. One day, maybe.) The account name is the closest thing to a unique id that's available.
Yes, I think that's the right answer. It's certainly the right design in my case, and perhaps in the general case too. Thanks for looking into this, Peter.

On Jan 14, 2013, at 3:18 AM, Peter Westlake <peter.westlake@pobox.com> wrote:
I think that this is the right design for now, given the constraints of the current cred API; however, it's certainly possible we could think up an extension to the cred API that would make this case easier to deal with. It also leaves open some not-quite-trivial questions like "how do you know when to expire the cache, which are usually easy to work out for a particular application but do not generalize easily. I hope you don't give up on thinking about a nicer and more general API just because the immediate problem is solved :). Glad I could offer useful advice, though. -glyph

On Mon, Jan 14, 2013, at 22:58, Glyph wrote:
That was the main reason I didn't actually implement a cache :-) Plus I have a nice solution that works, and only costs one LDAP call.
Thank you! The main question left in my mind is about the degree of dependency between the checker and the realm if extra information is passed, by whatever method. If the realm expects the checker to pass it (for instance) an LDAP session, then it's pretty much committed to one particular checker. That means abandoning pluggability - which admittedly isn't very sensible in that case - and once you do that, simply passing back a complex structure as an avatarId seems as good a method as any. It's simple, and it works now. Likewise Itamar's special-purpose portal suggestion. Peter.

On Jan 14, 2013, at 5:10 PM, Peter Westlake <peter.westlake@pobox.com> wrote:
The problem is not so much that pluggability is no longer possible if the realm requires something that not all checkers can provide, but that the failure mode is incoherent. For example, checkers specify the credential interfaces that they can check specifically so that they can be slotted together with a protocol seamlessly; if a protocol offers credentials that can't be checked by any of the checkers on its portal, it will try not to offer those mechanisms to its peer. Ultimately, if it tries to shove the wrong credentials in, it will still get a sensible authentication failure, not a random exception. This is important because, for example, credentials checkers are pluggable via the --auth= option to certain twistd plugins; in the future, hopefully realms could be as well. If the realm could declare what it needed from the avatar ID (or, probably, we'd want to call it something other than "ID" if it does more than identify the avatar) and it could sensibly report errors when those things were not provided, then we could have a mechanism that nicely integrated everything. (Also, stuff like this is why we use zope.interface - it makes describing the way these bits fit together relatively straightforward.) -glyph

Am 14.01.2013 um 23:58 schrieb Glyph <glyph@twistedmatrix.com>:
I was under the impression that the IRealm.requestAvatar() is *always* called *once* after ICredentialsChecker.requestAvatarId() so I simply delete the data from the cache once my avatar is created (and it works fine). Am I presuming something I shouldn’t?

Am 09.01.2013 um 14:04 schrieb Jan Urbański <wulczer@wulczer.org>:
;)
Since my final approach wasn’t mentioned yet: I found it semantically weird to pass the full data in a variable called “avatarId”, so I went with a dict as a cache that both the checker and the realm get passed in on construction. Not pretty but in my eyes still prettier. :)
participants (8)
-
Adi Roiban
-
Glyph
-
Hynek Schlawack
-
Itamar Turner-Trauring
-
Jan Urbański
-
Marco Giusti
-
Peter Westlake
-
Ralph Meijer