mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-10 19:43:01 +00:00
token expiry fixes
typos in token expiry: - omitted from token model (it's in the spec in docs, but wasn't in the model) - wrong type when sorting oauth tokens on token page could cause token page to not render
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
import datetime
|
|
||||||
|
|
||||||
from http.client import responses
|
from http.client import responses
|
||||||
|
|
||||||
@@ -14,7 +14,17 @@ from .. import orm
|
|||||||
from ..handlers import BaseHandler
|
from ..handlers import BaseHandler
|
||||||
from ..utils import isoformat, url_path_join
|
from ..utils import isoformat, url_path_join
|
||||||
|
|
||||||
|
|
||||||
class APIHandler(BaseHandler):
|
class APIHandler(BaseHandler):
|
||||||
|
"""Base class for API endpoints
|
||||||
|
|
||||||
|
Differences from page handlers:
|
||||||
|
|
||||||
|
- JSON responses and errors
|
||||||
|
- strict referer checking for Cookie-authenticated requests
|
||||||
|
- strict content-security-policy
|
||||||
|
- methods for REST API models
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content_security_policy(self):
|
def content_security_policy(self):
|
||||||
@@ -137,7 +147,7 @@ class APIHandler(BaseHandler):
|
|||||||
'oauth_client': token.client.description or token.client.client_id,
|
'oauth_client': token.client.description or token.client.client_id,
|
||||||
}
|
}
|
||||||
if token.expires_at:
|
if token.expires_at:
|
||||||
expires_at = datetime.datetime.fromtimestamp(token.expires_at)
|
expires_at = datetime.fromtimestamp(token.expires_at)
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"token must be an APIToken or OAuthAccessToken, not %s"
|
"token must be an APIToken or OAuthAccessToken, not %s"
|
||||||
@@ -157,6 +167,7 @@ class APIHandler(BaseHandler):
|
|||||||
'kind': kind,
|
'kind': kind,
|
||||||
'created': isoformat(token.created),
|
'created': isoformat(token.created),
|
||||||
'last_activity': isoformat(token.last_activity),
|
'last_activity': isoformat(token.last_activity),
|
||||||
|
'expires_at': isoformat(expires_at),
|
||||||
}
|
}
|
||||||
model.update(extra)
|
model.update(extra)
|
||||||
return model
|
return model
|
||||||
|
@@ -247,9 +247,11 @@ class TokenPageHandler(BaseHandler):
|
|||||||
api_tokens.append(token)
|
api_tokens.append(token)
|
||||||
|
|
||||||
# group oauth client tokens by client id
|
# group oauth client tokens by client id
|
||||||
|
# AccessTokens have expires_at as an integer timestamp
|
||||||
|
now_timestamp = now.timestamp()
|
||||||
oauth_tokens = defaultdict(list)
|
oauth_tokens = defaultdict(list)
|
||||||
for token in user.oauth_tokens:
|
for token in user.oauth_tokens:
|
||||||
if token.expires_at and token.expires_at < now:
|
if token.expires_at and token.expires_at < now_timestamp:
|
||||||
self.log.warning("Deleting expired token")
|
self.log.warning("Deleting expired token")
|
||||||
self.db.delete(token)
|
self.db.delete(token)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
@@ -1214,14 +1214,19 @@ def test_token_as_user_deprecated(app, as_user, for_user, status):
|
|||||||
|
|
||||||
|
|
||||||
@mark.gen_test
|
@mark.gen_test
|
||||||
@mark.parametrize("headers, status, note", [
|
@mark.parametrize("headers, status, note, expires_in", [
|
||||||
({}, 200, 'test note'),
|
({}, 200, 'test note', None),
|
||||||
({}, 200, ''),
|
({}, 200, '', 100),
|
||||||
({'Authorization': 'token bad'}, 403, ''),
|
({'Authorization': 'token bad'}, 403, '', None),
|
||||||
])
|
])
|
||||||
def test_get_new_token(app, headers, status, note):
|
def test_get_new_token(app, headers, status, note, expires_in):
|
||||||
|
options = {}
|
||||||
if note:
|
if note:
|
||||||
body = json.dumps({'note': note})
|
options['note'] = note
|
||||||
|
if expires_in:
|
||||||
|
options['expires_in'] = expires_in
|
||||||
|
if options:
|
||||||
|
body = json.dumps(options)
|
||||||
else:
|
else:
|
||||||
body = ''
|
body = ''
|
||||||
# request a new token
|
# request a new token
|
||||||
@@ -1239,6 +1244,10 @@ def test_get_new_token(app, headers, status, note):
|
|||||||
assert reply['user'] == 'admin'
|
assert reply['user'] == 'admin'
|
||||||
assert reply['created']
|
assert reply['created']
|
||||||
assert 'last_activity' in reply
|
assert 'last_activity' in reply
|
||||||
|
if expires_in:
|
||||||
|
assert isinstance(reply['expires_at'], str)
|
||||||
|
else:
|
||||||
|
assert reply['expires_at'] is None
|
||||||
if note:
|
if note:
|
||||||
assert reply['note'] == note
|
assert reply['note'] == note
|
||||||
else:
|
else:
|
||||||
|
@@ -598,6 +598,29 @@ def test_announcements(app, announcements):
|
|||||||
assert_announcement("logout", r.text)
|
assert_announcement("logout", r.text)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_token_page(app):
|
||||||
|
name = "cake"
|
||||||
|
cookies = yield app.login_user(name)
|
||||||
|
r = yield get_page("token", app, cookies=cookies)
|
||||||
|
r.raise_for_status()
|
||||||
|
assert urlparse(r.url).path.endswith('/hub/token')
|
||||||
|
assert "Request new API token" in r.text
|
||||||
|
assert "API Tokens" in r.text
|
||||||
|
assert "Server at %s" % app.users[name].url in r.text
|
||||||
|
# no oauth tokens yet, shouldn't have that section
|
||||||
|
assert "Authorized Applications" not in r.text
|
||||||
|
|
||||||
|
# spawn the user to trigger oauth, etc.
|
||||||
|
r = yield get_page("spawn", app, cookies=cookies)
|
||||||
|
r.raise_fo_status()
|
||||||
|
|
||||||
|
r = yield get_page("token", app, cookies=cookies)
|
||||||
|
r.raise_for_status()
|
||||||
|
assert "API Tokens" in r.text
|
||||||
|
assert "Authorized Applications" not in r.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_server_not_running_api_request(app):
|
def test_server_not_running_api_request(app):
|
||||||
cookies = yield app.login_user("bees")
|
cookies = yield app.login_user("bees")
|
||||||
|
Reference in New Issue
Block a user