Repository: jupyterhub/jupyterhub

Issue:
PAM/AD auth doesn't create homedir on first login
So from what i can see in auth.py, the 'login' pam service is used to authenticate the user. For some reason I don't think the 'session' part of this service is honoured. I've tested this by using pamtester with the login service (see below) and the home directory is created.

pamtester login <username> open_session close_session

Any idea why this may be? I think it has something to do with the simplepam python module functionality but i'm not certain.

any help is appreciated. Cheers



Discussion:
I'm not sure what would be different, but I will try to investigate.

Hey, chers for quick reply. Done a bit of digging and it seems like the simple pam module doesn't implement any PAM arguemnts (open/close session as stated above). I have however found a modified module here - https://github.com/gnosek/python-pam. If you take a look at pam.py, you'll notice the implementation of a handle for passing raw PAM arguments, such as open session/close session.

If these were implemented, then home directory creation of the 'login' service would be honoured.

However, I don't think this is an issue with jupyterhub as such. It seems to me like the simplepam module in python doesn't allow this functionality...

Just to give you a bit of background on my use of jupyter. I've got a container based jupyter instance that i've implemented in CentOS. Jupyter relies on the home directory for storage of some critical files so if the home directory isn't there then the server wont start after login. I've considered mounting the home directory of the host into the container, however this doesn't really solve the problem. If the use has never logged in to this machine before then they have no home directory and we have the same problem.

Any help/advice is much appreciated. I think since containers are becoming more popular now, maybe there is a need for the session information to be used? maybe I'm wrong though.


Resolving patch:
diff --git a/jupyterhub/auth.py b/jupyterhub/auth.py
--- a/jupyterhub/auth.py
+++ b/jupyterhub/auth.py
@@ -8,7 +8,7 @@
 from subprocess import check_call, check_output, CalledProcessError
 
 from tornado import gen
-import simplepam
+import pamela
 
 from traitlets.config import LoggingConfigurable
 from traitlets import Bool, Set, Unicode, Any
@@ -208,10 +208,11 @@ def authenticate(self, handler, data):
         username = data['username']
         if not self.check_whitelist(username):
             return
-        # simplepam wants bytes, not unicode
-        # see simplepam#3
-        busername = username.encode(self.encoding)
-        bpassword = data['password'].encode(self.encoding)
-        if simplepam.authenticate(busername, bpassword, service=self.service):
+        try:
+            pamela.authenticate(username, data['password'], service=self.service)
+            pamela.open_session(username, service=self.service)
+        except pamela.PAMError as e:
+            self.log.warn("PAM Authentication failed: %s", e)
+        else:
             return username
     


Test patch:
diff --git a/jupyterhub/tests/mocking.py b/jupyterhub/tests/mocking.py
--- a/jupyterhub/tests/mocking.py
+++ b/jupyterhub/tests/mocking.py
@@ -18,16 +18,18 @@
 from ..auth import PAMAuthenticator
 from .. import orm
 
+from pamela import PAMError
+
 def mock_authenticate(username, password, service='login'):
-    # mimic simplepam's failure to handle unicode
-    if isinstance(username, str):
-        return False
-    if isinstance(password, str):
-        return False
-    
     # just use equality for testing
     if password == username:
         return True
+    else:
+        raise PAMError("Fake")
+
+
+def mock_open_session(username, service):
+    pass
 
 
 class MockSpawner(LocalProcessSpawner):
@@ -80,7 +82,9 @@ def system_user_exists(self, user):
         return not user.name.startswith('dne')
     
     def authenticate(self, *args, **kwargs):
-        with mock.patch('simplepam.authenticate', mock_authenticate):
+        with mock.patch.multiple('pamela',
+                authenticate=mock_authenticate,
+                open_session=mock_open_session):
             return super(MockPAMAuthenticator, self).authenticate(*args, **kwargs)
 
 class MockHub(JupyterHub):