Repository: jupyterhub/oauthenticator
Pull request #366 (2020-09-05 18:02:02)

Rename OAuthenticator.whitelist to allow
- [x] deprecate traits, config
- [x] test deprecated names still work
- [x] test deprecated config
- [x] update docs

https://github.com/jupyterhub/team-compass/issues/299
https://github.com/jupyterhub/jupyterhub/pull/3090


Patch:
diff --git a/docs/source/example-oauthenticator.py b/docs/source/example-oauthenticator.py
index 272b438..b89a708 100644
--- a/docs/source/example-oauthenticator.py
+++ b/docs/source/example-oauthenticator.py
@@ -89,7 +89,7 @@ class GitHubOAuthenticator(OAuthenticator):
             # and login has failed
             return None
 
-        # here we can add additional checks such as against team whitelists
+        # here we can add additional checks such as against team allowed lists
         # if the OAuth provider has such a concept
 
         # 'name' is the JupyterHub username
diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst
index 4acdb18..49ad4da 100644
--- a/docs/source/getting-started.rst
+++ b/docs/source/getting-started.rst
@@ -7,7 +7,7 @@ The general steps to take when using OAuthenticator:
 2. Register with the provider
 3. Choose an authenticator class, or use :class:`~.oauthenticator.generic.GenericOAuthenticator`
    and configure JupyterHub to use it
-4. Configure the authenticator class (client_id, client_secret, callback_url, whitelist, etc.)
+4. Configure the authenticator class (client_id, client_secret, callback_url, allowed_users, etc.)
 5. Specific configuration for your identity provider
 
 OAuthenticator currently supports the following **identity providers**:
@@ -209,13 +209,13 @@ groups by setting
 
 ::
 
-   c.GitLabOAuthenticator.gitlab_project_id_whitelist = [ ... ]
+   c.GitLabOAuthenticator.allowed_gitlab_project_ids = [ ... ]
 
 and
 
 ::
 
-   c.GitLabOAuthenticator.gitlab_group_whitelist = [ ... ]
+   c.GitLabOAuthenticator.allowed_gitlab_groups = [ ... ]
 
 but be aware that each entry incurs a separate API call, increasing the
 risk of rate limiting and timeouts.
diff --git a/docs/source/google.md b/docs/source/google.md
index ea66ea4..ab19c25 100644
--- a/docs/source/google.md
+++ b/docs/source/google.md
@@ -46,21 +46,21 @@ and give it read only access to users and groups.
 
 **Note:** if you remove a member from a google group you will have to force this user to login again in order for the change to take effect
 
-#### if you want to manage admin users and whitelisted via google groups
+#### if you want to manage admin users and allowed users via google groups
 
 ```python
 c.GoogleOAuthenticator.gsuite_administrator = {'example.com': 'someuser'}
 c.GoogleOAuthenticator.google_service_account_keys = {'example.com': '/path/to/service_account.json'}
 c.GoogleOAuthenticator.admin_google_groups = {'example.com': ['someadmingroup']}
-c.GoogleOAuthenticator.google_group_whitelist = {'example.com': ['somegroupwithaccess', 'othergroupwithaccess'] }
+c.GoogleOAuthenticator.allowed_google_groups = {'example.com': ['somegroupwithaccess', 'othergroupwithaccess'] }
 ```
 
-#### if you only want to whitelist users via google groups
+#### if you only want to allow users via google groups
 
 ```python
 c.GoogleOAuthenticator.gsuite_administrator = {'example.com': 'someuser'}
 c.GoogleOAuthenticator.google_service_account_keys = {'example.com': '/path/to/service_account.json'}
-c.GoogleOAuthenticator.google_group_whitelist = {'example.com': ['somegroupwithaccess', 'othergroupwithaccess'] }
+c.GoogleOAuthenticator.allowed_google_groups = {'example.com': ['somegroupwithaccess', 'othergroupwithaccess'] }
 ```
 ### You are done!
 
diff --git a/examples/full/jupyterhub_config.py b/examples/full/jupyterhub_config.py
index 7d2b303..5e8c7d5 100644
--- a/examples/full/jupyterhub_config.py
+++ b/examples/full/jupyterhub_config.py
@@ -8,7 +8,7 @@ c.JupyterHub.authenticator_class = LocalGitHubOAuthenticator
 
 c.LocalGitHubOAuthenticator.create_system_users = True
 
-c.Authenticator.whitelist = whitelist = set()
+c.Authenticator.allowed_users = allowed_users = set()
 c.JupyterHub.admin_users = admin = set()
 
 import os
@@ -26,7 +26,7 @@ with open(join(root, 'userlist')) as f:
             continue
         parts = line.split()
         name = parts[0]
-        whitelist.add(name)
+        allowed_users.add(name)
         if len(parts) > 1 and parts[1] == 'admin':
             admin.add(name)
 
