mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Tests and docs for /-/create-token, refs #1852
This commit is contained in:
parent
68ccb7578b
commit
7ab091e8ef
3 changed files with 93 additions and 4 deletions
|
|
@ -170,9 +170,16 @@ class CreateTokenView(BaseView):
|
||||||
name = "create_token"
|
name = "create_token"
|
||||||
has_json_alternate = False
|
has_json_alternate = False
|
||||||
|
|
||||||
async def get(self, request):
|
def check_permission(self, request):
|
||||||
if not request.actor:
|
if not request.actor:
|
||||||
raise Forbidden("You must be logged in to create a token")
|
raise Forbidden("You must be logged in to create a token")
|
||||||
|
if not request.actor.get("id"):
|
||||||
|
raise Forbidden(
|
||||||
|
"You must be logged in as an actor with an ID to create a token"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get(self, request):
|
||||||
|
self.check_permission(request)
|
||||||
return await self.render(
|
return await self.render(
|
||||||
["create_token.html"],
|
["create_token.html"],
|
||||||
request,
|
request,
|
||||||
|
|
@ -180,8 +187,7 @@ class CreateTokenView(BaseView):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
if not request.actor:
|
self.check_permission(request)
|
||||||
raise Forbidden("You must be logged in to create a token")
|
|
||||||
post = await request.post_vars()
|
post = await request.post_vars()
|
||||||
expires = None
|
expires = None
|
||||||
errors = []
|
errors = []
|
||||||
|
|
@ -203,7 +209,7 @@ class CreateTokenView(BaseView):
|
||||||
token = None
|
token = None
|
||||||
if not errors:
|
if not errors:
|
||||||
token_bits = {
|
token_bits = {
|
||||||
"a": request.actor,
|
"a": request.actor["id"],
|
||||||
"e": (int(time.time()) + expires) if expires else None,
|
"e": (int(time.time()) + expires) if expires else None,
|
||||||
}
|
}
|
||||||
token = "dstok_{}".format(self.ds.sign(token_bits, "token"))
|
token = "dstok_{}".format(self.ds.sign(token_bits, "token"))
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,21 @@ To limit this ability for just one specific database, use this:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. _CreateTokenView:
|
||||||
|
|
||||||
|
API Tokens
|
||||||
|
==========
|
||||||
|
|
||||||
|
Datasette includes a default mechanism for generating API tokens that can be used to authenticate requests.
|
||||||
|
|
||||||
|
Authenticated users can create new API tokens using a form on the ``/-/create-token`` page.
|
||||||
|
|
||||||
|
Created tokens can then be passed in the ``Authorization: Bearer token_here`` header of HTTP requests to Datasette.
|
||||||
|
|
||||||
|
A token created by a user will include that user's ``"id"`` in the token payload, so any permissions granted to that user based on their ID will be made available to the token as well.
|
||||||
|
|
||||||
|
Coming soon: a mechanism for creating tokens that can only perform a subset of the actions available to the user who created them.
|
||||||
|
|
||||||
.. _permissions_plugins:
|
.. _permissions_plugins:
|
||||||
|
|
||||||
Checking permissions in plugins
|
Checking permissions in plugins
|
||||||
|
|
|
||||||
|
|
@ -110,3 +110,71 @@ def test_no_logout_button_in_navigation_if_no_ds_actor_cookie(app_client, path):
|
||||||
response = app_client.get(path + "?_bot=1")
|
response = app_client.get(path + "?_bot=1")
|
||||||
assert "<strong>bot</strong>" in response.text
|
assert "<strong>bot</strong>" in response.text
|
||||||
assert '<form action="/-/logout" method="post">' not in response.text
|
assert '<form action="/-/logout" method="post">' not in response.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"post_data,errors,expected_duration",
|
||||||
|
(
|
||||||
|
({"expire_type": ""}, [], None),
|
||||||
|
({"expire_type": "x"}, ["Invalid expire duration"], None),
|
||||||
|
({"expire_type": "minutes"}, ["Invalid expire duration"], None),
|
||||||
|
(
|
||||||
|
{"expire_type": "minutes", "expire_duration": "x"},
|
||||||
|
["Invalid expire duration"],
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"expire_type": "minutes", "expire_duration": "-1"},
|
||||||
|
["Invalid expire duration"],
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"expire_type": "minutes", "expire_duration": "0"},
|
||||||
|
["Invalid expire duration"],
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"expire_type": "minutes", "expire_duration": "10"},
|
||||||
|
[],
|
||||||
|
600,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"expire_type": "hours", "expire_duration": "10"},
|
||||||
|
[],
|
||||||
|
10 * 60 * 60,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{"expire_type": "days", "expire_duration": "3"},
|
||||||
|
[],
|
||||||
|
60 * 60 * 24 * 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_auth_create_token(app_client, post_data, errors, expected_duration):
|
||||||
|
assert app_client.get("/-/create-token").status == 403
|
||||||
|
ds_actor = app_client.actor_cookie({"id": "test"})
|
||||||
|
response = app_client.get("/-/create-token", cookies={"ds_actor": ds_actor})
|
||||||
|
assert response.status == 200
|
||||||
|
assert ">Create an API token<" in response.text
|
||||||
|
# Now try actually creating one
|
||||||
|
response2 = app_client.post(
|
||||||
|
"/-/create-token",
|
||||||
|
post_data,
|
||||||
|
csrftoken_from=True,
|
||||||
|
cookies={"ds_actor": ds_actor},
|
||||||
|
)
|
||||||
|
assert response2.status == 200
|
||||||
|
if errors:
|
||||||
|
for error in errors:
|
||||||
|
assert '<p class="message-error">{}</p>'.format(error) in response2.text
|
||||||
|
else:
|
||||||
|
# Extract token from page
|
||||||
|
token = response2.text.split('value="dstok_')[1].split('"')[0]
|
||||||
|
details = app_client.ds.unsign(token, "token")
|
||||||
|
assert details.keys() == {"a", "e"}
|
||||||
|
assert details["a"] == "test"
|
||||||
|
if expected_duration is None:
|
||||||
|
assert details["e"] is None
|
||||||
|
else:
|
||||||
|
about_right = int(time.time()) + expected_duration
|
||||||
|
assert about_right - 2 < details["e"] < about_right + 2
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue