Initial commit
Pretty much all the things work
This commit is contained in:
commit
b1e5804d65
7 changed files with 199 additions and 0 deletions
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal file
|
@ -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/
|
20
.pylintrc
Normal file
20
.pylintrc
Normal file
|
@ -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
|
27
LICENSE
Normal file
27
LICENSE
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2015, Yuvi Panda <yuvipanda@gmail.com>
|
||||
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.
|
29
README.md
Normal file
29
README.md
Normal file
|
@ -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.
|
12
firstuseauthenticator/__init__.py
Normal file
12
firstuseauthenticator/__init__.py
Normal file
|
@ -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]
|
41
firstuseauthenticator/firstuseauthenticator.py
Normal file
41
firstuseauthenticator/firstuseauthenticator.py
Normal file
|
@ -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']
|
13
setup.py
Normal file
13
setup.py
Normal file
|
@ -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']
|
||||
)
|
Loading…
Add table
Reference in a new issue