diff --git a/oauthenticator/bitbucket.py b/oauthenticator/bitbucket.py
index 04db87f..a3d2060 100644
--- a/oauthenticator/bitbucket.py
+++ b/oauthenticator/bitbucket.py
@@ -13,7 +13,7 @@ from tornado.httpclient import HTTPRequest, AsyncHTTPClient
 
 from jupyterhub.auth import LocalAuthenticator
 
-from traitlets import Set, default
+from traitlets import Set, default, observe
 
 from .oauth2 import OAuthLoginHandler, OAuthenticator
 
@@ -28,6 +28,14 @@ def _api_headers(access_token):
 
 class BitbucketOAuthenticator(OAuthenticator):
 
+    _deprecated_aliases = {
+        "team_whitelist": ("allowed_teams", "0.12.0"),
+    }
+
+    @observe(*list(_deprecated_aliases))
+    def _deprecated_trait(self, change):
+        super()._deprecated_trait(change)
+
     login_service = "Bitbucket"
     client_id_env = 'BITBUCKET_CLIENT_ID'
     client_secret_env = 'BITBUCKET_CLIENT_SECRET'
@@ -40,11 +48,12 @@ class BitbucketOAuthenticator(OAuthenticator):
     def _token_url_default(self):
         return "https://bitbucket.org/site/oauth2/access_token"
 
