-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
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
API: Public endpoint permission handling (public access rules) #5614
Comments
refs TryGhost#4004, TryGhost#5614 - added new public permission handling functions to permissions - added a new util to handle either public permissions or normal permissions - updated posts, tags and users endpoints to use the new util - added test coverage for the new code
…hen migrating closes TryGhost#5298 - remove all harcoded instances of jQuery throughout the front-end of the blog - add migration function to add cdn link to ghost_foot code injection when migrating up from version 003 - migration version bump
refs TryGhost#5614 and TryGhost#5503 - update private blog type, including update to settings.edit - switch order of populate settings & update fixtures + populate all settings Private blog settings should not be returned by public endpoints therefore they need a type which is not `blog` or `theme`. `core` doesn't suit either, as those settings don't usually have UI To resolve this, I created a new type `private` which can be used for any setting which has a UI but should not be public data
refs TryGhost#5614 and TryGhost#5503 - update private blog type, including update to settings.edit - switch order of populate settings & update fixtures + populate all settings Private blog settings should not be returned by public endpoints therefore they need a type which is not `blog` or `theme`. `core` doesn't suit either, as those settings don't usually have UI To resolve this, I created a new type `private` which can be used for any setting which has a UI but should not be public data
refs TryGhost#5614 and TryGhost#5503 - update private blog type, including update to settings.edit - switch order of populate settings & update fixtures + populate all settings Private blog settings should not be returned by public endpoints therefore they need a type which is not `blog` or `theme`. `core` doesn't suit either, as those settings don't usually have UI To resolve this, I created a new type `private` which can be used for any setting which has a UI but should not be public data
This issue is a catch-all for the work of checking and fixing any issues with permissions now that public api access is possible. At the moment, two of the checks listed in the original issue are definitely failing:
I'm working on a PR to fix the latter of these two first, as it requires a new approach to permissions, and I believe once I've cracked that then handling counts will become easier, and it will be worthwhile adding tests for the other checks. To ensure the PR makes some sense when it lands, I wanted to outline this new approach in detail. TLDR;With complex API queries which may perform subqueries, joins or complex filters we need to apply access rules for any resource that is requested as part of a query. E.g. if we fetch tags, and order by post count, we need to make sure the post count requested correctly applies the access rules. By using the filtering syntax we already have access to, we can combine access rules, defaults and custom filters into a set of
Making sure it's easy to get hold of the access rules & defaults for each resource, combine them, and apply them, will provide a way for us to ensure that even very complex queries result in the right data being fetched. Checking access rulesCurrently, the majority of our permissions handling takes the form of a 'check':
In terms of get requests, that is checking if a user or client is allowed to fetch the data it is asking for based on whether the request is public or private (authenticated) and if it is public, using the public access rules. The problem we have with checks, is making them granular enough to cover the complexity of API browse requests. It's easy to say 'can this user fetch any posts?' it's hard to check 'does this query do anything that would cause drafts to be returned because that's not allowed'. In the older version of the API it wasn't too hard to see if a request for posts will result in drafts being returned because we only have to check the Applying access rules to a query.To ensure that we always return data which conforms to the rules, the current implementation has the concept of 'default' clauses which get added to the query. So for posts, we add a Having a simple, easy way to always 'apply' the rules to any query, will result in us being able to do all kinds of complex joins and filters and always be able to ensure the data returned is permitted. However, the current version can be easily tricked by using an 'OR' E.g: Resulting SQL: Any combination of filter query which includes 'or status is draft' will override the first clause. To solve this, we need to use groups in SQL to enforce that the first set of clauses must always be true. E.g. Additionally important here, is that as well as the 'public access rules' we also have, for posts, a concept of a default which can be overridden in the form of Adding all this together and we have 3 different types of filter that we might want to apply: Types of filter statementWe have the following filter statement types we need to combine:
Enforced filters must be applied to the query no matter what. This is achieved by doing:
Default filters should be applied to the query, unless they are overridden by the custom filter expression. Overridden filter statements are removed until the default filter expression is reduced down to only those which are not set by the custom filter expression. Default filters and Enforced filters are all simple single clauses, without groups, combined with 'and', which allows us to do:
We don't have to put defaults in the first group, but this helps when debugging or reading back queries, the first group is from the 'system', and the second is user-provided. Posts example:enforced filters: public request ? status:published : none; public request One consideration is whether or not outputting 'no posts' for a public API request that tries to get draft posts is correct or whether this should throw an error. Either should be possible, throwing an error requires a bit of extra processing. Combining filters to applyThe key idea in all of this is boiling down the rules for our queries into a set of easily applicable 'filter' rules: enforced, default, and any custom filters. Adding the ability to easily fetch & combine these filters for the current request no matter what resource we're requesting and then apply them to a query for a resource means we can always apply our rules to nested queries, joins, filters etc just as easily as we can to a simple request. This is what I'm aiming for 😁 |
refs TryGhost#5614 - change isPublicContext to detectPublicContext - behaviour now expands the context object out - this is a bit of a sideeffect, but this is the simplest change that makes it possible to use the context in the model layer without significant wider changes - add new access rules plugin - takes a context object as part of `forge()` & caches it on the model instance - provides helper functions for testing access rules later on
refs TryGhost#5614, TryGhost#5943 - adds a new 'filter' bookshelf plugin which extends the model - the filter plugin provides handling for merging/combining various filters (enforced, defaults and custom/user-provided) - the filter plugin also handles the calls to gql - post processing is also moved to the plugin, to be further refactored/removed in future - adds tests showing how filter could be abused prior to this commit
refs TryGhost#6009, TryGhost#5614 - Use the new isPublicContext method to detect whether to add extra clauses to the count - Add count to users
I'm closing most API issues temporarily with the JSON API Overhaul & OAuth access are currently scheduled next on the roadmap |
Ghost's API has a number of endpoints which should be available for what we refer to as 'public' access. Essentially, any data which can be accessed legitimately from the frontend of a blog, ought also to be accessible through other clients. As described in #4004, public access will still require a client ID, so that this access can be revoked.
The frontend controller code which handles outputting data via a theme is a client of the API, so is the
{{#get}}
helper (#4439), and also javascript code in the theme which may make requests via AJAX. Our plan is to have default clients which automatically get access, just like frontend controller/theme code.Similarly to the frontend controller, the
{{#get}}
helper uses the internal method-call version of the API, so it doesn't need a client id. Each blog will get a default client id which is made available to any JavaScript in a theme (issue #4184) so that it can make ajax calls, and eventually it will be possible to register custom clients as well.The endpoints that will be public in the first iteration, will be the browse & read endpoints of the post, tags and user resources. Several of the authentication flow endpoints are already public as they happen before auth is available. Every other endpoint should remain behind user auth for the time being. Settings and config will need consideration for public access shortly after, and the other endpoints will become available with the introduction of full oAuth 2.0 flows for clients.
The Rules
Several of the endpoints which are public have very strict rules around what information that can be accessed from that endpoint when the request is public.
published
posts may be accessed, unless a draft is specifically requested via the UUID (which cannot be looked up via the public API).active
users may be accessed, email addresses and passwords must not be returnedTasks / Checklist
The following tasks are required to complete this work:
filter
parameter is used (API: Filter parameter (GQL filter queries spec) #5604)A note on the settings endpoint: when made publicly accessible, settings will have the rule that only settings with type
blog
andtheme
can be accessed publicly. The private blogisPrivate
andpassword
settings are currently incorrectly classified asblog
settings and would need to be changed before the settings endpoint can be made public.The text was updated successfully, but these errors were encountered: