palkan / action_policy-graphql Goto Github PK
View Code? Open in Web Editor NEWAction Policy integration for GraphQL
License: MIT License
Action Policy integration for GraphQL
License: MIT License
Allow to use proc for authorized_scope
. Specifically it might be helpful if you would like to pass some ids via scope_options
.
field :events, EventType.connection_type, null: false, authorized_scope: -> object { {with: CustomEventPolicy, scope_options: {city_id: object.id}} }
Can i change authorize behavior to check policy before fetching data?
I noticed in the latest update that action_policy-graphql is pulling ruby-next in as a dependency rather than just ruby-next-core.
Would it be possible to set this gem up to ship transpiled files in the gem (like action_policy itself) rather than requiring consumers of the gem to install ruby-next?
It works as expected in production (with enabled eager loading). But I'm having difficulties in development when I change any file in app/graphql
it starts to fail with the following error:
error: "NoMethodError"
message: "undefined method `expose_authorization_rules' for Types::ResourceType:Class"
Ruby Version: 2.6.6
Framework Version (Rails, whatever): 5.2
Action Policy Version: 0.5.1
Action Policy GraphQL Version: 0.5.3
Changing code in app/graphql
while running rails server in development mode.
Method expose_authorization_rules
can be used to expose resource authentication rules.
Getting NoMethodError
exception.
e.g.
I would expect preauthorize: { raise: false, with: PostPolicy }
to default to preauthorize: { to: :index?, raise: false, with: PostPolicy }
but it does not, in the README it states this is the expected behavior. So I guess this is a bug?
Hey @palkan hope you're doing well!
This week we just started to implement feature gates in our GraphQL API.
I was wondering if you've done this kind of thing, and if you think ActionPolicy was a good place for this kind of thing.
One thought that I had was if I added feature gates to a policy I could share it across the app vs just in GraphQL.
another question that I had was around the expose_authorization_rules
API, do you have any real world examples as to why you would want to show anything other than a boolean here? I find myself only ever reaching for the .value in the AuthorizationResult.
I was curious if you'd have any realistic use cases for the rest of the information like reasons
that are exposed in the AuthorizationResult.
If not, I'd propose a change to something like:
expose_authorization_rules :edit_profile,
:create_profile,
boolean: true
to return a boolean
instead of an AuthorizationResult.
what do you think?
Hi, can I transfer some additional data to expose_authorization_rules and then use them in the policy?
for example:
expose_authorization_rules :create?, context: { test_data: "I'm test_data" }
then get this information in the policy for use
def create?
self.context[:test_data]
end
Since action_policy
version have been bumped to 0.6.6
I go through the following error: undefined method 'relation_scope'
.
Ruby Version:
3.2.2
Framework Version (Rails, whatever):
Rails 7.0
Action Policy Version:
0.6.6
Action Policy GraphQL Version:
0.5.3
I simply updated action_policy
from 0.6.5
to 0.6.6
Smooth update since it was a minor version bump.
It broke my development environment each time a policy class using relation_scope is called.
Ruby Version:
3.0
Framework Version (Rails, whatever):
Rails 6.1
Action Policy Version:
0.5.5
Action Policy GraphQL Version:
0.5.2
Upgraded graphql gem from 1.12.3
to 1.12.4
Connections would continue working
With this code
field :rewards,
RewardType.connection_type,
'Returns a paginated list of all rewards',
authorized_scope: true,
null: false
I get
ActionPolicy::NotFound:
Couldn't find policy class for [#<Types::QueryType:0x00005577c3abc2b0 @object=nil, @context=#<Query::Context ...>, @__authorization_context={:user=>#<User ancestry: nil, id: 795, email: [FILTERED], created_at: "2021-02-16 17:43:54.930691000 +0000", updated_at: "2021-02-16 17:43:55.234755800 +0000", account_id: 989, first_name: [FILTERED], last_name: [FILTERED], employee_number: [FILTERED], manager_id: nil, hire_date: nil, relocate: nil, short_term_plan: nil, long_term_plan: nil, last_email_sent: nil, status: "active", jti: "c4a742a6-7978-467a-85b3-a2cd0691c111", potential_rating_id: nil, departure_risk_id: nil, succession_time_frame_id: nil, experience_before_next_role: "Retro photo booth hammock waistcoat marfa sriracha...", performance_rating_id: nil, review_frequency: "monthly", enabled: true, role_group_id: nil, uuid: "a7769f4e-8694-44c3-a7bf-38e49cc6180a">}, @__policies_cache__={"//users/795-20210216174355234755/347440"=>nil}, @authorization_namespace=Types>, "Couldn't find implicit authorization target for Types::QueryType. Please, provide policy class explicitly using `with` option or define the `implicit_authorization_target` method."] (Array)
# /bundle/ruby/3.0.0/gems/action_policy-0.5.5/lib/.rbnext/1995.next/action_policy/behaviours/policy_for.rb:42:in `implicit_authorization_target!'
# /bundle/ruby/3.0.0/gems/action_policy-0.5.5/lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb:17:in `authorized_scope'
# /bundle/ruby/3.0.0/gems/action_policy-graphql-0.5.2/lib/action_policy/graphql/authorized_field.rb:120:in `after_resolve'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:769:in `block (2 levels) in with_extensions'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:766:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:766:in `block in with_extensions'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:115:in `after_lazy'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:764:in `with_extensions'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:696:in `public_send_field'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:609:in `block in resolve'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:115:in `after_lazy'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:607:in `resolve'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:272:in `block (4 levels) in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/tracing.rb:66:in `trace'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:271:in `block (3 levels) in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/query.rb:364:in `block in with_error_handling'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/errors.rb:45:in `with_error_handling'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/query.rb:363:in `with_error_handling'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:270:in `block (2 levels) in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:432:in `resolve_with_directives'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:267:in `block in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:516:in `after_lazy'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:214:in `evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:206:in `block in evaluate_selection'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:132:in `block (2 levels) in coerce_arguments'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:111:in `block in coerce_arguments'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:110:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:110:in `coerce_arguments'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/arguments_cache.rb:49:in `dataload_for'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:205:in `evaluate_selection'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:142:in `block (2 levels) in evaluate_selections'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:141:in `block in evaluate_selections'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:140:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:140:in `evaluate_selections'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:62:in `block in run_eager'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:61:in `run_eager'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter.rb:77:in `block in evaluate'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/tracing.rb:66:in `trace'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter.rb:76:in `evaluate'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter.rb:49:in `begin_query'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:85:in `begin_query'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:104:in `block (2 levels) in run_as_multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:104:in `block in run_as_multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:103:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:103:in `each_with_index'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:103:in `run_as_multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:63:in `block (2 levels) in run_queries'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:208:in `block in instrument_and_analyze'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:29:in `block (2 levels) in apply_instrumenters'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:46:in `block (2 levels) in each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:41:in `each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:45:in `block in each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:44:in `each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:27:in `block in apply_instrumenters'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:26:in `apply_instrumenters'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:187:in `instrument_and_analyze'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:62:in `block in run_queries'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/tracing.rb:66:in `trace'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:60:in `run_queries'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:50:in `run_all'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:1690:in `multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:1661:in `execute'
# ./app/controllers/graphql_controller.rb:13:in `execute'
# /bundle/ruby/3.0.0/gems/actiontext-6.1.2.1/lib/action_text/rendering.rb:20:in `with_renderer'
# /bundle/ruby/3.0.0/gems/actiontext-6.1.2.1/lib/action_text/engine.rb:55:in `block (4 levels) in <class:Engine>'
# /bundle/ruby/3.0.0/gems/rack-attack-6.5.0/lib/rack/attack.rb:99:in `call'
# /bundle/ruby/3.0.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth/middleware/token_dispatcher.rb:20:in `call'
# /bundle/ruby/3.0.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth/middleware/revocation_manager.rb:21:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/builder.rb:244:in `call'
# /bundle/ruby/3.0.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth/middleware.rb:23:in `call'
# /bundle/ruby/3.0.0/gems/rack-attack-6.5.0/lib/rack/attack.rb:113:in `call'
# /bundle/ruby/3.0.0/gems/warden-1.2.9/lib/warden/manager.rb:36:in `block in call'
# /bundle/ruby/3.0.0/gems/warden-1.2.9/lib/warden/manager.rb:34:in `catch'
# /bundle/ruby/3.0.0/gems/warden-1.2.9/lib/warden/manager.rb:34:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb:15:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/etag.rb:27:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/conditional_get.rb:40:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/head.rb:12:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:266:in `context'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:260:in `call'
# /bundle/ruby/3.0.0/gems/lograge-0.11.2/lib/lograge/rails_ext/rack/logger.rb:15:in `call_app'
# /bundle/ruby/3.0.0/gems/railties-6.1.2.1/lib/rails/rack/logger.rb:26:in `block in call'
# /bundle/ruby/3.0.0/gems/railties-6.1.2.1/lib/rails/rack/logger.rb:26:in `call'
# /bundle/ruby/3.0.0/gems/request_store-1.5.0/lib/request_store/middleware.rb:19:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/method_override.rb:24:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/runtime.rb:22:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/sendfile.rb:110:in `call'
# /bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors.rb:100:in `call'
# /bundle/ruby/3.0.0/gems/railties-6.1.2.1/lib/rails/engine.rb:539:in `call'
# /bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb:29:in `request'
# /bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb:266:in `process_request'
# /bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb:119:in `request'
# ./spec/requests/graphql/queries/rewards_spec.rb:120:in `block (4 levels) in <top (required)>'
# /bundle/ruby/3.0.0/gems/webmock-3.11.2/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
With
field :rewards,
RewardType.connection_type,
'Returns a paginated list of all rewards',
authorized_scope: { with: RewardPolicy },
null: false
I get
ActionPolicy::UnrecognizedScopeTarget:
Couldn't infer scope type for GraphQL::Pagination::ActiveRecordRelationConnection instance
# /bundle/ruby/3.0.0/gems/action_policy-0.5.5/lib/action_policy/policy/scoping.rb:105:in `resolve_scope_type'
# /bundle/ruby/3.0.0/gems/action_policy-0.5.5/lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb:31:in `authorization_scope_type_for'
# /bundle/ruby/3.0.0/gems/action_policy-0.5.5/lib/.rbnext/1995.next/action_policy/behaviours/scoping.rb:19:in `authorized_scope'
# /bundle/ruby/3.0.0/gems/action_policy-graphql-0.5.2/lib/action_policy/graphql/authorized_field.rb:120:in `after_resolve'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:769:in `block (2 levels) in with_extensions'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:766:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:766:in `block in with_extensions'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:115:in `after_lazy'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:764:in `with_extensions'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:696:in `public_send_field'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:609:in `block in resolve'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:115:in `after_lazy'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/field.rb:607:in `resolve'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:272:in `block (4 levels) in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/tracing.rb:66:in `trace'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:271:in `block (3 levels) in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/query.rb:364:in `block in with_error_handling'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/errors.rb:45:in `with_error_handling'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/query.rb:363:in `with_error_handling'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:270:in `block (2 levels) in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:432:in `resolve_with_directives'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:267:in `block in evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:516:in `after_lazy'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:214:in `evaluate_selection_with_args'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:206:in `block in evaluate_selection'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:132:in `block (2 levels) in coerce_arguments'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:111:in `block in coerce_arguments'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:110:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema/member/has_arguments.rb:110:in `coerce_arguments'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/arguments_cache.rb:49:in `dataload_for'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:205:in `evaluate_selection'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:142:in `block (2 levels) in evaluate_selections'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:141:in `block in evaluate_selections'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:140:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:140:in `evaluate_selections'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:62:in `block in run_eager'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter/runtime.rb:61:in `run_eager'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter.rb:77:in `block in evaluate'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/tracing.rb:66:in `trace'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter.rb:76:in `evaluate'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/interpreter.rb:49:in `begin_query'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:85:in `begin_query'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:104:in `block (2 levels) in run_as_multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/dataloader/null_dataloader.rb:16:in `append_job'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:104:in `block in run_as_multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:103:in `each'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:103:in `each_with_index'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:103:in `run_as_multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:63:in `block (2 levels) in run_queries'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:208:in `block in instrument_and_analyze'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:29:in `block (2 levels) in apply_instrumenters'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:46:in `block (2 levels) in each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:41:in `each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:45:in `block in each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:44:in `each_query_call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:27:in `block in apply_instrumenters'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:72:in `call_hooks'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/instrumentation.rb:26:in `apply_instrumenters'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:187:in `instrument_and_analyze'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:62:in `block in run_queries'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/tracing.rb:66:in `trace'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:60:in `run_queries'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/execution/multiplex.rb:50:in `run_all'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:1690:in `multiplex'
# /bundle/ruby/3.0.0/gems/graphql-1.12.4/lib/graphql/schema.rb:1661:in `execute'
# ./app/controllers/graphql_controller.rb:13:in `execute'
# /bundle/ruby/3.0.0/gems/actiontext-6.1.2.1/lib/action_text/rendering.rb:20:in `with_renderer'
# /bundle/ruby/3.0.0/gems/actiontext-6.1.2.1/lib/action_text/engine.rb:55:in `block (4 levels) in <class:Engine>'
# /bundle/ruby/3.0.0/gems/rack-attack-6.5.0/lib/rack/attack.rb:99:in `call'
# /bundle/ruby/3.0.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth/middleware/token_dispatcher.rb:20:in `call'
# /bundle/ruby/3.0.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth/middleware/revocation_manager.rb:21:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/builder.rb:244:in `call'
# /bundle/ruby/3.0.0/gems/warden-jwt_auth-0.5.0/lib/warden/jwt_auth/middleware.rb:23:in `call'
# /bundle/ruby/3.0.0/gems/rack-attack-6.5.0/lib/rack/attack.rb:113:in `call'
# /bundle/ruby/3.0.0/gems/warden-1.2.9/lib/warden/manager.rb:36:in `block in call'
# /bundle/ruby/3.0.0/gems/warden-1.2.9/lib/warden/manager.rb:34:in `catch'
# /bundle/ruby/3.0.0/gems/warden-1.2.9/lib/warden/manager.rb:34:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb:15:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/etag.rb:27:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/conditional_get.rb:40:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/head.rb:12:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:266:in `context'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:260:in `call'
# /bundle/ruby/3.0.0/gems/lograge-0.11.2/lib/lograge/rails_ext/rack/logger.rb:15:in `call_app'
# /bundle/ruby/3.0.0/gems/railties-6.1.2.1/lib/rails/rack/logger.rb:26:in `block in call'
# /bundle/ruby/3.0.0/gems/railties-6.1.2.1/lib/rails/rack/logger.rb:26:in `call'
# /bundle/ruby/3.0.0/gems/request_store-1.5.0/lib/request_store/middleware.rb:19:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/method_override.rb:24:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/runtime.rb:22:in `call'
# /bundle/ruby/3.0.0/gems/rack-2.2.3/lib/rack/sendfile.rb:110:in `call'
# /bundle/ruby/3.0.0/gems/rack-cors-1.1.1/lib/rack/cors.rb:100:in `call'
# /bundle/ruby/3.0.0/gems/railties-6.1.2.1/lib/rails/engine.rb:539:in `call'
# /bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb:29:in `request'
# /bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb:266:in `process_request'
# /bundle/ruby/3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb:119:in `request'
# ./spec/requests/graphql/queries/rewards_spec.rb:146:in `block (4 levels) in <top (required)>'
# /bundle/ruby/3.0.0/gems/webmock-3.11.2/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
Hi, there is a way to pass Graphql context to the Policies? Example:
# frozen_string_literal: true
module Types
class QueryType < Types::BaseObject
field :user,
resolver: Queries::User::Fetch,
preauthorize: { to: :user?, with: Queries::UsersPolicy }
end
end
module Queries
class UsersPolicy < ApplicationPolicy
authorize :user, allow_nil: true
def initialize(user, record)
# context here
variables = record.context.query.provided_variables
end
def user?
user.admin?
end
end
end
I need the query variables from graphql
Get error while authorizing inside mutation(think the same will be with resolver).
These steps are done:
class Types::BaseObject < GraphQL::Schema::Object
include ActionPolicy::GraphQL::Behaviour
end
class Types::BaseMutation < GraphQL::Schema::Mutation
include ActionPolicy::GraphQL::Behaviour
end
class Types::BaseResolver < GraphQL::Schema::Resolver
include ActionPolicy::GraphQL::Behaviour
end
Error message:
*** NoMethodError Exception: undefined method `authorize!' for #Mutations::Companies::CreateCompanyMutation:0x00007fb12c8fcd40
Did you mean? authorized?
But when authorize inside mutation_type.rb then it works:
field :create_company, mutation: Mutations::Companies::CreateCompanyMutation, authorize: {to: :create?, with: CompanyPolicy}
Have a look please :)
Hi, @palkan. There is a way to get record or the ID of the record to handle before to execute an action without "preauthorize"?
I do something like this:
Mutation type:
field :update_user,
mutation: Mutations::Users::UpdateUserMutation,
authorize: { to: :update?, with: Mutations::UserPolicy }
Mutation:
class UpdateUserMutation < BaseMutation
argument :id, String, required: true
argument :data, Types::Inputs::User::UpdateUser, required: true
type Types::Data::User
def resolve(id:, data:)
UserService.new.save(id, data)
end
Policy:
module Mutations
class UserPolicy < ApplicationPolicy
def update?
user.admin? || own?
end
private
def own?
record.id == user.id
end
end
end
That way I get "not authorized", but it still performs the service action even if not authorized. If I put "preauthorize" i can't get the "record" to handle.. ๐ค
I would need something to handle the record and stop the execution of resolve if it was not authorized. Is it possible?
My current implementation:
include ActionPolicy::Behaviour
field :can_ignore, ActionPolicy::GraphQL::Types::AuthorizationResult, null: false
def can_ignore
allowed_to? :ignore?
end
graphql-ruby's relay mutation resolver is a proc: (https://github.com/rmosolgo/graphql-ruby/blob/master/guides/relay/mutations.md). Is it possible to access the authorize!
method there? What is the best way to utilize the authorize method? Currently, I am just accessing it as a class method and passing the context manually.
Say I had a CourseType
with a way to query it from QueryType
with no authorization, through a field called courses
. Then the following query works as expected, returning a list of Course ids:
{
courses {
id
}
}
The problem is, when context[:current_user]
is null, attempting to access any of the exposed rules raises an exception Missing policy authorization context: user
.
For instance, lets say that the CourseType
has a way to query for a list of LessonType
with no authorization, through a field called lessons
. Additionally, consider that we expose the ability to create lessons on CourseType with the following: expose_authorization_rules :create?, with: LessonPolicy, field_name: :can_create_lesson
.
Now, if we ask the value of canCreateLesson
, an exception will be raised:
{
courses {
id
canCreateLesson {
value
}
}
}
Here is the graphql response with error backtrace:
{
"errors": [
{
"message": "Missing policy authorization context: user",
"backtrace": [
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/policy/authorization.rb:57:in `block in initialize'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/policy/authorization.rb:51:in `each'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/policy/authorization.rb:51:in `initialize'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/rails/policy/instrumentation.rb:16:in `block in initialize'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/activesupport-6.0.3/lib/active_support/notifications.rb:182:in `instrument'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/rails/policy/instrumentation.rb:16:in `initialize'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/behaviours/policy_for.rb:13:in `new'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/behaviours/policy_for.rb:13:in `policy_for'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/behaviours/thread_memoized.rb:50:in `block in policy_for'",
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy/behaviours/thread_memoized.rb:57:in `block in __policy_thread_memoize__'",
(more lines that are within the graphql gem)
I believe this is a bug? The expected behavior (I think) is to simply return false values with reasons (maybe something like user?
as the reason for I18n integration).
Looking through the action_policy-graphql code, it seems like having user in the context is a requirement? However, when I try to set the user context to Current.user || {}
to avoid nil
values, then I get a new error altogether:
{
"errors": [
{
"message": "Couldn't find policy class for <LessonPolicy#create?: false (reasons: {:lesson=>[:owner?]}) (ActionPolicy::Base::APR)",
"backtrace": [
"/Users/neiljohari/.rvm/gems/ruby-2.7.1/gems/action_policy-0.4.3/lib/action_policy.rb:35:in `lookup'",
(only the lookup seems relevant)
...
Is there any equivalent to verify_authorized? This is a must to helps us not forget to verify authorization.
When have authorize: true
set in my field (or I use authorize! user, to: :show?
), I have found that even with a nil
user context, it still tries to 'resolve', which causes a lot of undefined method x for nil:Nilclass
in places I have called the user object in the resolver.
I expected that if I have authorized: true
set, or authorize! user, to: :show?
, then it should first check for the presence of the authorization context before even resolving, and raise AuthorizationContextMissing
beforehand.
This way I do not have to check for the user object in every resolver, and I can instead use a
rescue_from(ActionPolicy::AuthorizationContextMissing) do
raise GraphQL::ExecutionError, 'Please login to perform this action'
end
Right now I have to raise GraphQL::ExecutionError, 'Please login to perform this action' unless current_user
in every resolver
Ruby Version: 2.7.1
Framework Version (Rails, whatever): Rails 6.1.4.1
Action Policy Version: 0.6.0
Action Policy GraphQL Version: 0.5.3
I'm trying to redefine authorization_namespace
but nothing worked. For example for a mutation, the namespace is Mutations
. When i tried to override it in BaseMutation, it was still Mutations
. So i set a pry inside my definition but it was never reached. I also tried overriding it inside BaseObject
or the GraphqlController
directly but nothing worked.
To be able to custom the authorization_namespace
My definition of authorization_namespace
was never acknowledged.
Can you tell me how are we supposed to do this ? Thanks a lot
Hey there,
Would it be possible to either push more releases to rubygems or make a separate beta branch for passing commits?
Would be good to be able to help test changes while not having to pull down breaking commits.
Cheers!
Ruby Version: 2.6
Framework Version (Rails, whatever): Rails
Action Policy Version: 0.5.3
Action Policy GraphQL Version: 0.5.0
Using field authorization in mutations.
It should raise authorization error if user is not authorized to access fields.
It raises NoMethodError
:
NoMethodError:
undefined method `authorize_mutation_raise_exception' for ActionPolicy::GraphQL:Module
Did you mean? authorize_raise_exception
preauthorize_mutation_raise_exception
Ruby Version: ruby 3.2.2
Framework Version (Rails, whatever): rails (7.0.4.3), graphql (2.0.21)
Action Policy Version: (0.6.5)
Action Policy GraphQL Version: (0.5.3)
We started using dataloader in our policies to help reduce the amount of queries.
We pass the graphql context as part of the policy context, which allows us to use our dataloaders in the database.
We expected everything to keep working as it did, but with less or equal amount of queries.
We started getting completely wrong results when using expose_authorization_rules
.
During our investigation we monkey patched the allowance_to
method like this:
def allowance_to(rule, record = :__undef__, **options)
policy = lookup_authorization_policy(record, **options)
policy.apply(authorization_rule_for(policy, rule))
policy.result.tap { |a| puts "policy_result: #{rule} #{a.inspect}" }
end
We got following (sample) output.
policy_result: publish? <PublicGraph::Policies::FolderNodePolicy#publish?: false>
policy_result: move_into? <PublicGraph::Policies::FolderNodePolicy#move_into?: false>
policy_result: create_item? <PublicGraph::Policies::FolderNodePolicy#move_into?: false>
policy_result: create_item_in? <PublicGraph::Policies::FolderNodePolicy#move_into?: false>
policy_result: create_folder_in? <PublicGraph::Policies::FolderNodePolicy#move_into?: false>
As you can see a discrepancy on the rule
is exposed. The result that is returned is not that of the rule requested.
In order to understand what is happening it is important to know that resolving of these policies have (somewhere in the stack) a call to dataloader. Dataloader uses fibers to do its "magic", basically stopping execution of functions as long as it can.
This seems to be incompatible with the (default on) ActionPolicy::Behaviours::Memoized
and ActionPolicy::Behaviours::ThreadMemoized
. When calling the apply
method (in allowance_to) the result is stored in the instance. When the thread is yielded (by the fiber scheduler) other threads can replace this result with an other call to apply.
As I see it this implementation can not be considered thread safe, especially in its default configuration.
Ruby Version: 2.6
Framework Version (Rails, whatever): Rails
Action Policy Version: 0.4.4
Action Policy GraphQL Version: 0.4.0
We are trying to combine action_policy-graphql with other custom extensions.
My custom extension is used for authentication and it is supposed to run before authentication. I have my extension injected inside field class and I expect that base field initialize
method runs before initialize
method of any module included into it.
I can see that AuthorizedField
is included multiple times and it's initialize
method is called before initialize
method defined in class:
> self.class.ancestors
=> [ActionPolicy::GraphQL::AuthorizedField,
Types::BaseField,
ActionPolicy::GraphQL::AuthorizedField,
GraphQL::Schema::Member::AcceptsDefinition::InitializeExtension,
GraphQL::Schema::Member::AcceptsDefinition::ToGraphQLExtension,
GraphQL::Schema::Field,
GraphQL::Schema::FindInheritedValue::EmptyObjects,
GraphQL::Schema::Member::HasPath,
GraphQL::Schema::Member::HasAstNode,
GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader,
GraphQL::Schema::Member::HasArguments,
GraphQL::Schema::Member::AcceptsDefinition,
GraphQL::Schema::Member::CachedGraphQLDefinition,
ActiveSupport::ToJsonWithActiveSupportEncoder,
Object,
Nori::CoreExt::Object,
FriendlyId::ObjectUtils,
RequireAll,
PP::ObjectMixin,
JSON::Ext::Generator::GeneratorMethods::Object,
ActiveSupport::Tryable,
ActiveSupport::Dependencies::Loadable,
Kernel,
BasicObject]
Why i cant use authorized_scope with authorize in query type?
Hey @palkan I'm currently in the process of creating a resolver that takes an argument called type
and depending on the type there are different types of authorization scopes I want to apply, however, I'm not sure how or if there's a way to either access resolver arguments in a policy, or how to apply different behaviors of a policy depending on the resolver arguments.
here's an example:
def feed(type:)
ArticlePolicy.public if type == "PUBLIC"
ArticlePolicy.personalized if type == "PERSONALIZED"
end
the intent here is depending on which feed is being queried, I want to limit the kind of articles shown. e.g. if they want to see the public feed, they see all public articles, but if they want to see a personalized feed of articles of all the people they follow, then it'll only show those.
How would you approach something like this with ActionPolicy?
In GraphQL is often preferred to return nil
if you're unauthorized to view something.
and only raise an error in a mutation.
I'd like to have a feature
that works like something along the lines of
config/initializers/action_policy.rb
ActionPolicy::GraphQL.configure do |config|
config.defaults.mutation.authorize.raise = true
config.defaults.mutation.preauthorize.raise = true
config.defaults.query.authorize.raise = false
config.defaults.query.preauthorize.raise = false
end
to set the defaults of how ActionPolicy works in GraphQL
Use the shorter version of ActionPolicy::GraphQL::Behaviour
to make it work:
module Types
module Base
class Resolver < ::GraphQL::Schema::Resolver
include ActionPolicy::Behaviour
include ActionPolicy::Behaviours::ThreadMemoized
include ActionPolicy::Behaviours::Memoized
include ActionPolicy::Behaviours::Namespaced
authorize :user, through: :current_user
def current_user
context[:current_user]
end
end
end
end
Would be great if it will be supported out of the box.
@palkan is it possible to use authorized_Scope on an input like you would with params
from rail's strong parameters?
I saw that you had this feature in Rails and it started to make me wonder why inputs
don't work like strong parameters in the context of GraphQL Ruby.
Hello,
We are on our way to implement pre-authorization on fields in our GraphQL schema and on mutations in particular. We have quite a lot of them and, to reduce redundant declaration, we'd like to use the .with_options
method provided by ActiveSupport to merge options common to mutation field declarations around the same model.
Ruby Version: 3.2
Framework Version: Rails 7.1
Action Policy Version: 0.6.7
Action Policy GraphQL Version: 0.5.3
class Mutation < Types::Objects::Base
with_options preauthorize: { with: ThingPolicy } do
field :thing_create, mutation: Mutations::Things::Create
field :thing_update, mutation: Mutations::Things::Update
end
end
with_options
should work as expected and actually forward options to every .field
call.
with_options
works as expected but field extensions consume options in a destructive way: as they internally use Hash#delete
on the options hash, the first field is correctly extended but subsequent fields receive empty options.
This can be fixed in a minimalist way with something like:
# In action_policy/graphql/authorized_field.rb
module ActionPolicy::GraphQL::AuthorizedField
class Extension < ::GraphQL::Schema::FieldExtension
+ def initialize(field:, options:) = super(field:, options: options&.dup || {})
+
def extract_option(key, &default)
value = options.fetch(key, &default)
options.delete key
value
end
end
end
Note: This will not be fixed in graphql-ruby. See rmosolgo/graphql-ruby#4733.
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.
Hey @palkan,
Hope you're doing well.
I was wondering if you could show an example of how I might implement an authorized_scope with a relay connection.
I tried setting it up initially but it gave me the following error:
Couldn't find policy class for [#<Types::QueryType:0x00007f986eb5a730 @object=nil, @context=#<Query::Context ...>, @__policies_cache__={\"//70146334492460\"=>nil}, @authorization_namespace=Types>, \"Couldn't find implicit authorization target for Types::QueryType. Please, provide policy class explicitly using `with` option or define the `implicit_authorization_target` method.\"] (Array)```
Hi there @palkan! ๐
First of all, thanks for your amazing gem :)
Here is my issue: I would like to be able to authorize some specific arguments in a mutation, and not only the mutation as a whole.
Let's say I have a mutation to update a User. Maybe the user itself could update its first_name, but only the admin could upgrade its role, and the manager could assign it in a specific team.
I would like to do something like this:
class Mutations::UpdateUser < BaseMutation
argument :user_id, ID, loads: Types::UserType, required: true
argument :first_name, String, required: false # no specific authorization here
argument :last_name, String, required: false # no specific authorization here
argument :team_id, ID, loads: Types::Team, required: false, authorize: true
argument :role, Types::RoleEnum, required: false, authorize: true
def resolve(user:, **params)
authorize! user, to: :update?
user.update!(params)
{ user: user }
end
end
And then, in my policy class, I could have something like this:
class UserPolicy < ApplicationPolicy
def team? = user.manager? || user.admin?
def role? = user.admin?
def update? # my mutation authorization logic
end
For now, the only way to achieve this I found was to do all this argument-validation logic inside my resolver, but the implementation feels a bit lame.
For example:
def resolve(user:, team: nil, role: nil, **params)
authorize! user, to: :update?
authorize! user, to: :update_team? if team
authorize! user, to: :update_role? if role
end
Is there a better way to manage this?
Thanks a lot :)
I like the idea behind the expose_authorization_rules
macro. But it only works with existing records. What about if I have to check if there are a permission for the user to create a post? At the moment I'm using the following pattern:
user.rb
module Types
module Models
class User < Types::Base::Object
# ...
field :can_create_post, Boolean, null: false
def can_create_post
allowed_to?(:create_post?, object)
end
end
end
end
user_policy.rb
class UserPolicy < ApplicationPolicy
# actions with associations
def create_post?
policy_for(record: Post).create?
end
end
post_policy.rb
class PostPolicy < ApplicationPolicy
def create?
user.role.is_a?(Manager)
end
end
post.rb (mutation)
def resolve(text:)
authorize!(current_user || ::User, to: :create_post?)
I found it most simple, but seems like it might be improved.
Hi @palkan ,
Big fan of your work! I'm trying to set up authorization on my project and while it's clear to place the permissions inline when there is an object (edit, destroy), it's less obvious where to put permissions for index and create, which are at the class level.
One way is to place them inside a Type::PermissionType class.
How would you recommend organizing this? Thank you
a few questions I have is one,
in the case that my current_user
is called viewer
in context, what does the modification of that look like to setup ActionPolicy GraphQL?
also, I initially tried this:
# frozen_string_literal: true
class Types::Base < GraphQL::Schema::Object
include ActionPolicy::GraphQL::Behaviour
field_class Fields::Base
def current_user
context[:viewer]
end
end
but I am having issues it seems.
when I followed the way to authorize objects at a type level .e.g:
class Types::Post < Types::Base
#...
def self.authorized?(object, context)
super &&
object.allowed_to?(
:show?,
object,
context: {user: context[:viewer]}
)
end
end
I get an undefined method allowed_to?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.