-    team_whitelist = Set(
-        config=True, help="Automatically whitelist members of selected teams"
+    team_whitelist = Set(help="Deprecated, use `BitbucketOAuthenticator.allowed_teams`", config=True,)
+
+    allowed_teams = Set(
+        config=True, help="Automatically allow members of selected teams"
     )
 
-    bitbucket_team_whitelist = team_whitelist
 
     headers = {
         "Accept": "application/json",
@@ -93,12 +102,12 @@ class BitbucketOAuthenticator(OAuthenticator):
 
         username = resp_json["username"]
 
-        # Check if user is a member of any whitelisted teams.
+        # Check if user is a member of any allowed teams.
         # This check is performed here, as the check requires `access_token`.
-        if self.bitbucket_team_whitelist:
-            user_in_team = await self._check_team_whitelist(username, access_token)
+        if self.allowed_teams:
+            user_in_team = await self._check_membership_allowed_teams(username, access_token)
             if not user_in_team:
-                self.log.warning("%s not in team whitelist", username)
+                self.log.warning("%s not in team allowed list of users", username)
                 return None
 
         return {
@@ -106,7 +115,7 @@ class BitbucketOAuthenticator(OAuthenticator):
             'auth_state': {'access_token': access_token, 'bitbucket_user': resp_json},
         }
 
-    async def _check_team_whitelist(self, username, access_token):
+    async def _check_membership_allowed_teams(self, username, access_token):
         http_client = AsyncHTTPClient()
 
         headers = _api_headers(access_token)
@@ -121,8 +130,8 @@ class BitbucketOAuthenticator(OAuthenticator):
             next_page = resp_json.get('next', None)
 
             user_teams = set([entry["username"] for entry in resp_json["values"]])
-            # check if any of the organizations seen thus far are in whitelist
-            if len(self.bitbucket_team_whitelist & user_teams) > 0:
+            # check if any of the organizations seen thus far are in the allowed list
+            if len(self.allowed_teams & user_teams) > 0:
                 return True
         return False
 
diff --git a/oauthenticator/cilogon.py b/oauthenticator/cilogon.py
index ae88bf9..09c680d 100644
--- a/oauthenticator/cilogon.py
+++ b/oauthenticator/cilogon.py
@@ -4,7 +4,7 @@ Uses OAuth 2.0 with cilogon.org (override with CILOGON_HOST)
 
 Caveats:
 
-- For user whitelist/admin purposes, username will be the ePPN by default.
+- For allowed user list /admin purposes, username will be the ePPN by default.
   This is typically an email address and may not work as a Unix userid.
   Normalization may be required to turn the JupyterHub username into a Unix username.
 - Default username_claim of ePPN does not work for all providers,
@@ -22,7 +22,7 @@ from tornado import web
 from tornado.httputil import url_concat
 from tornado.httpclient import HTTPRequest, AsyncHTTPClient
 
-from traitlets import Unicode, List, Bool, default, validate
+from traitlets import Unicode, List, Bool, default, validate, observe
 
 from jupyterhub.auth import LocalAuthenticator
 
@@ -44,6 +44,14 @@ class CILogonLoginHandler(OAuthLoginHandler):
 
 
 class CILogonOAuthenticator(OAuthenticator):
+    _deprecated_aliases = {
+        "idp_whitelist": ("allowed_idps", "0.12.0"),
+    }
+
+    @observe(*list(_deprecated_aliases))
+    def _deprecated_trait(self, change):
+        super()._deprecated_trait(change)
+
     login_service = "CILogon"
 
     client_id_env = 'CILOGON_CLIENT_ID'
@@ -78,7 +86,8 @@ class CILogonOAuthenticator(OAuthenticator):
             return ['openid'] + proposal.value
         return proposal.value
 
-    idp_whitelist = List(
+    idp_whitelist = List(help="Deprecated, use `CIlogonOAuthenticator.allowed_idps`", config=True,)
+    allowed_idps = List(
         config=True,
         help="""A list of IDP which can be stripped from the username after the @ sign.""",
     )
@@ -86,7 +95,7 @@ class CILogonOAuthenticator(OAuthenticator):
         False,
         config=True,
         help="""Remove the IDP domain from the username. Note that only domains which
-             appear in the `idp_whitelist` will be stripped.""",
+             appear in the `allowed_idps` will be stripped.""",
     )
     idp = Unicode(
         config=True,
@@ -187,14 +196,14 @@ class CILogonOAuthenticator(OAuthenticator):
                 )
             raise web.HTTPError(500, "Failed to get username from CILogon")
 
-        if self.idp_whitelist:
+        if self.allowed_idps:
             gotten_name, gotten_idp = username.split('@')
-            if gotten_idp not in self.idp_whitelist:
+            if gotten_idp not in self.allowed_idps:
                 self.log.error(
-                    "Trying to login from not whitelisted domain %s", gotten_idp
+                    "Trying to login from not allowed domain %s", gotten_idp
                 )
-                raise web.HTTPError(500, "Trying to login from not whitelisted domain")
-            if len(self.idp_whitelist) == 1 and self.strip_idp_domain:
+                raise web.HTTPError(500, "Trying to login from a domain not allowed")
+            if len(self.allowed_idps) == 1 and self.strip_idp_domain:
                 username = gotten_name
         userdict = {"name": username}
         # Now we set up auth_state
diff --git a/oauthenticator/github.py b/oauthenticator/github.py
index 3086393..2697d9d 100644
--- a/oauthenticator/github.py
+++ b/oauthenticator/github.py
@@ -17,7 +17,7 @@ from tornado.httpclient import HTTPRequest, AsyncHTTPClient, HTTPError
 
 from jupyterhub.auth import LocalAuthenticator
 
-from traitlets import List, Set, Unicode, default
+from traitlets import List, Set, Unicode, default, observe
 
 from .common import next_page_from_links
 from .oauth2 import OAuthLoginHandler, OAuthenticator
@@ -37,6 +37,14 @@ class GitHubOAuthenticator(OAuthenticator):
     # set scopes via config, e.g.
     # c.GitHubOAuthenticator.scope = ['read:org']
 
+    _deprecated_aliases = {
+        "github_organization_whitelist": ("allowed_organizations", "0.12.0"),
+    }
+
+    @observe(*list(_deprecated_aliases))
+    def _deprecated_trait(self, change):
+        super()._deprecated_trait(change)
+
     login_service = "GitHub"
 
     github_url = Unicode("https://github.com", config=True)
@@ -108,8 +116,10 @@ class GitHubOAuthenticator(OAuthenticator):
     client_id_env = 'GITHUB_CLIENT_ID'
     client_secret_env = 'GITHUB_CLIENT_SECRET'
 
-    github_organization_whitelist = Set(
-        config=True, help="Automatically whitelist members of selected organizations"
+    github_organization_whitelist = Set(help="Deprecated, use `GitHubOAuthenticator.allowed_organizations`", config=True,)
+
+    allowed_organizations = Set(
+        config=True, help="Automatically allow members of selected organizations"
     )
 
     async def authenticate(self, handler, data=None):
@@ -168,17 +178,17 @@ class GitHubOAuthenticator(OAuthenticator):
         # username is now the GitHub userid.
         if not username:
             return None
-        # Check if user is a member of any whitelisted organizations.
+        # Check if user is a member of any allowed organizations.
         # This check is performed here, as it requires `access_token`.
-        if self.github_organization_whitelist:
-            for org in self.github_organization_whitelist:
-                user_in_org = await self._check_organization_whitelist(
+        if self.allowed_organizations:
+            for org in self.allowed_organizations:
+                user_in_org = await self._check_membership_allowed_organizations(
                     org, username, access_token
                 )
                 if user_in_org:
                     break
             else:  # User not found in member list for any organisation
-                self.log.warning("User %s is not in org whitelist", username)
+                self.log.warning("User %s is not in allowed org list", username)
                 return None
         userdict = {"name": username}
         # Now we set up auth_state
@@ -197,7 +207,7 @@ class GitHubOAuthenticator(OAuthenticator):
 
         return userdict
 
-    async def _check_organization_whitelist(self, org, username, access_token):
+    async def _check_membership_allowed_organizations(self, org, username, access_token):
         http_client = AsyncHTTPClient()
         headers = _api_headers(access_token)
         # Check membership of user `username` for organization `org` via api [check-membership](https://developer.github.com/v3/orgs/members/#check-membership)
diff --git a/oauthenticator/gitlab.py b/oauthenticator/gitlab.py
index c653653..50ce226 100644
--- a/oauthenticator/gitlab.py
+++ b/oauthenticator/gitlab.py
@@ -19,7 +19,7 @@ from tornado.httpclient import HTTPRequest, AsyncHTTPClient
 
 from jupyterhub.auth import LocalAuthenticator
 
-from traitlets import Set, CUnicode, Unicode, default
+from traitlets import Set, CUnicode, Unicode, default, observe
 
 from .oauth2 import OAuthLoginHandler, OAuthenticator
 
@@ -37,6 +37,15 @@ class GitLabOAuthenticator(OAuthenticator):
     # set scopes via config, e.g.
     # c.GitLabOAuthenticator.scope = ['read_user']
 
+    _deprecated_aliases = {
+        "gitlab_group_whitelist": ("allowed_gitlab_groups", "0.12.0"),
+        "gitlab_project_id_whitelist": ("allowed_project_ids", "0.12.0")
+    }
+
+    @observe(*list(_deprecated_aliases))
+    def _deprecated_trait(self, change):
+        super()._deprecated_trait(change)
+
     login_service = "GitLab"
 
     client_id_env = 'GITLAB_CLIENT_ID'
@@ -93,12 +102,17 @@ class GitLabOAuthenticator(OAuthenticator):
     def _token_url_default(self):
         return "%s/oauth/access_token" % self.gitlab_url
 
-    gitlab_group_whitelist = Set(
-        config=True, help="Automatically whitelist members of selected groups"
+    gitlab_group_whitelist = Set(help="Deprecated, use `GitLabOAuthenticator.allowed_gitlab_groups`", config=True,)
+
+    allowed_gitlab_groups = Set(
+        config=True, help="Automatically allow members of selected groups"
     )
-    gitlab_project_id_whitelist = Set(
+
+    gitlab_project_id_whitelist = Set(help="Deprecated, use `GitLabOAuthenticator.allowed_project_ids`", config=True,)
+
+    allowed_project_ids = Set(
         config=True,
-        help="Automatically whitelist members with Developer access to selected project ids",
+        help="Automatically allow members with Developer access to selected project ids",
     )
 
     gitlab_version = None
@@ -157,19 +171,19 @@ class GitLabOAuthenticator(OAuthenticator):
         user_id = resp_json["id"]
         is_admin = resp_json.get("is_admin", False)
 
-        # Check if user is a member of any whitelisted groups or projects.
+        # Check if user is a member of any allowed groups or projects.
         # These checks are performed here, as it requires `access_token`.
         user_in_group = user_in_project = False
         is_group_specified = is_project_id_specified = False
 
-        if self.gitlab_group_whitelist:
+        if self.allowed_gitlab_groups:
             is_group_specified = True
-            user_in_group = await self._check_group_whitelist(user_id, access_token)
+            user_in_group = await self._check_membership_allowed_groups(user_id, access_token)
 
-        # We skip project_id check if user is in whitelisted group.
-        if self.gitlab_project_id_whitelist and not user_in_group:
+        # We skip project_id check if user is in allowed group.
+        if self.allowed_project_ids and not user_in_group:
             is_project_id_specified = True
-            user_in_project = await self._check_project_id_whitelist(
+            user_in_project = await self._check_membership_allowed_project_ids(
                 user_id, access_token
             )
 
@@ -185,7 +199,7 @@ class GitLabOAuthenticator(OAuthenticator):
                 'auth_state': {'access_token': access_token, 'gitlab_user': resp_json},
             }
         else:
-            self.log.warning("%s not in group or project whitelist", username)
+            self.log.warning("%s not in group or project allowed list", username)
             return None
 
     async def _get_gitlab_version(self, access_token):
@@ -202,11 +216,11 @@ class GitLabOAuthenticator(OAuthenticator):
         version_ints = list(map(int, version_strings))
         return version_ints
 
-    async def _check_group_whitelist(self, user_id, access_token):
+    async def _check_membership_allowed_groups(self, user_id, access_token):
         http_client = AsyncHTTPClient()
         headers = _api_headers(access_token)
-        # Check if user is a member of any group in the whitelist
-        for group in map(url_escape, self.gitlab_group_whitelist):
+        # Check if user is a member of any group in the allowed list
+        for group in map(url_escape, self.allowed_gitlab_groups):
             url = "%s/groups/%s/members/%s%d" % (
                 self.gitlab_api,
                 quote(group, safe=''),
@@ -219,11 +233,11 @@ class GitLabOAuthenticator(OAuthenticator):
                 return True  # user _is_ in group
         return False
 
-    async def _check_project_id_whitelist(self, user_id, access_token):
+    async def _check_membership_allowed_project_ids(self, user_id, access_token):
         http_client = AsyncHTTPClient()
         headers = _api_headers(access_token)
-        # Check if user has developer access to any project in the whitelist
-        for project in self.gitlab_project_id_whitelist:
+        # Check if user has developer access to any project in the allowed list
+        for project in self.allowed_project_ids:
             url = "%s/projects/%s/members/%s%d" % (
                 self.gitlab_api,
                 project,
diff --git a/oauthenticator/google.py b/oauthenticator/google.py
index 56d68ff..4e90800 100644
--- a/oauthenticator/google.py
+++ b/oauthenticator/google.py
@@ -13,7 +13,7 @@ from tornado.httpclient import HTTPRequest, AsyncHTTPClient
 from tornado.auth import GoogleOAuth2Mixin
 from tornado.web import HTTPError
 
-from traitlets import Dict, Unicode, List, default, validate
+from traitlets import Dict, Unicode, List, default, validate, observe
 
 from jupyterhub.crypto import decrypt, EncryptionUnavailable, InvalidToken
 from jupyterhub.auth import LocalAuthenticator
@@ -30,6 +30,14 @@ def check_user_in_groups(member_groups, allowed_groups):
 
 
 class GoogleOAuthenticator(OAuthenticator, GoogleOAuth2Mixin):
+    _deprecated_aliases = {
+        "google_group_whitelist": ("allowed_google_groups", "0.12.0"),
+    }
+
+    @observe(*list(_deprecated_aliases))
+    def _deprecated_trait(self, change):
+        super()._deprecated_trait(change)
+
     google_api_url = Unicode("https://www.googleapis.com", config=True)
 
     @default('google_api_url')
@@ -65,9 +73,11 @@ class GoogleOAuthenticator(OAuthenticator, GoogleOAuth2Mixin):
         help="Username of a G Suite Administrator for the service account to act as"
     ).tag(config=True)
 
-    google_group_whitelist = Dict(
+    google_group_whitelist = Dict(help="Deprecated, use `GoogleOAuthenticator.allowed_google_groups`", config=True,)
+
+    allowed_google_groups = Dict(
         List(Unicode()),
-        help="Automatically whitelist members of selected groups"
+        help="Automatically allow members of selected groups"
     ).tag(config=True)
 
     admin_google_groups = Dict(
@@ -195,7 +205,7 @@ class GoogleOAuthenticator(OAuthenticator, GoogleOAuth2Mixin):
             }
         }
 
-        if self.admin_google_groups or self.google_group_whitelist:
+        if self.admin_google_groups or self.allowed_google_groups:
             user_info = await self._add_google_groups_info(user_info, google_groups)
 
         return user_info
@@ -274,8 +284,8 @@ class GoogleOAuthenticator(OAuthenticator, GoogleOAuth2Mixin):
         # Check if user is a member of any admin groups.
         if self.admin_google_groups:
             is_admin = check_user_in_groups(google_groups, self.admin_google_groups[user_email_domain])
-        # Check if user is a member of any whitelisted groups.
-        user_in_group = check_user_in_groups(google_groups, self.google_group_whitelist[user_email_domain])
+        # Check if user is a member of any allowed groups.
+        user_in_group = check_user_in_groups(google_groups, self.allowed_google_groups[user_email_domain])
 
         if self.admin_google_groups and (is_admin or user_in_group):
             user_info['admin'] = is_admin
diff --git a/oauthenticator/oauth2.py b/oauthenticator/oauth2.py
index 4f0a5f9..8e3ee78 100644
--- a/oauthenticator/oauth2.py
+++ b/oauthenticator/oauth2.py
@@ -18,7 +18,7 @@ from jupyterhub.handlers import BaseHandler
 from jupyterhub.auth import Authenticator
 from jupyterhub.utils import url_path_join
 
-from traitlets import Unicode, Bool, List, Dict, default
+from traitlets import Unicode, Bool, List, Dict, default, observe
 
 
 def guess_callback_uri(protocol, host, hub_server_url):
@@ -337,3 +337,23 @@ class OAuthenticator(Authenticator):
 
     async def authenticate(self, handler, data=None):
         raise NotImplementedError()
+
+
+    def _deprecated_trait(self, change):
+        """observer for deprecated traits"""
+        old_attr = change.name
+        new_attr, version = self._deprecated_aliases.get(old_attr)
+        new_value = getattr(self, new_attr)
+        if new_value != change.new:
+            # only warn if different
+            # protects backward-compatible config from warnings
+            # if they set the same value under both names
+            self.log.warning(
+                "{cls}.{old} is deprecated in {cls} {version}, use {cls}.{new} instead".format(
+                    cls=self.__class__.__name__,
+                    old=old_attr,
+                    new=new_attr,
+                    version=version,
+                )
+            )
+            setattr(self, new_attr, change.new)


Test patch:
diff --git a/oauthenticator/tests/test_bitbucket.py b/oauthenticator/tests/test_bitbucket.py
index 72ff833..fa9eeba 100644
--- a/oauthenticator/tests/test_bitbucket.py
+++ b/oauthenticator/tests/test_bitbucket.py
@@ -1,7 +1,9 @@
 import os
 from unittest.mock import patch
 
+import logging
 from pytest import fixture, mark
+from traitlets.config import Config
 
 from ..bitbucket import BitbucketOAuthenticator
 
@@ -36,10 +38,10 @@ async def test_bitbucket(bitbucket_client):
     assert 'bitbucket_user' in auth_state
 
 
-async def test_team_whitelist(bitbucket_client):
+async def test_allowed_teams(bitbucket_client):
     client = bitbucket_client
     authenticator = BitbucketOAuthenticator()
-    authenticator.bitbucket_team_whitelist = ['blue']
+    authenticator.allowed_teams = ['blue']
 
     teams = {
         'red': ['grif', 'simmons', 'donut', 'sarge', 'lopez'],
@@ -70,7 +72,7 @@ async def test_team_whitelist(bitbucket_client):
     assert name is None
 
     # reverse it, just to be safe
-    authenticator.team_whitelist = ['red']
+    authenticator.allowed_teams = ['red']
 
     handler = client.handler_for_user(user_model('caboose'))
     name = await authenticator.authenticate(handler)
@@ -80,3 +82,20 @@ async def test_team_whitelist(bitbucket_client):
     user_info = await authenticator.authenticate(handler)
     name = user_info['name']
     assert name == 'donut'
+
+def test_deprecated_config(caplog):
+    cfg = Config()
+    cfg.BitbucketOAuthenticator.team_whitelist = ['red']
+
+    log = logging.getLogger("testlog")
+    authenticator = BitbucketOAuthenticator(config=cfg, log=log)
+    assert caplog.record_tuples == [
+        (
+            log.name,
+            logging.WARNING,
+            'BitbucketOAuthenticator.team_whitelist is deprecated in BitbucketOAuthenticator 0.12.0, use '
+            'BitbucketOAuthenticator.allowed_teams instead',
+        )
+    ]
+
+    assert authenticator.allowed_teams == {"red"}
diff --git a/oauthenticator/tests/test_cilogon.py b/oauthenticator/tests/test_cilogon.py
index 642aba2..02478f1 100644
--- a/oauthenticator/tests/test_cilogon.py
+++ b/oauthenticator/tests/test_cilogon.py
@@ -1,8 +1,9 @@
 import json
 
+import logging
 from pytest import fixture, mark, raises
-
 from tornado.web import HTTPError
+from traitlets.config import Config
 
 from ..cilogon import CILogonOAuthenticator
 
@@ -95,3 +96,21 @@ async def test_cilogon_missing_alternate_claim(cilogon_client):
         alternative_user_model('jtkirk@ufp.gov', 'uid'))
     with raises(HTTPError):
         user_info = await authenticator.authenticate(handler)
+
+
+def test_deprecated_config(caplog):
+    cfg = Config()
+    cfg.CILogonOAuthenticator.idp_whitelist = ['pink']
+
+    log = logging.getLogger("testlog")
+    authenticator = CILogonOAuthenticator(config=cfg, log=log)
+    assert caplog.record_tuples == [
+        (
+            log.name,
+            logging.WARNING,
+            'CILogonOAuthenticator.idp_whitelist is deprecated in CILogonOAuthenticator 0.12.0, use '
+            'CILogonOAuthenticator.allowed_idps instead',
+        )
+    ]
+
+    assert authenticator.allowed_idps == ['pink']
diff --git a/oauthenticator/tests/test_github.py b/oauthenticator/tests/test_github.py
index 75c5217..38d854b 100644
--- a/oauthenticator/tests/test_github.py
+++ b/oauthenticator/tests/test_github.py
@@ -3,10 +3,12 @@ import functools
 import json
 from io import BytesIO
 
+import logging
 from pytest import fixture, mark
 from urllib.parse import urlparse, parse_qs
 from tornado.httpclient import HTTPRequest, HTTPResponse
 from tornado.httputil import HTTPHeaders
+from traitlets.config import Config
 
 from ..github import GitHubOAuthenticator
 
@@ -58,7 +60,7 @@ def make_link_header(urlinfo, page):
                     .format(urlinfo.scheme, urlinfo.netloc, urlinfo.path, page)}
 
 
-async def test_org_whitelist(github_client):
+async def test_allowed_org_membership(github_client):
     client = github_client
     authenticator = GitHubOAuthenticator()
 
@@ -127,7 +129,7 @@ async def test_org_whitelist(github_client):
         client_hosts.append((membership_regex, team_membership))
         client_hosts.append((member_regex, functools.partial(team_members, paginate)))
 
-        authenticator.github_organization_whitelist = ['blue']
+        authenticator.allowed_organizations = ['blue']
 
         handler = client.handler_for_user(user_model('caboose'))
         user = await authenticator.authenticate(handler)
@@ -138,7 +140,7 @@ async def test_org_whitelist(github_client):
         assert user is None
 
         # reverse it, just to be safe
-        authenticator.github_organization_whitelist = ['red']
+        authenticator.allowed_organizations = ['red']
 
         handler = client.handler_for_user(user_model('caboose'))
         user = await authenticator.authenticate(handler)
@@ -150,3 +152,20 @@ async def test_org_whitelist(github_client):
 
         client_hosts.pop()
         client_hosts.pop()
+
+def test_deprecated_config(caplog):
+    cfg = Config()
+    cfg.GitHubOAuthenticator.github_organization_whitelist = ["jupy"]
+
+    log = logging.getLogger("testlog")
+    authenticator = GitHubOAuthenticator(config=cfg, log=log)
+    assert caplog.record_tuples == [
+        (
+            log.name,
+            logging.WARNING,
+            'GitHubOAuthenticator.github_organization_whitelist is deprecated in GitHubOAuthenticator 0.12.0, use '
+            'GitHubOAuthenticator.allowed_organizations instead',
+        )
+    ]
+
+    assert authenticator.allowed_organizations == {"jupy"}
diff --git a/oauthenticator/tests/test_gitlab.py b/oauthenticator/tests/test_gitlab.py
index 635c98d..fe2d3b1 100644
--- a/oauthenticator/tests/test_gitlab.py
+++ b/oauthenticator/tests/test_gitlab.py
@@ -5,8 +5,10 @@ import functools
 import collections
 from urllib.parse import urlparse, parse_qs
 
+import logging
 from tornado.httpclient import HTTPResponse
 from tornado.httputil import HTTPHeaders
+from traitlets.config import Config
 from pytest import fixture, mark
 
 from ..gitlab import GitLabOAuthenticator
@@ -64,7 +66,7 @@ def make_link_header(urlinfo, page):
                     .format(urlinfo.scheme, urlinfo.netloc, urlinfo.path, page)}
 
 
-async def test_group_whitelist(gitlab_client):
+async def test_allowed_groups(gitlab_client):
     client = gitlab_client
     authenticator = GitLabOAuthenticator()
     mock_api_version(client, '12.4.0-ee')
@@ -132,7 +134,7 @@ async def test_group_whitelist(gitlab_client):
             (API_ENDPOINT + '/groups', functools.partial(groups, paginate))
         )
 
-        authenticator.gitlab_group_whitelist = ['blue']
+        authenticator.allowed_gitlab_groups = ['blue']
 
         handler = client.handler_for_user(group_user_model('caboose'))
         user_info = await authenticator.authenticate(handler)
@@ -153,7 +155,7 @@ async def test_group_whitelist(gitlab_client):
         assert name is None
 
         # reverse it, just to be safe
-        authenticator.gitlab_group_whitelist = ['red']
+        authenticator.allowed_gitlab_groups = ['red']
 
         handler = client.handler_for_user(group_user_model('caboose'))
         name = await authenticator.authenticate(handler)
@@ -167,7 +169,7 @@ async def test_group_whitelist(gitlab_client):
         client.hosts['gitlab.com'].pop()
 
 
-async def test_project_id_whitelist(gitlab_client):
+async def test_allowed_project_ids(gitlab_client):
     client = gitlab_client
     authenticator = GitLabOAuthenticator()
     mock_api_version(client, '12.4.0-pre')
@@ -221,7 +223,7 @@ async def test_project_id_whitelist(gitlab_client):
         (member_regex, is_member)
     )
 
-    authenticator.gitlab_project_id_whitelist = [1231231]
+    authenticator.allowed_project_ids = [1231231]
 
     # Forbidden since John has guest access
     handler = client.handler_for_user(john_user_model)
@@ -239,17 +241,35 @@ async def test_project_id_whitelist(gitlab_client):
     user_info = await authenticator.authenticate(handler)
     assert user_info is None
 
-    authenticator.gitlab_project_id_whitelist = [123123152543]
+    authenticator.allowed_project_ids = [123123152543]
 
     # Forbidden since the project does not exist.
     handler = client.handler_for_user(harry_user_model)
     user_info = await authenticator.authenticate(handler)
     assert user_info is None
 
-    authenticator.gitlab_project_id_whitelist = [123123152543, 1231231]
+    authenticator.allowed_project_ids = [123123152543, 1231231]
 
     # Authenticated since Harry has developer access to one of the project in the list
     handler = client.handler_for_user(harry_user_model)
     user_info = await authenticator.authenticate(handler)
     name = user_info['name']
     assert name == 'harry'
+
+
+def test_deprecated_config(caplog):
+    cfg = Config()
+    cfg.GitLabOAuthenticator.gitlab_group_whitelist = {'red'}
+
+    log = logging.getLogger("testlog")
+    authenticator = GitLabOAuthenticator(config=cfg, log=log)
+    assert caplog.record_tuples == [
+        (
+            log.name,
+            logging.WARNING,
+            'GitLabOAuthenticator.gitlab_group_whitelist is deprecated in GitLabOAuthenticator 0.12.0, use '
+            'GitLabOAuthenticator.allowed_gitlab_groups instead'
+        )
+    ]
+
+    assert authenticator.allowed_gitlab_groups == {'red'}
diff --git a/oauthenticator/tests/test_google.py b/oauthenticator/tests/test_google.py
index 9e485e6..3e6d58f 100644
--- a/oauthenticator/tests/test_google.py
+++ b/oauthenticator/tests/test_google.py
@@ -1,8 +1,11 @@
 import re
 from unittest.mock import Mock
 
+import logging
 from pytest import fixture, mark, raises
 from tornado.web import Application, HTTPError
+from traitlets.config import Config
+
 
 from ..google import GoogleOAuthenticator
 
@@ -72,37 +75,55 @@ async def test_admin_google_groups(google_client):
     authenticator = GoogleOAuthenticator(
         hosted_domain=['email.com', 'mycollege.edu'],
         admin_google_groups={'email.com': ['fakeadmingroup']},
-        google_group_whitelist={'email.com': ['fakegroup']}
+        allowed_google_groups={'email.com': ['fakegroup']}
     )
     handler = google_client.handler_for_user(user_model('fakeadmin@email.com'))
     admin_user_info = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakeadmingroup'])
     admin_user = admin_user_info['admin']
     assert admin_user == True
-    handler = google_client.handler_for_user(user_model('fakewhitelisted@email.com'))
-    whitelist_user_info = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakegroup'])
-    whitelisted_user_groups = whitelist_user_info['auth_state']['google_user']['google_groups']
-    admin_user = whitelist_user_info['admin']
-    assert 'fakegroup' in whitelisted_user_groups
+    handler = google_client.handler_for_user(user_model('fakealloweduser@email.com'))
+    allowed_user_info = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakegroup'])
+    allowed_user_groups = allowed_user_info['auth_state']['google_user']['google_groups']
+    admin_user = allowed_user_info['admin']
+    assert 'fakegroup' in allowed_user_groups
     assert admin_user == False
-    handler = google_client.handler_for_user(user_model('fakenonwhitelisted@email.com'))
-    whitelisted_user_groups = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakenonwhitelistedgroup'])
-    assert whitelisted_user_groups is None
+    handler = google_client.handler_for_user(user_model('fakenonalloweduser@email.com'))
+    allowed_user_groups = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakenonallowedgroup'])
+    assert allowed_user_groups is None
 
 
-async def test_whitelisted_google_groups(google_client):
+async def test_allowed_google_groups(google_client):
     authenticator = GoogleOAuthenticator(
         hosted_domain=['email.com', 'mycollege.edu'],
-        google_group_whitelist={'email.com': ['fakegroup']}
+        allowed_google_groups={'email.com': ['fakegroup']}
     )
     handler = google_client.handler_for_user(user_model('fakeadmin@email.com'))
     admin_user_info = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakeadmingroup'])
     assert admin_user_info is None
-    handler = google_client.handler_for_user(user_model('fakewhitelisted@email.com'))
-    whitelist_user_info = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakegroup'])
-    whitelisted_user_groups = whitelist_user_info['auth_state']['google_user']['google_groups']
-    admin_field = whitelist_user_info.get('admin')
-    assert 'fakegroup' in whitelisted_user_groups
+    handler = google_client.handler_for_user(user_model('fakealloweduser@email.com'))
+    allowed_user_info = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakegroup'])
+    allowed_user_groups = allowed_user_info['auth_state']['google_user']['google_groups']
+    admin_field = allowed_user_info.get('admin')
+    assert 'fakegroup' in allowed_user_groups
     assert admin_field is None
-    handler = google_client.handler_for_user(user_model('fakenonwhitelisted@email.com'))
-    whitelisted_user_groups = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakenonwhitelistedgroup'])
-    assert whitelisted_user_groups is None
+    handler = google_client.handler_for_user(user_model('fakenonalloweduser@email.com'))
+    allowed_user_groups = await authenticator.authenticate(handler, google_groups=['anotherone', 'fakenonallowedgroup'])
+    assert allowed_user_groups is None
+
+
+def test_deprecated_config(caplog):
+    cfg = Config()
+    cfg.GoogleOAuthenticator.google_group_whitelist = {'email.com': ['group']}
+
+    log = logging.getLogger("testlog")
+    authenticator = GoogleOAuthenticator(config=cfg, log=log)
+    assert caplog.record_tuples == [
+        (
+            log.name,
+            logging.WARNING,
+            'GoogleOAuthenticator.google_group_whitelist is deprecated in GoogleOAuthenticator 0.12.0, use '
+            'GoogleOAuthenticator.allowed_google_groups instead',
+        )
+    ]
+
+    assert authenticator.allowed_google_groups == {'email.com': ['group']}