-
Notifications
You must be signed in to change notification settings - Fork 9
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
Figure out mutation/resolver input or arguments authorization #24
Comments
Let me leave some thoughts over that. I'm implementing a solution exposing the same concepts as Let's take a look at the schema and consider several use-casses. input MessageInput {
content: String!
author_id: ID!
}
type Message {
id: ID!
content: String
author: String
}
type Query {
getMessage(id: ID!): Message
}
type Mutation {
createMessage(input: MessageInput): Message
updateMessage(id: ID!, input: MessageInput): Message
}
class QueryType < Types::BaseObject
field :get_message, Types::Message, null: true, :authorize_field do
argument :id, ID, required: true
end
end
class QueryPolicy < Federation::ApplicationPolicy
def get_message?
permitted_ids.include?(args.id)
end
private
def permitted_ids
# ...
end
end I've already implemented that approach in for Apollo Server where policies have the same arguments as resolvers and can interact with We are still able to handle complex input objects as a whole, and it's a valid way to go for complex checks where it's necessary to consider multiple arguments, but it could be messy to handle multiple input type fields independently in one place. There is potentially another way to make those auth checks more granular. 2.1. Granular check for regular arguments. class QueryType < Types::BaseObject
field :get_message, Types::Message, null: true, :authorize_field do
argument :id, ID, required: true, authorize: true # Magic here
end
end
class QueryPolicy < Federation::ApplicationPolicy
def get_message?
current_user.active?
end
end
class GetMessageArgumentsPolicy < Federation::ApplicationPolicy
def id?
permitted_ids.include?(value)
end
end Here, we are able to decompose general access rule to the query (e.g. only active users can do that) with the argument policy where we can check the value of the argument. 2.2 This could be extended to input objects. E.g. for
Both of them are composable together, e.g. for class QueryType < Types::BaseObject
field :updateMessage, Types::Message, null: true, :authorize_field do
argument :id, ID, required: true, authorize: true # Magic here
argument :id, Types::MessageInput, required: true
end
end
class Types::MessageInput < Types::BaseInputObject
description "Attributes for creating or updating a blog post"
argument :content, String, required: true
argument :author_id, ID, required: true, authorize: true # Magic here
end |
@sponomarev Thanks! I like the granularity idea. The only downside that it could lead to policies bloat. On the other hand, the Are there other cases, which couldn't be reduced to As for input object, wouldn't it be enough to provide something similar to scoping (like strong parameters)? My gut feeling is that writing policies for internal, code-specific, entities (such as GraphQL fields) is not right, we should operate on domain objects. That is, "am I allowed to see this message?" and "am I allowed to update these attributes for the message?" instead of "am I allowed to use this ID to get a message?" and "am I allowed to use this ID as an authorId field for this input?" respectively. |
A few words on passing arguments to policy checks. Although it adds a lot of flexibility, it may lead to a separation of concerns violation (or boundaries crossing, name yourself): you can make your policy know about the context it is used within. Action Policy polices are meant to be context-independent, general-purpose ACLs (unlike in Apollo library, which is GraphQL-specific, and that's why passing the arguments totally makes sense there). |
I'll admit that I haven't thought too deeply into this, but an idea that comes to mind is perhaps a completely separate project similar to strong_params tailored to graphql. Then we can simply use action_policy as-is to conditionally choose between which fields should be accepted/rejected. Here's an example of something I've previously done in a Rails project using REST and strong_params:
Maybe a similar approach can be used to authorize the input fields passed to a resolver, letting us explicitly require/disallow fields but do this dependent on user permissions as defined by |
I have found the following approach works good in our project: reuse Policy params_filter do
attributes(
:password,
:password_confirmation,
:locale
)
if user.new_record?
attributes(
:email,
:profile_type
)
end
association(:profile)
association(:person)
association(:phone_number)
end
GraphQL resolver looks the following way: def resolve(user:)
authorize!(current_user, to: :manage?)
user.attributes = authorized(
user,
with: UserPolicy,
scope_options: {record: user}
) With help of additional scope_matcher :action_controller_params, Types::Base::InputObject If you would like to work with uploads through the ActionController::Parameters::PERMITTED_SCALAR_TYPES << ApolloUploadServer::Wrappers::UploadedFile More detailed version is here: |
Related to palkan/action_policy#89 and #22.
Let's think about how we can use policies to authorize mutations inputs (and, probably, resolvers in general). What are the possible use-cases? I can recall a few:
authorize_field:
here?), for example, available only to admins/managers/etc.The goal of this ticket is to discuss how a general approach could look like, collect examples (and convert them into test cases), design the API.
The text was updated successfully, but these errors were encountered: