mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-17 15:03:02 +00:00
add a notion of admin users
and an @admin_only decorator for restricted methods
This commit is contained in:
@@ -158,6 +158,12 @@ class JupyterHubApp(Application):
|
|||||||
debug_db = Bool(False)
|
debug_db = Bool(False)
|
||||||
db = Any()
|
db = Any()
|
||||||
|
|
||||||
|
admin_users = Set(config=True,
|
||||||
|
help="""list of usernames of admin users
|
||||||
|
|
||||||
|
If unspecified, all users are admin.
|
||||||
|
"""
|
||||||
|
)
|
||||||
tornado_settings = Dict(config=True)
|
tornado_settings = Dict(config=True)
|
||||||
|
|
||||||
handlers = List()
|
handlers = List()
|
||||||
@@ -300,6 +306,7 @@ class JupyterHubApp(Application):
|
|||||||
log=self.log,
|
log=self.log,
|
||||||
db=self.db,
|
db=self.db,
|
||||||
hub=self.hub,
|
hub=self.hub,
|
||||||
|
admin_users=self.admin_users,
|
||||||
authenticator=import_item(self.authenticator)(config=self.config),
|
authenticator=import_item(self.authenticator)(config=self.config),
|
||||||
spawner_class=import_item(self.spawner_class),
|
spawner_class=import_item(self.spawner_class),
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
|
@@ -56,6 +56,10 @@ class BaseHandler(RequestHandler):
|
|||||||
# Login and cookie-related
|
# Login and cookie-related
|
||||||
#---------------------------------------------------------------
|
#---------------------------------------------------------------
|
||||||
|
|
||||||
|
@property
|
||||||
|
def admin_users(self):
|
||||||
|
return self.settings.setdefault('admin_users', set())
|
||||||
|
|
||||||
def get_current_user_token(self):
|
def get_current_user_token(self):
|
||||||
"""get_current_user from Authorization header token"""
|
"""get_current_user from Authorization header token"""
|
||||||
auth_header = self.request.headers.get('Authorization', '')
|
auth_header = self.request.headers.get('Authorization', '')
|
||||||
@@ -90,10 +94,10 @@ class BaseHandler(RequestHandler):
|
|||||||
|
|
||||||
def user_from_username(self, username):
|
def user_from_username(self, username):
|
||||||
"""Get ORM User for username"""
|
"""Get ORM User for username"""
|
||||||
|
|
||||||
user = self.db.query(orm.User).filter(orm.User.name==username).first()
|
user = self.db.query(orm.User).filter(orm.User.name==username).first()
|
||||||
if user is None:
|
if user is None:
|
||||||
user = orm.User(name=username)
|
admin = (not self.admin_users) or username in self.admin_users
|
||||||
|
user = orm.User(name=username, admin=admin)
|
||||||
self.db.add(user)
|
self.db.add(user)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
return user
|
return user
|
||||||
|
@@ -8,7 +8,7 @@ import uuid
|
|||||||
|
|
||||||
from sqlalchemy.types import TypeDecorator, VARCHAR
|
from sqlalchemy.types import TypeDecorator, VARCHAR
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Column, Integer, String, ForeignKey, Unicode, Binary,
|
Column, Integer, String, ForeignKey, Unicode, Binary, Boolean,
|
||||||
)
|
)
|
||||||
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
||||||
from sqlalchemy.orm import sessionmaker, relationship, backref
|
from sqlalchemy.orm import sessionmaker, relationship, backref
|
||||||
@@ -157,6 +157,7 @@ class User(Base):
|
|||||||
# should we allow multiple servers per user?
|
# should we allow multiple servers per user?
|
||||||
_server_id = Column(Integer, ForeignKey('servers.id'))
|
_server_id = Column(Integer, ForeignKey('servers.id'))
|
||||||
server = relationship(Server, primaryjoin=_server_id == Server.id)
|
server = relationship(Server, primaryjoin=_server_id == Server.id)
|
||||||
|
admin = Column(Boolean, default=False)
|
||||||
|
|
||||||
api_tokens = relationship("APIToken", backref="user")
|
api_tokens = relationship("APIToken", backref="user")
|
||||||
cookie_tokens = relationship("CookieToken", backref="user")
|
cookie_tokens = relationship("CookieToken", backref="user")
|
||||||
|
@@ -29,24 +29,42 @@ def wait_for_server(ip, port, timeout=10):
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def auth_decorator(check_auth):
|
||||||
|
"""Make an authentication decorator
|
||||||
|
|
||||||
def token_authenticated(method):
|
I heard you like decorators, so I put a decorator
|
||||||
"""decorator for a method authenticated only by the Authorization token header"""
|
in your decorator, so you can decorate while you decorate.
|
||||||
def check_token(self, *args, **kwargs):
|
"""
|
||||||
|
def decorator(method):
|
||||||
|
def decorated(self, *args, **kwargs):
|
||||||
|
check_auth(self)
|
||||||
|
return method(self, *args)
|
||||||
|
decorated.__name__ = method.__name__
|
||||||
|
decorated.__doc__ = method.__doc__
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
decorator.__name__ = check_auth.__name__
|
||||||
|
decorator.__doc__ = check_auth.__doc__
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@auth_decorator
|
||||||
|
def token_authenticated(self):
|
||||||
|
"""decorator for a method authenticated only by the Authorization token header
|
||||||
|
|
||||||
|
(no cookies)
|
||||||
|
"""
|
||||||
if self.get_current_user_token() is None:
|
if self.get_current_user_token() is None:
|
||||||
raise web.HTTPError(403)
|
raise web.HTTPError(403)
|
||||||
return method(self, *args, **kwargs)
|
|
||||||
check_token.__name__ = method.__name__
|
|
||||||
check_token.__doc__ = method.__doc__
|
|
||||||
return check_token
|
|
||||||
|
|
||||||
|
@auth_decorator
|
||||||
def authenticated_403(method):
|
def authenticated_403(self):
|
||||||
"""decorator like web.authenticated, but raise 403 instead of redirect to login"""
|
"""like web.authenticated, but raise 403 instead of redirect to login"""
|
||||||
def check_user(self, *args, **kwargs):
|
|
||||||
if self.get_current_user() is None:
|
if self.get_current_user() is None:
|
||||||
raise web.HTTPError(403)
|
raise web.HTTPError(403)
|
||||||
return method(self, *args, **kwargs)
|
|
||||||
check_user.__name__ = method.__name__
|
@auth_decorator
|
||||||
check_user.__doc__ = method.__doc__
|
def admin_only(self):
|
||||||
return check_user
|
"""decorator for restricting access to admin users"""
|
||||||
|
user = self.get_current_user()
|
||||||
|
if user is None or not user.admin:
|
||||||
|
raise web.HTTPError(403)
|
||||||
|
Reference in New Issue
Block a user