validate usernames

via Authenticator.validate_username

base class configurable with Authenticator.username_pattern
This commit is contained in:
Min RK
2016-01-08 15:49:16 +01:00
parent beb2dae6ce
commit 9441fa37c5
5 changed files with 62 additions and 1 deletions

View File

@@ -33,14 +33,25 @@ class UserListAPIHandler(APIHandler):
admin = data.get('admin', False)
to_create = []
invalid_names = []
for name in usernames:
name = self.authenticator.normalize_username(name)
if not self.authenticator.validate_username(name):
invalid_names.append(name)
continue
user = self.find_user(name)
if user is not None:
self.log.warn("User %s already exists" % name)
else:
to_create.append(name)
if invalid_names:
if len(invalid_names) == 1:
msg = "Invalid username: %s" % invalid_names[0]
else:
msg = "Invalid usernames: %s" % ', '.join(invalid_names)
raise web.HTTPError(400, msg)
if not to_create:
raise web.HTTPError(400, "All %i users already exist" % len(usernames))

View File

@@ -634,6 +634,9 @@ class JupyterHub(Application):
self.authenticator.normalize_username(name)
for name in self.authenticator.admin_users
]
for username in admin_users:
if not self.authenticator.validate_username(username):
raise ValueError("username %r is not valid" % username)
if not admin_users:
self.log.warning("No admin users, admin interface will be unavailable.")
@@ -658,6 +661,9 @@ class JupyterHub(Application):
self.authenticator.normalize_username(name)
for name in self.authenticator.whitelist
]
for username in whitelist:
if not self.authenticator.validate_username(username):
raise ValueError("username %r is not valid" % username)
if not whitelist:
self.log.info("Not using whitelist. Any authenticated user will be allowed.")

View File

@@ -14,7 +14,7 @@ from tornado import gen
import pamela
from traitlets.config import LoggingConfigurable
from traitlets import Bool, Set, Unicode, Any
from traitlets import Bool, Set, Unicode, Dict, Any
from .handlers.login import LoginHandler
from .utils import url_path_join
@@ -53,6 +53,28 @@ class Authenticator(LoggingConfigurable):
"""
)
username_pattern = Unicode(config=True,
help="""Regular expression pattern for validating usernames.
If not defined: allow any username.
"""
)
def _username_pattern_changed(self, name, old, new):
if not new:
self.username_regex = None
self.username_regex = re.compile(new)
username_regex = Any()
def validate_username(self, username):
"""Validate a (normalized) username.
Return True if username is valid, False otherwise.
"""
if not self.username_regex:
return True
return bool(self.username_regex.match(username))
username_map = Dict(config=True,
help="""Dictionary mapping authenticator usernames to JupyterHub users.
@@ -144,6 +166,8 @@ class Authenticator(LoggingConfigurable):
Subclasses may do more extensive things,
such as adding actual unix users.
"""
if not self.validate_username(user.name):
raise ValueError("Invalid username: %s" % user.name)
if self.whitelist:
self.whitelist.add(user.name)

View File

@@ -209,6 +209,17 @@ def test_add_multi_user_bad(app):
r = api_request(app, 'users', method='post', data='[]')
assert r.status_code == 400
def test_add_multi_user_invalid(app):
app.authenticator.username_pattern = r'w.*'
r = api_request(app, 'users', method='post',
data=json.dumps({'usernames': ['Willow', 'Andrew', 'Tara']})
)
app.authenticator.username_pattern = ''
assert r.status_code == 400
assert r.json()['message'] == 'Invalid usernames: andrew, tara'
def test_add_multi_user(app):
db = app.db
names = ['a', 'b']

View File

@@ -169,3 +169,12 @@ def test_username_map(io_loop):
assert authorized == 'inara'
def test_validate_names(io_loop):
a = auth.PAMAuthenticator()
assert a.validate_username('willow')
assert a.validate_username('giles')
a = auth.PAMAuthenticator(username_pattern='w.*')
assert not a.validate_username('xander')
assert a.validate_username('willow')