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

View File

@@ -3,11 +3,14 @@
# Copyright (c) IPython Development Team. # Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License. # Distributed under the terms of the Modified BSD License.
import pwd
from subprocess import check_call, check_output, CalledProcessError
from tornado import gen from tornado import gen
import simplepam import simplepam
from IPython.config import LoggingConfigurable 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 from .utils import url_path_join
@@ -35,6 +38,25 @@ class Authenticator(LoggingConfigurable):
and return None on failed authentication. 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): def login_url(self, base_url):
"""Override to register a custom login handler""" """Override to register a custom login handler"""
return url_path_join(base_url, 'login') return url_path_join(base_url, 'login')
@@ -50,10 +72,66 @@ class Authenticator(LoggingConfigurable):
""" """
return [] 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, encoding = Unicode('utf8', config=True,
help="""The encoding to use for PAM """ help="""The encoding to use for PAM"""
) )
service = Unicode('login', config=True, service = Unicode('login', config=True,
help="""The PAM service to use for authentication.""" 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 ..spawner import LocalProcessSpawner
from ..app import JupyterHubApp from ..app import JupyterHubApp
from ..auth import PAMAuthenticator from ..auth import PAMAuthenticator, Authenticator
from .. import orm from .. import orm
def mock_authenticate(username, password, service='login'): def mock_authenticate(username, password, service='login'):
@@ -46,6 +46,10 @@ class MockSpawner(LocalProcessSpawner):
class MockPAMAuthenticator(PAMAuthenticator): class MockPAMAuthenticator(PAMAuthenticator):
def system_user_exists(self, user):
# skip the add-system-user bit
return True
def authenticate(self, *args, **kwargs): def authenticate(self, *args, **kwargs):
with mock.patch('simplepam.authenticate', mock_authenticate): with mock.patch('simplepam.authenticate', mock_authenticate):
return super(MockPAMAuthenticator, self).authenticate(*args, **kwargs) return super(MockPAMAuthenticator, self).authenticate(*args, **kwargs)