Skip to content

Commit

Permalink
Merge pull request #29 from jupyterhub/admin-authorization
Browse files Browse the repository at this point in the history
Add admin authorization system to new users
  • Loading branch information
leportella authored Jan 15, 2019
2 parents 4f5287a + 5c0c4f9 commit 0084a09
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 13 deletions.
32 changes: 26 additions & 6 deletions nativeauthenticator/handlers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import os
from jinja2 import ChoiceLoader, FileSystemLoader
from jupyterhub.handlers import BaseHandler
from jupyterhub.utils import admin_only

from .orm import UserInfo

TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates')


class SignUpHandler(BaseHandler):
"""Render the sign in page."""

class LocalBase(BaseHandler):
def __init__(self, *args, **kwargs):
self._loaded = False
super().__init__(*args, **kwargs)
Expand All @@ -23,6 +23,9 @@ def _register_template_path(self):
env.loader = ChoiceLoader([previous_loader, loader])
self._loaded = True


class SignUpHandler(LocalBase):
"""Render the sign in page."""
async def get(self):
self._register_template_path()
html = self.render_template('signup.html')
Expand All @@ -33,8 +36,25 @@ async def post(self):
password = self.get_body_argument('password', strip=False)
user = self.authenticator.get_or_create_user(username, password)
html = self.render_template(
'signup.html',
result=bool(user),
result_message='Your information have been sent to the admin',
'signup.html',
result=bool(user),
result_message='Your information have been sent to the admin',
)
self.finish(html)


class AuthorizationHandler(LocalBase):
"""Render the sign in page."""
@admin_only
async def get(self):
self._register_template_path()
html = self.render_template('autorization-area.html',
users=self.db.query(UserInfo).all())
self.finish(html)


class ChangeAuthorizationHandler(LocalBase):
@admin_only
async def get(self, slug):
UserInfo.change_authorization(self.db, slug)
self.redirect('/authorize')
17 changes: 13 additions & 4 deletions nativeauthenticator/nativeauthenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from sqlalchemy.orm import relationship
from tornado import gen

from .handlers import SignUpHandler
from .handlers import (AuthorizationHandler, ChangeAuthorizationHandler,
SignUpHandler)
from .orm import UserInfo


Expand All @@ -27,7 +28,9 @@ def add_new_table(self):
@gen.coroutine
def authenticate(self, handler, data):
user = UserInfo.find(self.db, data['username'])
if user and user.is_valid_password(data['password']):
if not user:
return
if user.is_authorized and user.is_valid_password(data['password']):
return data['username']

def get_or_create_user(self, username, password):
Expand All @@ -37,12 +40,18 @@ def get_or_create_user(self, username, password):
self.db.add(user)

encoded_pw = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
user_info = UserInfo(user=user, username=username, password=encoded_pw)
infos = {'user': user, 'username': username, 'password': encoded_pw}
if username in self.admin_users:
infos.update({'is_authorized': True})

user_info = UserInfo(**infos)
self.db.add(user_info)
return user

def get_handlers(self, app):
native_handlers = [
(r'/signup', SignUpHandler)
(r'/signup', SignUpHandler),
(r'/authorize', AuthorizationHandler),
(r'/authorize/([^/]*)', ChangeAuthorizationHandler)
]
return super().get_handlers(app) + native_handlers
15 changes: 12 additions & 3 deletions nativeauthenticator/orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from jupyterhub.orm import Base, User

from sqlalchemy import (
Column, ForeignKey, Integer, String
Boolean, Column, ForeignKey, Integer, String
)
from sqlalchemy.orm import relationship

Expand All @@ -12,16 +12,25 @@ class UserInfo(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String, nullable=False)
password = Column(String, nullable=False)
is_authorized = Column(Boolean, default=False)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship(User)

@classmethod
def find(cls, db, username):
"""Find a user info record by name.
Returns None if not found.
"""
Returns None if not found"""
return db.query(cls).filter(cls.username == username).first()

def is_valid_password(self, password):
"""Checks if a password passed matches the
password stored"""
encoded_pw = bcrypt.hashpw(password.encode(), self.password)
return encoded_pw == self.password

@classmethod
def change_authorization(cls, db, username):
user = db.query(cls).filter(cls.username == username).first()
user.is_authorized = not user.is_authorized
db.commit()
return user
11 changes: 11 additions & 0 deletions nativeauthenticator/tests/test_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,20 @@ async def test_failed_authentication_wrong_password(tmpcwd, app):
assert not response


async def test_failed_authentication_not_authorized(tmpcwd, app):
'''Test if authentication fails with a wrong password'''
auth = NativeAuthenticator(db=app.db)
auth.get_or_create_user('John Snow', 'password')
response = await auth.authenticate(app, {'username': 'John Snow',
'password': 'password'})
assert not response


async def test_succeded_authentication(tmpcwd, app):
'''Test a successfull authentication'''
auth = NativeAuthenticator(db=app.db)
user = auth.get_or_create_user('John Snow', 'password')
UserInfo.change_authorization(app.db, 'John Snow')
response = await auth.authenticate(app, {'username': 'John Snow',
'password': 'password'})
assert response == user.name
Expand All @@ -65,3 +75,4 @@ async def test_handlers(app):
auth = NativeAuthenticator(db=app.db)
handlers = auth.get_handlers(app)
assert handlers[1][0] == '/signup'
assert handlers[2][0] == '/authorize'

0 comments on commit 0084a09

Please sign in to comment.