mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-16 06:22:59 +00:00
Add lengths to all Unicode() ones
- Otherwise does not work with MySQL - Change JSONDict to be TEXT (Unbounded) rather than VARCHAR. This makes most sense, since you can't index these anyway. - The 'ip' field in Server is set to 255, since that is the max allowed length of DNS entries. - Most of the rest of the Unicodes have approximately high values that most people should not mostly run into (famous last words).
This commit is contained in:
@@ -12,7 +12,7 @@ from tornado import gen
|
|||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
from tornado.httpclient import HTTPRequest, AsyncHTTPClient
|
from tornado.httpclient import HTTPRequest, AsyncHTTPClient
|
||||||
|
|
||||||
from sqlalchemy.types import TypeDecorator, VARCHAR
|
from sqlalchemy.types import TypeDecorator, TEXT
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
inspect,
|
inspect,
|
||||||
Column, Integer, ForeignKey, Unicode, Boolean,
|
Column, Integer, ForeignKey, Unicode, Boolean,
|
||||||
@@ -39,7 +39,7 @@ class JSONDict(TypeDecorator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
impl = VARCHAR
|
impl = TEXT
|
||||||
|
|
||||||
def process_bind_param(self, value, dialect):
|
def process_bind_param(self, value, dialect):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
@@ -59,20 +59,20 @@ Base.log = app_log
|
|||||||
|
|
||||||
class Server(Base):
|
class Server(Base):
|
||||||
"""The basic state of a server
|
"""The basic state of a server
|
||||||
|
|
||||||
connection and cookie info
|
connection and cookie info
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'servers'
|
__tablename__ = 'servers'
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
proto = Column(Unicode, default='http')
|
proto = Column(Unicode(15), default='http')
|
||||||
ip = Column(Unicode, default='')
|
ip = Column(Unicode(255), default='') # could also be a DNS name
|
||||||
port = Column(Integer, default=random_port)
|
port = Column(Integer, default=random_port)
|
||||||
base_url = Column(Unicode, default='/')
|
base_url = Column(Unicode(255), default='/')
|
||||||
cookie_name = Column(Unicode, default='cookie')
|
cookie_name = Column(Unicode(255), default='cookie')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Server(%s:%s)>" % (self.ip, self.port)
|
return "<Server(%s:%s)>" % (self.ip, self.port)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self):
|
||||||
ip = self.ip
|
ip = self.ip
|
||||||
@@ -91,18 +91,18 @@ class Server(Base):
|
|||||||
host=self.host,
|
host=self.host,
|
||||||
uri=self.base_url,
|
uri=self.base_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bind_url(self):
|
def bind_url(self):
|
||||||
"""representation of URL used for binding
|
"""representation of URL used for binding
|
||||||
|
|
||||||
Never used in APIs, only logging,
|
Never used in APIs, only logging,
|
||||||
since it can be non-connectable value, such as '', meaning all interfaces.
|
since it can be non-connectable value, such as '', meaning all interfaces.
|
||||||
"""
|
"""
|
||||||
if self.ip in {'', '0.0.0.0'}:
|
if self.ip in {'', '0.0.0.0'}:
|
||||||
return self.url.replace('127.0.0.1', self.ip or '*', 1)
|
return self.url.replace('127.0.0.1', self.ip or '*', 1)
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def wait_up(self, timeout=10, http=False):
|
def wait_up(self, timeout=10, http=False):
|
||||||
"""Wait for this server to come up"""
|
"""Wait for this server to come up"""
|
||||||
@@ -110,7 +110,7 @@ class Server(Base):
|
|||||||
yield wait_for_http_server(self.url, timeout=timeout)
|
yield wait_for_http_server(self.url, timeout=timeout)
|
||||||
else:
|
else:
|
||||||
yield wait_for_server(self.ip or '127.0.0.1', self.port, timeout=timeout)
|
yield wait_for_server(self.ip or '127.0.0.1', self.port, timeout=timeout)
|
||||||
|
|
||||||
def is_up(self):
|
def is_up(self):
|
||||||
"""Is the server accepting connections?"""
|
"""Is the server accepting connections?"""
|
||||||
try:
|
try:
|
||||||
@@ -136,7 +136,7 @@ class Server(Base):
|
|||||||
|
|
||||||
class Proxy(Base):
|
class Proxy(Base):
|
||||||
"""A configurable-http-proxy instance.
|
"""A configurable-http-proxy instance.
|
||||||
|
|
||||||
A proxy consists of the API server info and the public-facing server info,
|
A proxy consists of the API server info and the public-facing server info,
|
||||||
plus an auth token for configuring the proxy table.
|
plus an auth token for configuring the proxy table.
|
||||||
"""
|
"""
|
||||||
@@ -147,7 +147,7 @@ class Proxy(Base):
|
|||||||
public_server = relationship(Server, primaryjoin=_public_server_id == Server.id)
|
public_server = relationship(Server, primaryjoin=_public_server_id == Server.id)
|
||||||
_api_server_id = Column(Integer, ForeignKey('servers.id'))
|
_api_server_id = Column(Integer, ForeignKey('servers.id'))
|
||||||
api_server = relationship(Server, primaryjoin=_api_server_id == Server.id)
|
api_server = relationship(Server, primaryjoin=_api_server_id == Server.id)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.public_server:
|
if self.public_server:
|
||||||
return "<%s %s:%s>" % (
|
return "<%s %s:%s>" % (
|
||||||
@@ -155,7 +155,7 @@ class Proxy(Base):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return "<%s [unconfigured]>" % self.__class__.__name__
|
return "<%s [unconfigured]>" % self.__class__.__name__
|
||||||
|
|
||||||
def api_request(self, path, method='GET', body=None, client=None):
|
def api_request(self, path, method='GET', body=None, client=None):
|
||||||
"""Make an authenticated API request of the proxy"""
|
"""Make an authenticated API request of the proxy"""
|
||||||
client = client or AsyncHTTPClient()
|
client = client or AsyncHTTPClient()
|
||||||
@@ -178,10 +178,11 @@ class Proxy(Base):
|
|||||||
self.log.info("Adding user %s to proxy %s => %s",
|
self.log.info("Adding user %s to proxy %s => %s",
|
||||||
user.name, user.proxy_path, user.server.host,
|
user.name, user.proxy_path, user.server.host,
|
||||||
)
|
)
|
||||||
|
|
||||||
if user.spawn_pending:
|
if user.spawn_pending:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"User %s's spawn is pending, shouldn't be added to the proxy yet!", user.name)
|
"User %s's spawn is pending, shouldn't be added to the proxy yet!", user.name)
|
||||||
|
|
||||||
yield self.api_request(user.proxy_path,
|
yield self.api_request(user.proxy_path,
|
||||||
method='POST',
|
method='POST',
|
||||||
body=dict(
|
body=dict(
|
||||||
@@ -190,7 +191,7 @@ class Proxy(Base):
|
|||||||
),
|
),
|
||||||
client=client,
|
client=client,
|
||||||
)
|
)
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def delete_user(self, user, client=None):
|
def delete_user(self, user, client=None):
|
||||||
"""Remove a user's server to the proxy table."""
|
"""Remove a user's server to the proxy table."""
|
||||||
@@ -199,7 +200,7 @@ class Proxy(Base):
|
|||||||
method='DELETE',
|
method='DELETE',
|
||||||
client=client,
|
client=client,
|
||||||
)
|
)
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def get_routes(self, client=None):
|
def get_routes(self, client=None):
|
||||||
"""Fetch the proxy's routes"""
|
"""Fetch the proxy's routes"""
|
||||||
@@ -209,7 +210,7 @@ class Proxy(Base):
|
|||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def add_all_users(self, user_dict):
|
def add_all_users(self, user_dict):
|
||||||
"""Update the proxy table from the database.
|
"""Update the proxy table from the database.
|
||||||
|
|
||||||
Used when loading up a new proxy.
|
Used when loading up a new proxy.
|
||||||
"""
|
"""
|
||||||
db = inspect(self).session
|
db = inspect(self).session
|
||||||
@@ -251,9 +252,9 @@ class Proxy(Base):
|
|||||||
|
|
||||||
class Hub(Base):
|
class Hub(Base):
|
||||||
"""Bring it all together at the hub.
|
"""Bring it all together at the hub.
|
||||||
|
|
||||||
The Hub is a server, plus its API path suffix
|
The Hub is a server, plus its API path suffix
|
||||||
|
|
||||||
the api_url is the full URL plus the api_path suffix on the end
|
the api_url is the full URL plus the api_path suffix on the end
|
||||||
of the server base_url.
|
of the server base_url.
|
||||||
"""
|
"""
|
||||||
@@ -262,12 +263,12 @@ class Hub(Base):
|
|||||||
_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)
|
||||||
host = ''
|
host = ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_url(self):
|
def api_url(self):
|
||||||
"""return the full API url (with proto://host...)"""
|
"""return the full API url (with proto://host...)"""
|
||||||
return url_path_join(self.server.url, 'api')
|
return url_path_join(self.server.url, 'api')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.server:
|
if self.server:
|
||||||
return "<%s %s:%s>" % (
|
return "<%s %s:%s>" % (
|
||||||
@@ -279,31 +280,31 @@ class Hub(Base):
|
|||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
"""The User table
|
"""The User table
|
||||||
|
|
||||||
Each user has a single server,
|
Each user has a single server,
|
||||||
and multiple tokens used for authorization.
|
and multiple tokens used for authorization.
|
||||||
|
|
||||||
API tokens grant access to the Hub's REST API.
|
API tokens grant access to the Hub's REST API.
|
||||||
These are used by single-user servers to authenticate requests,
|
These are used by single-user servers to authenticate requests,
|
||||||
and external services to manipulate the Hub.
|
and external services to manipulate the Hub.
|
||||||
|
|
||||||
Cookies are set with a single ID.
|
Cookies are set with a single ID.
|
||||||
Resetting the Cookie ID invalidates all cookies, forcing user to login again.
|
Resetting the Cookie ID invalidates all cookies, forcing user to login again.
|
||||||
|
|
||||||
A `state` column contains a JSON dict,
|
A `state` column contains a JSON dict,
|
||||||
used for restoring state of a Spawner.
|
used for restoring state of a Spawner.
|
||||||
"""
|
"""
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
name = Column(Unicode)
|
name = Column(Unicode(1023))
|
||||||
# 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)
|
admin = Column(Boolean, default=False)
|
||||||
last_activity = Column(DateTime, default=datetime.utcnow)
|
last_activity = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
api_tokens = relationship("APIToken", backref="user")
|
api_tokens = relationship("APIToken", backref="user")
|
||||||
cookie_id = Column(Unicode, default=new_token)
|
cookie_id = Column(Unicode(1023), default=new_token)
|
||||||
state = Column(JSONDict)
|
state = Column(JSONDict)
|
||||||
|
|
||||||
other_user_cookies = set([])
|
other_user_cookies = set([])
|
||||||
@@ -321,7 +322,7 @@ class User(Base):
|
|||||||
cls=self.__class__.__name__,
|
cls=self.__class__.__name__,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def new_api_token(self):
|
def new_api_token(self):
|
||||||
"""Create a new API token"""
|
"""Create a new API token"""
|
||||||
assert self.id is not None
|
assert self.id is not None
|
||||||
@@ -344,29 +345,29 @@ class User(Base):
|
|||||||
class APIToken(Base):
|
class APIToken(Base):
|
||||||
"""An API token"""
|
"""An API token"""
|
||||||
__tablename__ = 'api_tokens'
|
__tablename__ = 'api_tokens'
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def user_id(cls):
|
def user_id(cls):
|
||||||
return Column(Integer, ForeignKey('users.id'))
|
return Column(Integer, ForeignKey('users.id'))
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
hashed = Column(Unicode)
|
hashed = Column(Unicode(1023))
|
||||||
prefix = Column(Unicode)
|
prefix = Column(Unicode(1023))
|
||||||
prefix_length = 4
|
prefix_length = 4
|
||||||
algorithm = "sha512"
|
algorithm = "sha512"
|
||||||
rounds = 16384
|
rounds = 16384
|
||||||
salt_bytes = 8
|
salt_bytes = 8
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def token(self):
|
def token(self):
|
||||||
raise AttributeError("token is write-only")
|
raise AttributeError("token is write-only")
|
||||||
|
|
||||||
@token.setter
|
@token.setter
|
||||||
def token(self, token):
|
def token(self, token):
|
||||||
"""Store the hashed value and prefix for a token"""
|
"""Store the hashed value and prefix for a token"""
|
||||||
self.prefix = token[:self.prefix_length]
|
self.prefix = token[:self.prefix_length]
|
||||||
self.hashed = hash_token(token, rounds=self.rounds, salt=self.salt_bytes, algorithm=self.algorithm)
|
self.hashed = hash_token(token, rounds=self.rounds, salt=self.salt_bytes, algorithm=self.algorithm)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<{cls}('{pre}...', user='{u}')>".format(
|
return "<{cls}('{pre}...', user='{u}')>".format(
|
||||||
cls=self.__class__.__name__,
|
cls=self.__class__.__name__,
|
||||||
@@ -387,7 +388,7 @@ class APIToken(Base):
|
|||||||
for orm_token in prefix_match:
|
for orm_token in prefix_match:
|
||||||
if orm_token.match(token):
|
if orm_token.match(token):
|
||||||
return orm_token
|
return orm_token
|
||||||
|
|
||||||
def match(self, token):
|
def match(self, token):
|
||||||
"""Is this my token?"""
|
"""Is this my token?"""
|
||||||
return compare_token(self.hashed, token)
|
return compare_token(self.hashed, token)
|
||||||
|
Reference in New Issue
Block a user