Authentication: Difference between revisions
(13 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
== Different use cases for different login systems == |
|||
Different login systems may affect the way auth stuff works differently. For example, all of these cases are very different, but all fall under the "auth world view": |
|||
* You log in via some central auth system thing that uses the mediagoblin login page interface, but passes off the username/password to some other database. (If that username and password exist remotely, do we need to "give" that username to a newly created user?) |
|||
* Persona/OpenID style: You have a user, but you have the option to log in the user via some external page and then come back. In this case, we're probably not creating new users on-demand, and we don't really use the login page at all anyway. |
|||
== Designing Authentication API == |
== Designing Authentication API == |
||
Line 15: | Line 22: | ||
_auth_providers.append(provider) |
_auth_providers.append(provider) |
||
def get_response(function, *args): |
|||
for p in _auth_providers: |
|||
response = p.function(*args) |
|||
if response: |
|||
return response |
|||
raise NotImplemented("No provider for %s" % functions) |
|||
def check_login(self, user, password): |
|||
return get_response("check_login", user, password) |
|||
for p in _auth_providers: |
|||
res = p.check_login(user, password) |
|||
if res: |
|||
return res |
|||
raise NotImplementedError</nowiki> |
|||
auth_plugin/__init__.py: |
auth_plugin/__init__.py: |
||
def setup_plugin(): |
def setup_plugin(): |
||
add_auth_provider( |
add_auth_provider(SomeAuthPlugin(config1)) |
||
add_auth_provider(SomeAuthPlugin(config2))</nowiki> |
|||
====Pros==== |
====Pros==== |
||
* Plugins could inherit from another plugin and override. |
|||
* Plugins can configure the individual instance (ldap server name) |
|||
* Plugins can instantiate multiple providers for different backends, like two different ldap servers |
|||
====Cons==== |
====Cons==== |
||
Line 36: | Line 48: | ||
===Interfacey Design w/ hooks:=== |
===Interfacey Design w/ hooks:=== |
||
Using an interface design similar to the Base Class design below and using hooks to register the auth_plugin interface. |
Using an interface design similar to the Base Class design below and using hooks to register the auth_plugin interface. Basicly a variation on the previous idea, just a bit more "hooky". |
||
Ex: |
Ex: |
||
auth/__init__.py:<nowiki> |
auth/__init__.py:<nowiki> |
||
... |
|||
def setup_auth(): |
def setup_auth(): |
||
global _auth_providers |
global _auth_providers |
||
_auth_providers = hook_runall("auth_provider") |
_auth_providers += hook_runall("auth_provider") |
||
... |
|||
auth_plugin/__init__.py: |
|||
class AuthUserInterface(object): |
|||
hooks = {"auth_provider": get_auth_provider} |
|||
def get_auth_provider(): return AuthUserInterface()</nowiki> |
|||
for p in _auth_providers: |
|||
res = p.check_login(user, password) |
|||
if res: |
|||
return res |
|||
raise NotImplementedError</nowiki> |
|||
auth_plugin/__init__.py:<nowiki> |
|||
hooks = {"auth_provider": AuthUserInterface()}</nowiki> |
|||
====Pros==== |
====Pros==== |
||
* Plugins could inherit from another plugin and override. |
|||
* Still allows configuration on the individual instance |
|||
====Cons==== |
====Cons==== |
||
* would basically be duplicating the code for hook_handle |
* would basically be duplicating the code for hook_handle |
||
* No easy way to add multiple instances of one Provider |
|||
===Non-Interfacey Design w/ hooks for every call:=== |
===Non-Interfacey Design w/ hooks for every call:=== |
||
This is the way we are going to implement auth plugins. |
|||
This design would have a plugin "template" in auth/__init__.py similar to the Interfacey designs, except that it would use hook_handle() for each function. |
This design would have a plugin "template" in auth/__init__.py similar to the Interfacey designs, except that it would use hook_handle() for each function. |
||
Ex. |
Ex. |
||
mediagoblin.ini:<nowiki> |
|||
[plugins] |
|||
[[plugins.ldap]] |
|||
[[[server1]]] |
|||
[[[server2]]] |
|||
auth/__init__.py: |
|||
def check_login(user, Password): |
def check_login(user, Password): |
||
return hook_handle("auth_check_login", user, password) |
return hook_handle("auth_check_login", user, password) |
||
auth_plugin/__init__.py: |
auth_plugin/__init__.py: |
||
... |
|||
hooks = {"auth_check_login": check_login</nowiki> |
|||
hooks = {"auth_check_login": check_login} |
|||
# or depending on the plugin |
|||
def setup_plugin(): |
|||
serv1 = ldap_thing(config1) |
|||
serv2 = ldap_thing(config2) |
|||
pluginapi.add_hook("check_login", serv1.check_login) |
|||
pluginapi.add_hook("check_login", serv2.check_login) |
|||
... |
|||
def check_login(user, password): |
|||
if a: return True # Yes, let them in |
|||
if b: return False # No and don't try other providers |
|||
return None # Don't know, try next provider.</nowiki> |
|||
====Pros==== |
====Pros==== |
||
Line 79: | Line 108: | ||
====Cons==== |
====Cons==== |
||
All the Pros from the interface design are not here: |
|||
* config data has to be global on the plugin, not local to the instantiated provider |
|||
* No straight way of doing multiple instances. |
|||
*: Yes, the individual hook could do it by itself |
|||
==Various ideas on how to use classy authentication providers with the hooky authentication system== |
|||
<nowiki> |
|||
... |
|||
class LDAPLoginer(): |
|||
pass |
|||
for ldap_config in ldap_configs: |
|||
my_ldap = LDAPLoginer(ldap_config) |
|||
add_hook('login': my_ldap.login) |
|||
... |
|||
... |
|||
def add_classy_hooks(cls, hook_list): |
|||
q = {} |
|||
for h in hook_list: |
|||
q[h] = getattr(cls, h) |
|||
pluginapi.add_hooks_dict(q) |
|||
for ldap_config in ldap_configs: |
|||
ldap = LDAPAuth(ldap_config) |
|||
add_classy_hooks(ldap, ['check_login']) |
|||
...</nowiki> |
|||
== Proposed DB Schema == |
|||
Would remove pw_hash from User model. Reason is if someone would like to have the only way to login to an account is with OpenID or Persona. We would still require every account to have a unique username regardless of the Authentication enabled, thus username will be left in the User model. Email would be left in the User model for the same reason as username. |
|||
===OpenID/Persona:=== |
|||
openid_url = Column(unicode, nullable=False) |
|||
user = Column(Integer, ForeignKey(User,id), nullable=False) |
|||
== __init__.py Base Class == |
== __init__.py Base Class == |
Latest revision as of 14:25, 6 May 2013
Different use cases for different login systems
Different login systems may affect the way auth stuff works differently. For example, all of these cases are very different, but all fall under the "auth world view":
- You log in via some central auth system thing that uses the mediagoblin login page interface, but passes off the username/password to some other database. (If that username and password exist remotely, do we need to "give" that username to a newly created user?)
- Persona/OpenID style: You have a user, but you have the option to log in the user via some external page and then come back. In this case, we're probably not creating new users on-demand, and we don't really use the login page at all anyway.
Designing Authentication API
To make our auth(entication) system more modular, we're likely going to have some sort of interface style API for it. This page is to design it.
Auth Plugin Design Pros/Cons
Interfacey Design w/o hooks:
Using an interface design similar to the Base Class design below. Calls would be added to _auth_providers list in the dummy class when setup_plugin is run. Each function in the dummy class would run through the _auth_providers list and return the response from the corresponding function from the last plugin in _auth_providers list.
Ex:
auth/__init__.py: _auth_providers = [] def add_auth_provider(provider): _auth_providers.append(provider) def get_response(function, *args): for p in _auth_providers: response = p.function(*args) if response: return response raise NotImplemented("No provider for %s" % functions) def check_login(self, user, password): return get_response("check_login", user, password) auth_plugin/__init__.py: def setup_plugin(): add_auth_provider(SomeAuthPlugin(config1)) add_auth_provider(SomeAuthPlugin(config2))
Pros
- Plugins could inherit from another plugin and override.
- Plugins can configure the individual instance (ldap server name)
- Plugins can instantiate multiple providers for different backends, like two different ldap servers
Cons
- basically be duplicating the code for hook_handle
Interfacey Design w/ hooks:
Using an interface design similar to the Base Class design below and using hooks to register the auth_plugin interface. Basicly a variation on the previous idea, just a bit more "hooky".
Ex:
auth/__init__.py: ... def setup_auth(): global _auth_providers _auth_providers += hook_runall("auth_provider") ... auth_plugin/__init__.py: hooks = {"auth_provider": get_auth_provider} def get_auth_provider(): return AuthUserInterface()
Pros
- Plugins could inherit from another plugin and override.
- Still allows configuration on the individual instance
Cons
- would basically be duplicating the code for hook_handle
- No easy way to add multiple instances of one Provider
Non-Interfacey Design w/ hooks for every call:
This is the way we are going to implement auth plugins.
This design would have a plugin "template" in auth/__init__.py similar to the Interfacey designs, except that it would use hook_handle() for each function.
Ex. mediagoblin.ini: [plugins] [[plugins.ldap]] [[[server1]]] [[[server2]]] auth/__init__.py: def check_login(user, Password): return hook_handle("auth_check_login", user, password) auth_plugin/__init__.py: ... hooks = {"auth_check_login": check_login} # or depending on the plugin def setup_plugin(): serv1 = ldap_thing(config1) serv2 = ldap_thing(config2) pluginapi.add_hook("check_login", serv1.check_login) pluginapi.add_hook("check_login", serv2.check_login) ... def check_login(user, password): if a: return True # Yes, let them in if b: return False # No and don't try other providers return None # Don't know, try next provider.
Pros
- Simpler to implement
Cons
All the Pros from the interface design are not here:
- config data has to be global on the plugin, not local to the instantiated provider
- No straight way of doing multiple instances.
- Yes, the individual hook could do it by itself
Various ideas on how to use classy authentication providers with the hooky authentication system
... class LDAPLoginer(): pass for ldap_config in ldap_configs: my_ldap = LDAPLoginer(ldap_config) add_hook('login': my_ldap.login) ... ... def add_classy_hooks(cls, hook_list): q = {} for h in hook_list: q[h] = getattr(cls, h) pluginapi.add_hooks_dict(q) for ldap_config in ldap_configs: ldap = LDAPAuth(ldap_config) add_classy_hooks(ldap, ['check_login']) ...
Proposed DB Schema
Would remove pw_hash from User model. Reason is if someone would like to have the only way to login to an account is with OpenID or Persona. We would still require every account to have a unique username regardless of the Authentication enabled, thus username will be left in the User model. Email would be left in the User model for the same reason as username.
OpenID/Persona:
openid_url = Column(unicode, nullable=False)
user = Column(Integer, ForeignKey(User,id), nullable=False)
__init__.py Base Class
- This is a brainstorm of some of the functions and variables that the base class should include.***
basic_auth = False # Will be used to render to correct forms if using both basic_auth and openid/persona
login_form = # Plugin LoginForm class
registration_form = # Plugin RegistrationForm class
class UserAuthInterface(object): deg _raise_not_implemented(self): # Will raise a warning if some component of this interface isn't implemented by an Auth plugin def check_login(self, user, password): return False def get_user(self, *args): # Will query database and will return a User() object def create_user(self, *args): # Will create a new user and save to the db. # Will return User() object def extra_validation(self, register_form, *args): # Will query the db and add error messages to register_form if any. # return true if able to create new user def get_user_metadata(self, user): # Return a nice object with metadata from auth provider. Used to pre-fill registration forms