class JSONAPI::Authorization::DefaultPunditAuthorizer

An authorizer is a class responsible for linking JSONAPI operations to your choice of authorization mechanism.

This class uses Pundit for authorization. You can use your own authorizer class instead if you have different needs. See the README.md for configuration information.

Fetching records is the concern of PunditScopedResource which in turn affects which records end up being passed here.

Attributes

user[R]

Public Class Methods

new(context:) click to toggle source

Creates a new DefaultPunditAuthorizer instance

Parameters

  • context - The context passed down from the controller layer

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 20
def initialize(context:)
  @user = JSONAPI::Authorization.configuration.user_context(context)
end

Public Instance Methods

create_resource(source_class:, related_records_with_context:) click to toggle source

POST /resources

Parameters

  • source_class - The class of the record to be created

  • related_records_with_context - A has with the association type,

the relationship name, and an Array of new related records.

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 107
def create_resource(source_class:, related_records_with_context:)
  ::Pundit.authorize(user, source_class, 'create?')
  related_records_with_context.each do |data|
    relation_name = data[:relation_name]
    records = data[:records]
    relationship_method = "create_with_#{relation_name}?"
    policy = ::Pundit.policy(user, source_class)
    if policy.respond_to?(relationship_method)
      unless policy.public_send(relationship_method, records)
        raise ::Pundit::NotAuthorizedError,
              query: relationship_method,
              record: source_class,
              policy: policy
      end
    else
      Array(records).each do |record|
        ::Pundit.authorize(user, record, 'update?')
      end
    end
  end
end
create_to_many_relationship(source_record:, new_related_records:, relationship_type:) click to toggle source

POST /resources/:id/relationships/other-resources

A request for adding to a has_many association

Parameters

  • source_record - The record whose relationship is modified

  • new_related_records - The new records to be added to the association

  • relationship_type - The relationship type

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 165
def create_to_many_relationship(source_record:, new_related_records:, relationship_type:)
  relationship_method = "add_to_#{relationship_type}?"
  authorize_relationship_operation(
    source_record: source_record,
    relationship_method: relationship_method,
    related_record_or_records: new_related_records
  )
end
find(source_class:) click to toggle source

GET /resources

Parameters

  • source_class - The source class (e.g. Article for ArticleResource)

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 29
def find(source_class:)
  ::Pundit.authorize(user, source_class, 'index?')
end
include_has_many_resource(source_record:, record_class:) click to toggle source

Any request including ?include=other-resources

This will be called for each has_many relationship if the include goes deeper than one level until some authorization fails or the include directive has been travelled completely.

We can't pass all the records of a has_many association here due to performance reasons, so the class is passed instead.

Parameters

  • source_record — The source relationship record, e.g. an Article in

    article.comments check
    
  • record_class - The underlying record class for the relationships

    resource.

rubocop:disable Lint/UnusedMethodArgument

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 243
def include_has_many_resource(source_record:, record_class:)
  ::Pundit.authorize(user, record_class, 'index?')
end
include_has_one_resource(source_record:, related_record:) click to toggle source

Any request including ?include=another-resource

This will be called for each has_one relationship if the include goes deeper than one level until some authorization fails or the include directive has been travelled completely.

Parameters

  • source_record — The source relationship record, e.g. an Article in

    article.author check
    
  • related_record - The associated record to return

rubocop:disable Lint/UnusedMethodArgument

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 260
def include_has_one_resource(source_record:, related_record:)
  ::Pundit.authorize(user, related_record, 'show?')
end
remove_resource(source_record:) click to toggle source

DELETE /resources/:id

Parameters

  • source_record - The record to be removed

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 134
def remove_resource(source_record:)
  ::Pundit.authorize(user, source_record, 'destroy?')
end
remove_to_many_relationship(source_record:, related_records:, relationship_type:) click to toggle source

DELETE /resources/:id/relationships/other-resources

A request to disassociate elements of a has_many association

