Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename DevGCDatalabAuthenticator to GCDataLabAuthenticatorNoRedirect #4

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions dlauthenticator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from dlauthenticator.dlauthenticator import BaseDataLabAuthenticator, DataLabAuthenticator, GCDataLabAuthenticator, DevGCDataLabAuthenticator
from dlauthenticator.dlauthenticator import BaseDataLabAuthenticator, DataLabAuthenticator
from dlauthenticator.dlauthenticator import GCDataLabAuthenticator, GCDataLabAuthenticatorNoRedirect

__all__ = [BaseDataLabAuthenticator, DataLabAuthenticator, GCDataLabAuthenticator, DevGCDataLabAuthenticator]
__all__ = [BaseDataLabAuthenticator, DataLabAuthenticator,
GCDataLabAuthenticator, GCDataLabAuthenticatorNoRedirect]
19 changes: 10 additions & 9 deletions dlauthenticator/dlauthenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def post_authenticate(self, handler, data, token):
class DataLabAuthenticator(BaseDataLabAuthenticator):
"""
Data Lab Jupyter token authenticator.
Notice this class doesn't perform a log in proper, that happens on the datalab login
Notice this class doesn't perform a log in proper, that happens on the Data Lab login
form, which sets a cookie with the login token in the browser, is that token the
one that is used in the is class to authenticate the user.
"""
Expand Down Expand Up @@ -282,9 +282,9 @@ def post_authenticate(self, handler, data, token):
@gen.coroutine
def pre_spawn_start(self, user, spawner):
# get_auth_state is a coroutine (async) wait for it with yield
# make sure the below is set in the config file
# c.Authenticator.enable_auth_state = True
# otherwise the below command will return None.
# make sure the attribute enable_auth_state = True
# is set in the class constructor, otherwise
# the below command will return None.
auth_state = yield spawner.user.get_auth_state()

# Dev note:
Expand Down Expand Up @@ -314,20 +314,21 @@ def pre_spawn_start(self, user, spawner):
}


class DevGCDataLabAuthenticator(GCDataLabAuthenticator):
class GCDataLabAuthenticatorNoRedirect(GCDataLabAuthenticator):
"""
Google Cloud development authenticator class.
This class doesn't use cookies but uses the DataLab authClient login interface instead.
This class doesn't use cookies but uses the Data Lab authClient login interface instead.
The cookies "next url" works only for jupyterhub clusters that have a domain name that matches
datalab.noirlab.edu, however development environments often has just the ip address.
By setting the c.JupyterHub.authenticator_class to DevGCDataLabAuthenticator the log in happens
By setting the c.JupyterHub.authenticator_class to GCDataLabAuthenticatorNoRedirect the log in happens
via the jupyterhub default login form.
E.g.
c.JupyterHub.authenticator_class = DevGCDataLabAuthenticator
c.JupyterHub.authenticator_class = GCDataLabAuthenticatorNoRedirect
"""

def __init__(self, parent=None, db=None, _deprecated_db_session=None):
BaseDataLabAuthenticator.__init__(self, parent=parent, db=db, _deprecated_db_session=_deprecated_db_session)
super().__init__(parent, db, _deprecated_db_session)
self.auto_login = False

def authenticate(self, handler, data):
"""
Expand Down
2 changes: 1 addition & 1 deletion dlauthenticator/tests/test_bypass_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def mock_request_handler(headers):
[(dlauthenticator.BaseDataLabAuthenticator, None),
(dlauthenticator.DataLabAuthenticator, mock_handler),
(dlauthenticator.GCDataLabAuthenticator, mock_handler),
(dlauthenticator.DevGCDataLabAuthenticator, mock_handler)
(dlauthenticator.GCDataLabAuthenticatorNoRedirect, mock_handler)
])
def test_bypass_file(auth_class, handler):
""" Test that the debug login bypass allows a valid login
Expand Down
2 changes: 1 addition & 1 deletion dlauthenticator/tests/test_invalid_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Test the only two classes where password login makes sense

