Add Authenticator.add_user hook

and .delete_user

This hook can be used to trigger events,
such as user validation, or creating of system users.

Adds a LocalAuthenticator class that implements
checking for and rudimentary creation of system users.
This commit is contained in:
MinRK
2014-09-23 16:09:26 -07:00
parent 8b84f91bab
commit 1a29328d06
3 changed files with 92 additions and 11 deletions

View File

@@ -85,10 +85,11 @@ class UserAPIHandler(BaseUserHandler):
user.admin = data['admin']
self.db.commit()
# add to whitelist, if a whitelist is in use
if self.authenticator and self.authenticator.whitelist:
self.authenticator.whitelist.add(user.name)
try:
self.authenticator.add_user(user)
except Exception:
self.db.delete(user)
self.db.commit()
self.write(json.dumps(self.user_model(user)))
self.set_status(201)
@@ -104,9 +105,7 @@ class UserAPIHandler(BaseUserHandler):
if user.spawner is not None:
yield self.stop_single_user(user)
# remove the user from the whitelist, if there is one
if self.authenticator and user.name in self.authenticator.whitelist:
self.authenticator.whitelist.remove(user.name)
self.authenticator.delete_user(user)
# remove from the db
self.db.delete(user)

View File

@@ -3,11 +3,14 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import pwd
from subprocess import check_call, check_output, CalledProcessError
from tornado import gen
import simplepam
from IPython.config import LoggingConfigurable
from IPython.utils.traitlets import Unicode, Set
from IPython.utils.traitlets import Bool, Set, Unicode
from .utils import url_path_join
@@ -35,6 +38,25 @@ class Authenticator(LoggingConfigurable):
and return None on failed authentication.
"""
def add_user(self, user):
"""Add a new user
By default, this just adds the user to the whitelist.
Subclasses may do more extensive things,
such as adding actual unix users.
"""
if self.whitelist:
self.whitelist.add(user.name)
def delete_user(self, user):
"""Triggered when a user is deleted.
Removes the user from the whitelist.
"""
if user.name in self.whitelist:
self.whitelist.remove(user.name)
def login_url(self, base_url):
"""Override to register a custom login handler"""
return url_path_join(base_url, 'login')
@@ -50,10 +72,66 @@ class Authenticator(LoggingConfigurable):
"""
return []
class LocalAuthenticator(Authenticator):
"""Base class for Authenticators that work with local *ix users
Checks for local users, and can attempt to create them if they exist.
"""
create_system_users = Bool(False, config=True,
help="""If a user is added that doesn't exist on the system,
should I try to create the system user?
"""
)
def add_user(self, user):
"""Add a new user
By default, this just adds the user to the whitelist.
Subclasses may do more extensive things,
such as adding actual unix users.
"""
if not self.system_user_exists(user):
if self.create_system_users:
self.add_system_user(user)
else:
raise KeyError("User %s does not exist." % user.name)
super(LocalAuthenticator, self).add_user(user)
def system_user_exists(self, user):
"""Check if the user exists on the system"""
try:
pwd.getpwnam(user.name)
except KeyError:
return False
else:
return True
def add_system_user(user):
"""Create a new *ix user on the system. Works on FreeBSD and Linux, at least."""
name = user.name
for useradd in (
['pw', 'useradd', '-m'],
['useradd', '-m'],
):
try:
check_output(['which', useradd[0]])
except CalledProcessError:
continue
else:
break
else:
raise RuntimeError("I don't know how to add users on this system.")
check_call(useradd + [name])
class PAMAuthenticator(Authenticator):
class PAMAuthenticator(LocalAuthenticator):
"""Authenticate local *ix users with PAM"""
encoding = Unicode('utf8', config=True,
help="""The encoding to use for PAM """
help="""The encoding to use for PAM"""
)
service = Unicode('login', config=True,
help="""The PAM service to use for authentication."""

View File

@@ -14,7 +14,7 @@ from IPython.utils.py3compat import unicode_type
from ..spawner import LocalProcessSpawner
from ..app import JupyterHubApp
from ..auth import PAMAuthenticator
from ..auth import PAMAuthenticator, Authenticator
from .. import orm
def mock_authenticate(username, password, service='login'):
@@ -46,6 +46,10 @@ class MockSpawner(LocalProcessSpawner):
class MockPAMAuthenticator(PAMAuthenticator):
def system_user_exists(self, user):
# skip the add-system-user bit
return True
def authenticate(self, *args, **kwargs):
with mock.patch('simplepam.authenticate', mock_authenticate):
return super(MockPAMAuthenticator, self).authenticate(*args, **kwargs)