mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
create-token command can now create restricted tokens, refs #1855
This commit is contained in:
parent
c6a811237c
commit
9cc1a7c4c8
1 changed files with 93 additions and 3 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
from datasette import hookimpl, Permission
|
from datasette import hookimpl, Permission
|
||||||
from datasette.utils import actor_matches_allow
|
from datasette.utils import actor_matches_allow
|
||||||
|
import asyncio
|
||||||
import click
|
import click
|
||||||
import itsdangerous
|
import itsdangerous
|
||||||
import json
|
import json
|
||||||
|
|
@ -278,17 +279,106 @@ def register_commands(cli):
|
||||||
help="Token should expire after this many seconds",
|
help="Token should expire after this many seconds",
|
||||||
type=int,
|
type=int,
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"alls",
|
||||||
|
"-a",
|
||||||
|
"--all",
|
||||||
|
type=str,
|
||||||
|
metavar="ACTION",
|
||||||
|
multiple=True,
|
||||||
|
help="Restrict token to this action",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"databases",
|
||||||
|
"-d",
|
||||||
|
"--database",
|
||||||
|
type=(str, str),
|
||||||
|
metavar="DB ACTION",
|
||||||
|
multiple=True,
|
||||||
|
help="Restrict token to this action on this database",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"resources",
|
||||||
|
"-r",
|
||||||
|
"--resource",
|
||||||
|
type=(str, str, str),
|
||||||
|
metavar="DB RESOURCE ACTION",
|
||||||
|
multiple=True,
|
||||||
|
help="Restrict token to this action on this database resource (a table, SQL view or named query)",
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--debug",
|
"--debug",
|
||||||
help="Show decoded token",
|
help="Show decoded token",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
)
|
)
|
||||||
def create_token(id, secret, expires_after, debug):
|
@click.option(
|
||||||
"Create a signed API token for the specified actor ID"
|
"--plugins-dir",
|
||||||
ds = Datasette(secret=secret)
|
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||||
|
help="Path to directory containing custom plugins",
|
||||||
|
)
|
||||||
|
def create_token(
|
||||||
|
id, secret, expires_after, alls, databases, resources, debug, plugins_dir
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a signed API token for the specified actor ID
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
datasette create-token root --secret mysecret
|
||||||
|
|
||||||
|
To only allow create-table:
|
||||||
|
|
||||||
|
\b
|
||||||
|
datasette create-token root --secret mysecret \\
|
||||||
|
--all create-table
|
||||||
|
|
||||||
|
Or to only allow insert-row against a specific table:
|
||||||
|
|
||||||
|
\b
|
||||||
|
datasette create-token root --secret myscret \\
|
||||||
|
--resource mydb mytable insert-row
|
||||||
|
|
||||||
|
Restricted actions can be specified multiple times using
|
||||||
|
multiple --all, --database, and --resource options.
|
||||||
|
|
||||||
|
Add --debug to see a decoded version of the token.
|
||||||
|
"""
|
||||||
|
ds = Datasette(secret=secret, plugins_dir=plugins_dir)
|
||||||
|
|
||||||
|
# Run ds.invoke_startup() in an event loop
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(ds.invoke_startup())
|
||||||
|
|
||||||
|
def fix_action(action):
|
||||||
|
# Warn if invalid, rename to abbr if possible
|
||||||
|
permission = ds.permissions.get(action)
|
||||||
|
if not permission:
|
||||||
|
# Output red message
|
||||||
|
click.secho(
|
||||||
|
f" Unknown permission: {action} ",
|
||||||
|
fg="red",
|
||||||
|
err=True,
|
||||||
|
)
|
||||||
|
return action
|
||||||
|
return permission.abbr or action
|
||||||
|
|
||||||
bits = {"a": id, "token": "dstok", "t": int(time.time())}
|
bits = {"a": id, "token": "dstok", "t": int(time.time())}
|
||||||
if expires_after:
|
if expires_after:
|
||||||
bits["d"] = expires_after
|
bits["d"] = expires_after
|
||||||
|
if alls or databases or resources:
|
||||||
|
bits["_r"] = {}
|
||||||
|
if alls:
|
||||||
|
bits["_r"]["a"] = [fix_action(a) for a in alls]
|
||||||
|
if databases:
|
||||||
|
bits["_r"]["d"] = {}
|
||||||
|
for database, action in databases:
|
||||||
|
bits["_r"]["d"].setdefault(database, []).append(fix_action(action))
|
||||||
|
if resources:
|
||||||
|
bits["_r"]["r"] = {}
|
||||||
|
for database, table, action in resources:
|
||||||
|
bits["_r"]["r"].setdefault(database, {}).setdefault(
|
||||||
|
table, []
|
||||||
|
).append(fix_action(action))
|
||||||
token = ds.sign(bits, namespace="token")
|
token = ds.sign(bits, namespace="token")
|
||||||
click.echo("dstok_{}".format(token))
|
click.echo("dstok_{}".format(token))
|
||||||
if debug:
|
if debug:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue