-
Notifications
You must be signed in to change notification settings - Fork 1.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
[WIP] Proposal: add inaccessible_field feature #1769
[WIP] Proposal: add inaccessible_field feature #1769
Conversation
Right now accessibility is done at query analysis time. The only provided hook is `inaccessible_fields` where you can either allow the query to continue or raise an error. If you raise an error, query execution does not occur. This prevents any partial responses from happening. If `nil` is returned and the query continues, all information from `GraphQL::Authorization::Analyzer` is lost. The idea behind this is to provide a 2nd option where the authorization enforcement is done during field execution (but before actually resolving) to allow for partial responses. Ideally this would be opt-in at the schema level by providing a `inaccessible_field` (note the difference, no `s` at the end, name TBD) method which would be called for *each* field which is not accessible. An `ExecutionError` could be raised to prevent the field was being resolved. Things to figure out: 1. how to opt-in to this behaviour. The optional schema method doesn't actually work due to the legacy schema and delegation.2 2. this only works for class-based fields. Does this need to be supported for the legacy ones as well? Is there even a way to do this nicely? 3. is checking the `irep_node` a stable enough way to do this?
|
||
# TODO: fix | ||
if context.schema.respond_to?(:inaccessible_field) | ||
context[:inaccessible_nodes] = memo[:inaccessible_nodes] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be added to the context regardless. The only conditional thing should be skipping inaccessible_fields
.
Thanks for the careful explanation and code! It sounds like you want runtime authorization at the field level. How about implementing class BaseField < GraphQL::Schema::Field
def authorized?(parent_object, context)
# check parent_object & self for the given context,
# return true|false, or raise an error
end
end It's just barely mentioned in the the docs but it's checked before resolving. Do you think that might work? |
🤔 I think I sufficiently confused you in our original chat which didn't make this obvious. I'll take a look at that and report back. |
One benefit of using the analyzer is much more efficient access checks. If we used In fact we already have to do this and manage it with our own cached access checks. But it would be nice to avoid this and have the gem ensure it's efficient which it is when done during the analysis phase. |
Hmm, I wonder though, because the other downside of ahead-of-time checks is that it's going to check the whole query, even if the first field returns I know that analysis is a moving target, because of performance issues with Besides that, my experience with analysis at GitHub doesn't leave me enamored with it. For example, we have an analyzer that checks if the given oauth token has all scopes required for the query. But, it can't do the whole job because sometimes the required scope depends on the object (some repositories require Since my own experience with analysis is not that good, I'm not excited about supporting a hybrid ahead-of-time/runtime feature. Instead, I would encourage people to use either visibility or runtime authorization, since those two are usually good enough. Regarding duplicate work, would it be sufficient to cache the result based on def authorized?(_parent_object, context)
# The context here is Query::Context, so I think it's safe to close over in this proc:
context[:return_type_checks] ||= Hash.new { |h, k| h[k] = k.allowed_return_type?(context) }
# Get the cached value, or run the block for `owner`:
context[:return_type_checks][owner]
end Is that ^^ similar to the existing caching you have? |
Yeah that's a good point that I've realized as well. Our caching is more independent from GraphQL. Instead of the cache key being a field/type, it's the required access (such as def authorized?(_parent_object, context)
context[:access].include?(@access)
end
Considering the unknown future around |
Right now accessibility is done at query analysis time. The only provided hook is
inaccessible_fields
where you can either allow the query to continue or raise an error.If you raise an error, query execution does not occur. This prevents any partial responses from happening.
If
nil
is returned and the query continues, all information fromGraphQL::Authorization::Analyzer
is lost.The idea behind this is to provide a 2nd option where the authorization enforcement is done during field execution (but before actually resolving) to allow for partial responses.
Ideally this would be opt-in at the schema level by providing a
inaccessible_field
(note the difference, nos
at the end, name TBD) method which would be called for each field which is not accessible. AnExecutionError
could be raised to prevent the field was being resolved.Things to figure out:
irep_node
a stable enough way to do this?