Parameters

  • source_record - The record whose relationship is modified

  • related_records - The records which will be disassociated from source_record

  • relationship_type - The relationship type

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 202
def remove_to_many_relationship(source_record:, related_records:, relationship_type:)
  relationship_method = "remove_from_#{relationship_type}?"
  authorize_relationship_operation(
    source_record: source_record,
    relationship_method: relationship_method,
    related_record_or_records: related_records
  )
end
remove_to_one_relationship(source_record:, relationship_type:) click to toggle source

DELETE /resources/:id/relationships/another-resource

A request to disassociate a has_one association

Parameters

  • source_record - The record whose relationship is modified

  • relationship_type - The relationship type

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 219
def remove_to_one_relationship(source_record:, relationship_type:)
  relationship_method = "remove_#{relationship_type}?"
  authorize_relationship_operation(
    source_record: source_record,
    relationship_method: relationship_method
  )
end
replace_fields(source_record:, related_records_with_context:) click to toggle source

PATCH /resources/:id

Parameters

  • source_record - The record to be modified

  • related_records_with_context - A hash with the association type,

the relationship name, an Array of new related records.

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 92
def replace_fields(source_record:, related_records_with_context:)
  ::Pundit.authorize(user, source_record, 'update?')
  authorize_related_records(
    source_record: source_record,
    related_records_with_context: related_records_with_context
  )
end
replace_to_many_relationship(source_record:, new_related_records:, relationship_type:) click to toggle source

PATCH /resources/:id/relationships/other-resources

A replace request for a has_many association

Parameters

  • source_record - The record whose relationship is modified

  • new_related_records - The new records replacing the entire has_many association

  • relationship_type - The relationship type

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 184
def replace_to_many_relationship(source_record:, new_related_records:, relationship_type:)
  relationship_method = "replace_#{relationship_type}?"
  authorize_relationship_operation(
    source_record: source_record,
    relationship_method: relationship_method,
    related_record_or_records: new_related_records
  )
end
replace_to_one_relationship(source_record:, new_related_record:, relationship_type:) click to toggle source

PATCH /resources/:id/relationships/another-resource

A replace request for a has_one association

Parameters

  • source_record - The record whose relationship is modified

  • new_related_record - The new record replacing the old record

  • relationship_type - The relationship type

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 147
def replace_to_one_relationship(source_record:, new_related_record:, relationship_type:)
  relationship_method = "replace_#{relationship_type}?"
  authorize_relationship_operation(
    source_record: source_record,
    relationship_method: relationship_method,
    related_record_or_records: new_related_record
  )
end
show(source_record:) click to toggle source

GET /resources/:id

Parameters

  • source_record - The record to show

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 38
def show(source_record:)
  ::Pundit.authorize(user, source_record, 'show?')
end
show_relationship(source_record:, related_record:) click to toggle source

GET /resources/:id/relationships/other-resources GET /resources/:id/relationships/another-resource

A query for a has_one or a has_many association

Parameters

  • source_record - The record whose relationship is queried

  • related_record - The associated has_one record to show or nil if the associated record was not found. For a has_many association, this will always be nil

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 53
def show_relationship(source_record:, related_record:)
  ::Pundit.authorize(user, source_record, 'show?')
  ::Pundit.authorize(user, related_record, 'show?') unless related_record.nil?
end

Private Instance Methods

authorize_relationship_operation( source_record:, relationship_method:, related_record_or_records: nil ) click to toggle source

rubocop:enable Lint/UnusedMethodArgument

# File lib/jsonapi/authorization/default_pundit_authorizer.rb, line 267
def authorize_relationship_operation(
  source_record:,
  relationship_method:,
  related_record_or_records: nil
)
  policy = ::Pundit.policy(user, source_record)
  if policy.respond_to?(relationship_method)
    args = [relationship_method, related_record_or_records].reject(&:nil?)
    unless policy.public_send(*args)
      raise ::Pundit::NotAuthorizedError,
            query: relationship_method,
            record: source_record,
            policy: policy
    end
  else
    ::Pundit.authorize(user, source_record, 'update?')
    if related_record_or_records
      Array(related_record_or_records).each do |related_record|
        ::Pundit.authorize(user, related_record, 'update?')
      end
    end
  end
end