commit b1e5804d651696a2fd7ede93cede0771af906771 Author: YuviPanda Date: Mon Oct 24 20:31:45 2016 -0700 Initial commit Pretty much all the things work diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba74660 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..855241d --- /dev/null +++ b/.pylintrc @@ -0,0 +1,20 @@ +[MASTER] +# Ignore git and virtualenv-related folders. +ignore=.git,lib + +[MESSAGES CONTROL] +disable=all + +# Explicitly whitelist the lint checks we want to have +# Prefer things that catch errors and what not, and not stylistic choices +enable=missing-docstring,empty-docstring,unneeded-not,singleton-comparison,misplaced-comparison-constant,unidiomatic-typecheck,consider-using-enumerate,consider-iterating-dictionary,bad-classmethod-argument,bad-mcs-method-argument,bad-mcs-classmethod-argument,too-many-lines,multiple-statements,superfluous-parens,multiple-imports,ungrouped-imports,syntax-error,init-is-generator,return-in-init,function-redefined,not-in-loop,return-outside-function,yield-outside-function,nonexistent-operator,duplicate-argument-name,abstract-class-instantiated,bad-reversed-sequence,too-many-star-expressions,invalid-star-assignment-target,star-needs-assignment-target,nonlocal-and-global,continue-in-finally,nonlocal-without-binding,method-hidden,access-member-before-definition,no-method-argument,no-self-argument,invalid-slots-object,assigning-non-slot,invalid-slots,inherit-non-class,inconsistent-mro,duplicate-bases,non-iterator-returned,unexpected-special-method-signature,invalid-length-returned,import-error,used-before-assignment,undefined-variable,undefined-all-variable,unbalanced-tuple-unpacking,unpacking-non-sequence,bad-except-order,raising-bad-type,bad-exception-context,misplaced-bare-raise,raising-non-exception,notimplemented-raised,catching-non-exception,bad-super-call,no-member,not-callable,assignment-from-no-return,no-value-for-parameter,too-many-function-args,unexpected-keyword-arg,redundant-keyword-arg,missing-kwoa,invalid-sequence-index,invalid-slice-index,assignment-from-none,not-context-manager,invalid-unary-operand-type,unsupported-binary-operation,repeated-keyword,not-an-iterable,not-a-mapping,unsupported-membership-test,unsubscriptable-object,logging-unsupported-format,logging-format-truncated,logging-too-many-args,logging-too-few-args,bad-format-character,truncated-format-string,mixed-format-string,format-needs-mapping,missing-format-string-key,too-many-format-args,too-few-format-args,bad-str-strip-call,yield-inside-async-function,not-async-context-manager,fatal,astroid-error,parse-error,method-check-failed,bad-inline-option,useless-suppression,deprecated-pragma,unreachable,dangerous-default-value,pointless-statement,expression-not-assigned,unnecessary-pass,unnecessary-lambda,duplicate-key,useless-else-on-loop,eval-used,exec-used,confusing-with-statement,using-constant-test,lost-exception,assert-on-tuple,bad-staticmethod-argument,protected-access,arguments-differ,signature-differs,abstract-method,super-init-not-called,no-init,non-parent-init-called,unnecessary-semicolon,bad-indentation,mixed-indentation,wildcard-import,deprecated-module,reimported,import-self,misplaced-future,global-variable-undefined,global-variable-not-assigned,global-at-module-level,unused-import,unused-variable,unused-argument,unused-wildcard-import,redefined-outer-name,redefined-builtin,redefine-in-handler,undefined-loop-variable,cell-var-from-loop,duplicate-except,broad-except,bare-except,binary-op-exception,logging-not-lazy,logging-format-interpolation,bad-format-string-key,bad-format-string,missing-format-argument-key,format-combined-specification,missing-format-attribute,invalid-format-index,anomalous-backslash-in-string,anomalous-unicode-escape-in-string,bad-open-mode,redundant-unittest-assert,deprecated-method + +[REPORTS] +# Do not print a summary report +reports=no + +[FORMAT] +# One of the few stylistic things we try to check. +# Modules shouldn't be *too large* - should be broken up +# Feel free to bump this number if you disagree +max-module-lines=2000 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..59eab2c --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Yuvi Panda +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of KubeSpawner nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..25ab382 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# JupyterHub First Use Authenticator # + +A JupyterHub Authenticator that lets users set their password when they first login. + +Very useful for transient JupyterHubs being used from a single physical location (such as a workshop), where multiple users need to log in but do not have a pre-existing authentication setup. With this, they can just pick a username and password and go! + +## Installation ## + +You can install this authenticator with: + +```bash +pip install jupyterhub-firstuseauthenticator +``` + +Once installed, you can have JupyterHub use it by adding the following to your `jupyterhub_config.py` file: + +```python +c.JupyterHub.authenticator_class = 'firstuseauthenticator.FirstUseAuthenticator' +``` + +## Configuration ## + +It works out of the box as advertized. There is one configuration parameter you can tweak. + +### FirstUseAuthenticator.dbm_path ### + +Path to the [dbm](https://docs.python.org/3.1/library/dbm.html) file used to store usernames and passwords. Put this somewhere where regular users do not have read/write access to it. + +Defaults to `passwords.dbm` in the current directory from which JupyterHub is spawned. diff --git a/firstuseauthenticator/__init__.py b/firstuseauthenticator/__init__.py new file mode 100644 index 0000000..2e68da7 --- /dev/null +++ b/firstuseauthenticator/__init__.py @@ -0,0 +1,12 @@ +""" +JupyterHub Authenticator to let users set their password on first use. + +After installation, you can enable this with: + +``` +c.JupyterHub.authenticator_class = 'firstuseauthenticator.FirstUseAuthenticator' +``` +""" +from firstuseauthenticator.firstuseauthenticator import FirstUseAuthenticator + +__all__ = [FirstUseAuthenticator] diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py new file mode 100644 index 0000000..0497980 --- /dev/null +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -0,0 +1,41 @@ +""" +JupyterHub Authenticator that lets users set password on first use. + +When users first log in, the password they use becomes their +password for that account. It is hashed with bcrypt & stored +locally in a dbm file, and checked next time they log in. +""" +import dbm +from jupyterhub.auth import Authenticator + +from tornado import gen +from traitlets.traitlets import Unicode + +import bcrypt + + +class FirstUseAuthenticator(Authenticator): + """ + JupyterHub authenticator that lets users set password on first use. + """ + dbm_path = Unicode( + 'passwords.dbm', + config=True, + help=""" + Path to store the db file with username / pwd hash in + """ + ) + + @gen.coroutine + def authenticate(self, handler, data): + # Move everything to bytes + username = data['username'].encode('utf-8') + password = data['password'].encode('utf-8') + with dbm.open(self.dbm_path, 'c', 0o600) as db: + stored_pw = db.get(username, None) + if stored_pw is not None: + if bcrypt.hashpw(password, stored_pw) != stored_pw: + return None + else: + db[username] = bcrypt.hashpw(password, bcrypt.gensalt()) + return data['username'] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..75baf8f --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_packages + +setup( + name='jupyterhub-firstuseauthenticator', + version='0.9', + description='JupyterHub Authenticator that lets users set passwords on first use', + url='https://github.com/yuvipanda/jupyterhub-firstuseauthenticator', + author='Yuvi Panda', + author_email='yuvipanda@gmail.com', + license='3 Clause BSD', + packages=find_packages(), + install_requires=['bcrypt'] +)