diff --git a/docs/source/getting-started/authenticators-users-basics.md b/docs/source/getting-started/authenticators-users-basics.md index a2647976..ec078fed 100644 --- a/docs/source/getting-started/authenticators-users-basics.md +++ b/docs/source/getting-started/authenticators-users-basics.md @@ -7,20 +7,20 @@ with an account and password on the system will be allowed to login. ## Create a set of allowed users You can restrict which users are allowed to login with a set, -`Authenticator.allowed`: +`Authenticator.allowed_users`: ```python -c.Authenticator.allowed = {'mal', 'zoe', 'inara', 'kaylee'} +c.Authenticator.allowed_users = {'mal', 'zoe', 'inara', 'kaylee'} ``` -Users in the allowed set are added to the Hub database when the Hub is +Users in the `allowed_users` set are added to the Hub database when the Hub is started. ## Configure admins (`admin_users`) Admin users of JupyterHub, `admin_users`, can add and remove users from -the user `allowed` set. `admin_users` can take actions on other users' +the user `allowed_users` set. `admin_users` can take actions on other users' behalf, such as stopping and restarting their servers. A set of initial admin users, `admin_users` can configured be as follows: @@ -28,7 +28,7 @@ A set of initial admin users, `admin_users` can configured be as follows: ```python c.Authenticator.admin_users = {'mal', 'zoe'} ``` -Users in the admin set are automatically added to the user `allowed` set, +Users in the admin set are automatically added to the user `allowed_users` set, if they are not already present. Each authenticator may have different ways of determining whether a user is an @@ -53,12 +53,12 @@ sure your users know if admin_access is enabled.** Users can be added to and removed from the Hub via either the admin panel or the REST API. When a user is **added**, the user will be -automatically added to the allowed set and database. Restarting the Hub -will not require manually updating the allowed set in your config file, +automatically added to the allowed users set and database. Restarting the Hub +will not require manually updating the allowed users set in your config file, as the users will be loaded from the database. After starting the Hub once, it is not sufficient to **remove** a user -from the allowed set in your config file. You must also remove the user +from the allowed users set in your config file. You must also remove the user from the Hub's database, either by deleting the user from JupyterHub's admin page, or you can clear the `jupyterhub.sqlite` database and start fresh. diff --git a/docs/source/reference/config-ghoauth.md b/docs/source/reference/config-ghoauth.md index 4183ddec..6ec46e1c 100644 --- a/docs/source/reference/config-ghoauth.md +++ b/docs/source/reference/config-ghoauth.md @@ -52,7 +52,7 @@ c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL'] c.LocalAuthenticator.create_system_users = True # specify users and admin -c.Authenticator.allowed = {'rgbkrk', 'minrk', 'jhamrick'} +c.Authenticator.allowed_users = {'rgbkrk', 'minrk', 'jhamrick'} c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'} # uses the default spawner diff --git a/jupyterhub/app.py b/jupyterhub/app.py index ba549aa4..4135f8f0 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -1689,22 +1689,22 @@ class JupyterHub(Application): # the admin_users config variable will never be used after this point. # only the database values will be referenced. - allowed = [ + allowed_users = [ self.authenticator.normalize_username(name) - for name in self.authenticator.allowed + for name in self.authenticator.allowed_users ] - self.authenticator.allowed = set(allowed) # force normalization - for username in allowed: + self.authenticator.allowed_users = set(allowed_users) # force normalization + for username in allowed_users: if not self.authenticator.validate_username(username): raise ValueError("username %r is not valid" % username) - if not allowed: + if not allowed_users: self.log.info( - "Not using allowed user list. Any authenticated user will be allowed." + "Not using allowed_users. Any authenticated user will be allowed." ) # add allowed users to the db - for name in allowed: + for name in allowed_users: user = orm.User.find(db, name) if user is None: user = orm.User(name=name) @@ -1714,9 +1714,9 @@ class JupyterHub(Application): db.commit() # Notify authenticator of all users. - # This ensures Authenticator.allowed is up-to-date with the database. - # This lets .allowed be used to set up initial list, - # but changes to the allowed list can occur in the database, + # This ensures Authenticator.allowed_users is up-to-date with the database. + # This lets .allowed_users be used to set up initial list, + # but changes to the allowed_users set can occur in the database, # and persist across sessions. total_users = 0 for user in db.query(orm.User): @@ -1753,9 +1753,9 @@ class JupyterHub(Application): user.created = user.last_activity or datetime.utcnow() db.commit() - # The allowed set and the users in the db are now the same. + # The allowed_users set and the users in the db are now the same. # From this point on, any user changes should be done simultaneously - # to the allowed set and user db, unless the allowed set is empty (all users allowed). + # to the allowed_users set and user db, unless the allowed set is empty (all users allowed). TOTAL_USERS.set(total_users) @@ -1773,7 +1773,7 @@ class JupyterHub(Application): await maybe_future(self.authenticator.check_allowed(username, None)) ): raise ValueError( - "Username %r is not in Authenticator.allowed" % username + "Username %r is not in Authenticator.allowed_users" % username ) user = orm.User.find(db, name=username) if user is None: @@ -1801,7 +1801,8 @@ class JupyterHub(Application): await maybe_future(self.authenticator.check_allowed(name, None)) ): raise ValueError( - "Token user name %r is not in Authenticator.allowed" % name + "Token user name %r is not in Authenticator.allowed_users" + % name ) if not self.authenticator.validate_username(name): raise ValueError("Token user name %r is not valid" % name) diff --git a/jupyterhub/auth.py b/jupyterhub/auth.py index cfc82541..dfbfab79 100644 --- a/jupyterhub/auth.py +++ b/jupyterhub/auth.py @@ -101,9 +101,9 @@ class Authenticator(LoggingConfigurable): """ ).tag(config=True) - whitelist = Set(help="Deprecated, use `Authenticator.allowed`", config=True,) + whitelist = Set(help="Deprecated, use `Authenticator.allowed_users`", config=True,) - allowed = Set( + allowed_users = Set( help=""" Set of usernames that are allowed to log in. @@ -114,11 +114,11 @@ class Authenticator(LoggingConfigurable): If empty, does not perform any additional restriction. .. versionchanged:: 1.2 - `Authenticator.whitelist` renamed to `allowed` + `Authenticator.whitelist` renamed to `allowed_users` """ ).tag(config=True) - blocked = Set( + blocked_users = Set( help=""" Set of usernames that are not allowed to log in. @@ -131,13 +131,13 @@ class Authenticator(LoggingConfigurable): .. versionadded: 0.9 .. versionchanged:: 1.2 - `Authenticator.blacklist` renamed to `blocked` + `Authenticator.blacklist` renamed to `blocked_users` """ ).tag(config=True) _deprecated_aliases = { - "whitelist": ("allowed", "1.2"), - "blacklist": ("blocked", "1.2"), + "whitelist": ("allowed_users", "1.2"), + "blacklist": ("blocked_users", "1.2"), } @observe(*list(_deprecated_aliases)) @@ -160,15 +160,15 @@ class Authenticator(LoggingConfigurable): ) setattr(self, new_attr, change.new) - @observe('allowed') - def _check_allowed(self, change): + @observe('allowed_users') + def _check_allowed_users(self, change): short_names = [name for name in change['new'] if len(name) <= 1] if short_names: sorted_names = sorted(short_names) single = ''.join(sorted_names) string_set_typo = "set('%s')" % single self.log.warning( - "Allowed list contains single-character names: %s; did you mean set([%r]) instead of %s?", + "Allowed set contains single-character names: %s; did you mean set([%r]) instead of %s?", sorted_names[:8], single, string_set_typo, @@ -301,7 +301,7 @@ class Authenticator(LoggingConfigurable): # with correct subclass override priority! for old_name, new_name in ( ('check_whitelist', 'check_allowed'), - ('check_blacklist', 'check_blocked'), + ('check_blacklist', 'check_blocked_users'), ('check_group_whitelist', 'check_allowed_groups'), ): old_method = getattr(self, old_name, None) @@ -399,7 +399,7 @@ class Authenticator(LoggingConfigurable): """Check if a username is allowed to authenticate based on configuration Return True if username is allowed, False otherwise. - No allowed set means any username is allowed. + No allowed_users set means any username is allowed. Names are normalized *before* being checked against the allowed set. @@ -409,12 +409,12 @@ class Authenticator(LoggingConfigurable): .. versionchanged:: 1.2 Renamed check_whitelist to check_allowed """ - if not self.allowed: + if not self.allowed_users: # No allowed set means any name is allowed return True - return username in self.allowed + return username in self.allowed_users - def check_blocked(self, username, authentication=None): + def check_blocked_users(self, username, authentication=None): """Check if a username is blocked to authenticate based on Authenticator.blocked configuration Return True if username is allowed, False otherwise. @@ -428,12 +428,12 @@ class Authenticator(LoggingConfigurable): Signature updated to accept authentication data as second argument .. versionchanged:: 1.2 - Renamed check_blacklist to check_blocked + Renamed check_blacklist to check_blocked_users """ - if not self.blocked: + if not self.blocked_users: # No block list means any name is allowed return True - return username not in self.blocked + return username not in self.blocked_users async def get_authenticated_user(self, handler, data): """Authenticate the user who is attempting to log in @@ -450,7 +450,7 @@ class Authenticator(LoggingConfigurable): The various stages can be overridden separately: - `authenticate` turns formdata into a username - `normalize_username` normalizes the username - - `check_allowed` checks against the user allowed + - `check_allowed` checks against the allowed usernames .. versionchanged:: 0.8 return dict instead of username @@ -475,7 +475,9 @@ class Authenticator(LoggingConfigurable): self.log.warning("Disallowing invalid username %r.", username) return - blocked_pass = await maybe_future(self.check_blocked(username, authenticated)) + blocked_pass = await maybe_future( + self.check_blocked_users(username, authenticated) + ) allowed_pass = await maybe_future(self.check_allowed(username, authenticated)) if blocked_pass: @@ -550,7 +552,7 @@ class Authenticator(LoggingConfigurable): It must return the username on successful authentication, and return None on failed authentication. - Checking allowed/blocked is handled separately by the caller. + Checking allowed_users/blocked_users is handled separately by the caller. .. versionchanged:: 0.8 Allow `authenticate` to return a dict containing auth_state. @@ -591,10 +593,10 @@ class Authenticator(LoggingConfigurable): This method may be a coroutine. - By default, this just adds the user to the allowed set. + By default, this just adds the user to the allowed_users set. Subclasses may do more extensive things, such as adding actual unix users, - but they should call super to ensure the allowed set is updated. + but they should call super to ensure the allowed_users set is updated. Note that this should be idempotent, since it is called whenever the hub restarts for all users. @@ -604,19 +606,19 @@ class Authenticator(LoggingConfigurable): """ if not self.validate_username(user.name): raise ValueError("Invalid username: %s" % user.name) - if self.allowed: - self.allowed.add(user.name) + if self.allowed_users: + self.allowed_users.add(user.name) def delete_user(self, user): """Hook called when a user is deleted - Removes the user from the allowed set. - Subclasses should call super to ensure the allowed set is updated. + Removes the user from the allowed_users set. + Subclasses should call super to ensure the allowed_users set is updated. Args: user (User): The User wrapper object """ - self.allowed.discard(user.name) + self.allowed_users.discard(user.name) auto_login = Bool( False, @@ -709,7 +711,7 @@ import types # deprecate white/blacklist method names for _old_name, _new_name, _version in [ ("check_whitelist", "check_allowed", "1.2"), - ("check_blacklist", "check_blocked", "1.2"), + ("check_blacklist", "check_blocked_users", "1.2"), ]: setattr( Authenticator, _old_name, _deprecated_method(_old_name, _new_name, _version), @@ -788,9 +790,9 @@ class LocalAuthenticator(Authenticator): @observe('allowed_groups') def _allowed_groups_changed(self, change): """Log a warning if mutually exclusive user and group allowed sets are specified.""" - if self.allowed: + if self.allowed_users: self.log.warning( - "Ignoring Authenticator.allowed set because Authenticator.allowed_groups supplied!" + "Ignoring Authenticator.allowed_users set because Authenticator.allowed_groups supplied!" ) def check_allowed(self, username, authentication=None): diff --git a/jupyterhub/tests/test_app.py b/jupyterhub/tests/test_app.py index 55000bc7..e00c699e 100644 --- a/jupyterhub/tests/test_app.py +++ b/jupyterhub/tests/test_app.py @@ -93,7 +93,7 @@ def test_generate_config(): os.remove(cfg_file) assert cfg_file in out assert 'Spawner.cmd' in cfg_text - assert 'Authenticator.allowed' in cfg_text + assert 'Authenticator.allowed_users' in cfg_text async def test_init_tokens(request): diff --git a/jupyterhub/tests/test_auth.py b/jupyterhub/tests/test_auth.py index 0021b1da..51cd4c2a 100644 --- a/jupyterhub/tests/test_auth.py +++ b/jupyterhub/tests/test_auth.py @@ -141,7 +141,7 @@ async def test_pam_auth_admin_groups(): async def test_pam_auth_allowed(): - authenticator = MockPAMAuthenticator(allowed={'wash', 'kaylee'}) + authenticator = MockPAMAuthenticator(allowed_users={'wash', 'kaylee'}) authorized = await authenticator.get_authenticated_user( None, {'username': 'kaylee', 'password': 'kaylee'} ) @@ -186,41 +186,51 @@ async def test_pam_auth_blocked(): assert authorized['name'] == 'wash' # Blacklist basics - authenticator = MockPAMAuthenticator(blocked={'wash'}) + authenticator = MockPAMAuthenticator(blocked_users={'wash'}) authorized = await authenticator.get_authenticated_user( None, {'username': 'wash', 'password': 'wash'} ) assert authorized is None # User in both allowed and blocked: default deny. Make error someday? - authenticator = MockPAMAuthenticator(blocked={'wash'}, allowed={'wash', 'kaylee'}) + authenticator = MockPAMAuthenticator( + blocked_users={'wash'}, allowed_users={'wash', 'kaylee'} + ) authorized = await authenticator.get_authenticated_user( None, {'username': 'wash', 'password': 'wash'} ) assert authorized is None # User not in blocked set can log in - authenticator = MockPAMAuthenticator(blocked={'wash'}, allowed={'wash', 'kaylee'}) + authenticator = MockPAMAuthenticator( + blocked_users={'wash'}, allowed_users={'wash', 'kaylee'} + ) authorized = await authenticator.get_authenticated_user( None, {'username': 'kaylee', 'password': 'kaylee'} ) assert authorized['name'] == 'kaylee' # User in allowed, blocked irrelevent - authenticator = MockPAMAuthenticator(blocked={'mal'}, allowed={'wash', 'kaylee'}) + authenticator = MockPAMAuthenticator( + blocked_users={'mal'}, allowed_users={'wash', 'kaylee'} + ) authorized = await authenticator.get_authenticated_user( None, {'username': 'wash', 'password': 'wash'} ) assert authorized['name'] == 'wash' # User in neither list - authenticator = MockPAMAuthenticator(blocked={'mal'}, allowed={'wash', 'kaylee'}) + authenticator = MockPAMAuthenticator( + blocked_users={'mal'}, allowed_users={'wash', 'kaylee'} + ) authorized = await authenticator.get_authenticated_user( None, {'username': 'simon', 'password': 'simon'} ) assert authorized is None - authenticator = MockPAMAuthenticator(blocked=set(), allowed={'wash', 'kaylee'}) + authenticator = MockPAMAuthenticator( + blocked_users=set(), allowed_users={'wash', 'kaylee'} + ) authorized = await authenticator.get_authenticated_user( None, {'username': 'kaylee', 'password': 'kaylee'} ) @@ -256,7 +266,7 @@ async def test_pam_auth_no_such_group(): async def test_wont_add_system_user(): user = orm.User(name='lioness4321') - authenticator = auth.PAMAuthenticator(allowed={'mal'}) + authenticator = auth.PAMAuthenticator(allowed_users={'mal'}) authenticator.create_system_users = False with pytest.raises(KeyError): await authenticator.add_user(user) @@ -264,7 +274,7 @@ async def test_wont_add_system_user(): async def test_cant_add_system_user(): user = orm.User(name='lioness4321') - authenticator = auth.PAMAuthenticator(allowed={'mal'}) + authenticator = auth.PAMAuthenticator(allowed_users={'mal'}) authenticator.add_user_cmd = ['jupyterhub-fake-command'] authenticator.create_system_users = True @@ -290,7 +300,7 @@ async def test_cant_add_system_user(): async def test_add_system_user(): user = orm.User(name='lioness4321') - authenticator = auth.PAMAuthenticator(allowed={'mal'}) + authenticator = auth.PAMAuthenticator(allowed_users={'mal'}) authenticator.create_system_users = True authenticator.add_user_cmd = ['echo', '/home/USERNAME'] @@ -311,13 +321,13 @@ async def test_add_system_user(): async def test_delete_user(): user = orm.User(name='zoe') - a = MockPAMAuthenticator(allowed={'mal'}) + a = MockPAMAuthenticator(allowed_users={'mal'}) - assert 'zoe' not in a.allowed + assert 'zoe' not in a.allowed_users await a.add_user(user) - assert 'zoe' in a.allowed + assert 'zoe' in a.allowed_users a.delete_user(user) - assert 'zoe' not in a.allowed + assert 'zoe' not in a.allowed_users def test_urls(): @@ -472,10 +482,10 @@ def test_deprecated_config(caplog): log.name, logging.WARNING, 'Authenticator.whitelist is deprecated in JupyterHub 1.2, use ' - 'Authenticator.allowed instead', + 'Authenticator.allowed_users instead', ) ] - assert authenticator.allowed == {'user'} + assert authenticator.allowed_users == {'user'} def test_deprecated_methods(): @@ -496,7 +506,7 @@ def test_deprecated_config_subclass(): cfg.MyAuthenticator.whitelist = {'user'} with pytest.deprecated_call(): authenticator = MyAuthenticator(config=cfg) - assert authenticator.allowed == {'user'} + assert authenticator.allowed_users == {'user'} def test_deprecated_methods_subclass():