@pytest.mark.parametrize("auth_class", [dlauthenticator.BaseDataLabAuthenticator,
dlauthenticator.DevGCDataLabAuthenticator])
dlauthenticator.GCDataLabAuthenticatorNoRedirect])
def test_invalid_password(auth_class):
""" Test that an invalid password fails to login the user.

Expand Down
2 changes: 1 addition & 1 deletion dlauthenticator/tests/test_invalid_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


@pytest.mark.parametrize("auth_class", [dlauthenticator.BaseDataLabAuthenticator,
dlauthenticator.DevGCDataLabAuthenticator])
dlauthenticator.GCDataLabAuthenticatorNoRedirect])
def test_invalid_user(auth_class):
""" Test that an invalid username fails to login.

Expand Down
68 changes: 68 additions & 0 deletions dlauthenticator/tests/test_pre_spawn_start.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import pytest
from unittest.mock import MagicMock, patch
from tornado.concurrent import Future
from .. import dlauthenticator

user_name = "testuser"
user_uid = "666"
user_guid = "666"
user_hash = "fake-hash"
user_token = f"{user_name}:{user_uid}:{user_guid}:{user_hash}"
user_selected_profile = "big-9-gb-ram-notebook"


@pytest.fixture
def mock_spawner():
mock_user = MagicMock()
mock_user.name = user_name

# Create a Future for the auth_state to be awaited
# In Tornado, when a coroutine is yielding a value, it
# expects that value to be a Future, or another coroutine
# So the method mock_user.get_auth_state should return a
# "Future", which eventually resolves to the dictionary
# { 'token': ..., 'uid': ..., 'guid': ... }
auth_state_future = Future()
auth_state_future.set_result({
'token': user_token, 'uid': user_uid, 'guid': user_guid
})
mock_user.get_auth_state = MagicMock(return_value=auth_state_future)

spawner = MagicMock()
spawner.user = mock_user
spawner.user_options = {'profile': user_selected_profile}

return spawner


# Run the pytest on these two classes
authenticator_classes = [
(dlauthenticator.GCDataLabAuthenticator, {'enable_auth_state': True,
'auto_login': True}),
(dlauthenticator.GCDataLabAuthenticatorNoRedirect, {'enable_auth_state': True,
'auto_login': False}),
]


@pytest.mark.asyncio
@pytest.mark.parametrize("authenticator_class, expected_attrs", authenticator_classes)
async def test_pre_spawn_start(authenticator_class, expected_attrs, mock_spawner):
authenticator = authenticator_class()

assert hasattr(authenticator, 'enable_auth_state')
assert authenticator.enable_auth_state == expected_attrs['enable_auth_state']
assert hasattr(authenticator, 'auto_login')
assert authenticator.auto_login == expected_attrs['auto_login']


with patch.object(authenticator.log, 'info') as mock_log_info:
await authenticator.pre_spawn_start(mock_spawner.user, mock_spawner)

# Check that the log was called with the correct arguments
mock_log_info.assert_called_once_with(
f"user=[{mock_spawner.user.name}] NB=[{mock_spawner.user_options.get('profile')}]")

# Check the spawner environment is set correctly
assert mock_spawner.environment['UPSTREAM_TOKEN'] == user_token
assert mock_spawner.environment['NB_USER'] == user_name
assert mock_spawner.environment['NB_UID'] == user_uid
2 changes: 1 addition & 1 deletion dlauthenticator/tests/test_valid_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def test_gc_valid_login(self, mock_isValidToken):
self.basic_asserts_for_gc_auth(res, username, uid, gid, token)

def test_dev_gc_valid_login(self):
dlauth = dlauthenticator.DevGCDataLabAuthenticator()
dlauth = dlauthenticator.GCDataLabAuthenticatorNoRedirect()

res = dlauth.authenticate(None,
dict(username=username, password=password)).result()
Expand Down