-
-
Notifications
You must be signed in to change notification settings - Fork 697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Authentication (and permissions) as a core concept #699
Comments
Another problem this would solve: if you want multiple authentication mechanisms - GitHub auth for users, I dealt with this a bit in simonw/datasette-auth-github#59 But having an authentication plugin hook - where playing get to decide if a user should be authenticated based on the incoming ASGI scopes - would be neater. |
So maybe this is all handled by plugin hooks?
How would a permissions hook work though? |
|
I implemented bearer tokens in a private project of mine as a one-off plugin. I'm going to extract that out into a installable plugin soon. For the moment, my from datasette import hookimpl
import secrets
class TokenAuth:
def __init__(
self, app, secret, auth,
):
self.app = app
self.secret = secret
self.auth = auth
async def __call__(self, scope, receive, send):
if scope.get("type") != "http":
return await self.app(scope, receive, send)
authorization = dict(scope.get("headers") or {}).get(b"authorization") or b""
expected = "Bearer {}".format(self.secret).encode("utf8")
if secrets.compare_digest(authorization, expected):
scope = dict(scope, auth=self.auth)
return await self.app(scope, receive, send)
@hookimpl(trylast=True)
def asgi_wrapper(datasette):
config = datasette.plugin_config("token-auth") or {}
secret = config.get("secret")
auth = config.get("auth")
def wrap_with_asgi_auth(app):
return TokenAuth(app, secret=secret, auth=auth,)
return wrap_with_asgi_auth Then I have the following in {
"plugins": {
"token-auth": {
"auth": {
"name": "token-bot"
},
"secret": {
"$env": "TOKEN_SECRET"
}
}
}
} And a |
I did have a bit of trouble with this one-off plugin getting it to load in the correct order - since I need authentication to work if EITHER the one-off plugin spots a token or my That's why I want authentication as a core Datasette concept - so plugins like these can easily play together in a predictable manner. |
Very nice! Thank you for sharing that 👍 :) Will try it out! |
I think there are two hooks here:
A non-None value means the request is authenticated in some way. The shape of that dictionary is entirely undefined. The second hook is for checking permissions. It can look something like this:
I don't know if Datasette should provide default implementations of these hooks. It may be that leaving them completely up to plugins is the way to go. I think I need to prototype this quickly to start feeling for how well it might work. |
|
Maybe call that |
I'm changing |
My usage of the term |
In AWS IAM world the following terminology is used: https://aws.amazon.com/iam/features/manage-permissions/
|
I like "actor" better than "entity" to mean "the user or API key that is authenticated for this request". I'm going to use "resource" instead of "subject" - updating the design comment again. |
I could bake some permission checks into default Datasette, which are all treated as allow by default but can then be locked down by plugins. Maybe the following:
Checks that current user can execute arbitrary SQL queries against a specific database (or use the
Can the user download the database file? Like allow_download. Maybe one for allow_csv_stream too. Having a permission check (defaulting to True) on every single "view" would be useful:
|
I started sketching this out in the authentication branch. Here's the documentation so far: https://github.com/simonw/datasette/blob/8871c20/docs/plugins.rst#actor_from_requestdatasette-request |
Debugging permissions is going to be important. Optional tooling that supports the following would be useful:
That last one is tricky if permissions are just strings that might be passed to |
Also added datasette argument to permission_allowed hook
I'm going to add an awaitable utility method to the Datasette class for checking permissions:
The second two arguments will be optional. |
The branch is now usable! Next step: write some experimental plugins that exercise some real authentication use-cases with it. |
OK, the implementation in PR #783 is in a good state now - it implements the new plugin hooks with tests and documentation, plus it implements this:
That URL, when clicked, will set a cookie for the I'm going to merge that pull request and continue working on this stuff on master. |
I should add an entire page to the documentation describing Datasette authentication. |
Also added datasette argument to permission_allowed hook
Also added JSON highlighting to introspection documentation.
I rebased in #783 so all of this is on master now. |
Some next steps:
|
https://latest.datasette.io/-/actor is now live (it returns |
Plugin idea: |
Debugging tool idea: Bonus: if you're logged in as the |
I can close this issue once I've expanded out this page of documentation https://datasette.readthedocs.io/en/latest/authentication.html - and published at least one plugin and/or feature that takes advantage of this new mechanism. |
The canned queries feature is gaining permissions support in #800. |
I landed canned query writes. This feature can now be considered complete: https://datasette.readthedocs.io/en/latest/authentication.html |
Right now Datasette authentication is provided exclusively by plugins:
This is an all-or-nothing approach: either your Datasette instance requires authentication at the top level or it does not.
But... as I build new plugins like https://github.com/simonw/datasette-configure-fts and https://github.com/simonw/datasette-edit-tables I increasingly have individual features which should be reserved for logged-in users while still wanting other parts of Datasette to be open to all.
This is too much for plugins to own independently of Datasette core. Datasette needs to ship a single "user is authenticated" concept (independent of how users actually sign in) so that different plugins can integrate with it.
The text was updated successfully, but these errors were encountered: