Skip to content

Authorizer runs before Validator, exposes runtime error risk #121

@GregPeden

Description

@GregPeden

I THINK, per this experience, that the validation test relationshipRules() is run AFTER the authorizer is run. That might be okay, it just means that the authorizer logic has to assume that required attributes or relationships might not be present or may be set to null (meaning 'no relationship'). This seems undesirable, but maybe it's important, in which case this should be well documented.

Example below, where setting the relationship "project" to null causes a fatal error "Call to a member function getId() on null in /home/vagrant/Code/hoistway-cms/app/JsonApi/Tasks/Authorizer.php:59". I can design around this by checking first, or it would be fixed if validation were performed first. Maybe there is a reason to not do that, though? Note that 'TemplateAuthorizer' is my own common auth check class, and for Tasks it has a special case so I am only overwriting the relevant test.

<?php

namespace App\JsonApi\Tasks;

use App\JsonApi\TemplateValidators;
use App\Models\Task\Task;
use CloudCreativity\JsonApi\Contracts\Validators\RelationshipsValidatorInterface;

class Validators extends TemplateValidators
{
    /**
     * @var string
     */
    protected $resourceType = 'tasks';

    /**
     * @var string[]
     */
    protected $allowedIncludePaths = [
        'project',
        'task-type'
    ];

    /**
     * @var array
     */
    protected $allowedSortParameters = [
        'created_at',
        'updated_at',
        'name',
        'slug'
    ];

    /**
     * @var array
     */
    protected $allowedFilteringParameters = [
        'id',
        'name',
        'slug'
    ];

    /**
     * Get the validation rules for the resource attributes.
     *
     * @param Task $record
     *      the record being updated, or null if it is a create request.
     * @return array
     */
    protected function attributeRules($record = null)
    {
        return [
            'name' => ['required', 'string', 'between:3,96'],
        ];
    }

    /**
     * Define the validation rules for the resource relationships.
     *
     * @param RelationshipsValidatorInterface $relationships
     * @param object|null $record
     *      the record being updated, or null if it is a create request.
     * @return void
     */
    protected function relationshipRules(RelationshipsValidatorInterface $relationships, $record = null)
    {
        $relationships->hasOne('project', 'projects', is_null($record), false);
        $relationships->hasOne('task-type', 'task-types', is_null($record), false);
    }

}
<?php

namespace App\JsonApi\Tasks;

use App\JsonApi\TemplateAuthorizer;
use App\Models\Project\Project;
use App\Models\Task\Task;
use CloudCreativity\JsonApi\Contracts\Object\ResourceInterface;
use Gate;
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;

class Authorizer extends TemplateAuthorizer
{

    /**
     * Return the Eloquent model to be used for policy checks
     *
     * @return string
     */
    protected function getPolicyModel()
    {
        return Task::class;
    }

    /**
     * Can the client read many resources at once?
     *
     * Encoding parameters are provided in case the parameters such as
     * filtering or inclusion paths affect whether the resources can be read.
     *
     * @param string $resourceType
     *      the requested resource type.
     * @param EncodingParametersInterface $parameters
     *      the parameters provided by the client
     * @return bool
     */
    public function canReadMany($resourceType, EncodingParametersInterface $parameters)
    {
        return false;
    }

    /**
     * Can the client create the provided resource?
     *
     * @param string $resourceType
     *      the resource type being created.
     * @param ResourceInterface $resource
     *      the resource provided by the client.
     * @param EncodingParametersInterface $parameters
     *      the parameters provided by the client
     * @return bool
     */
    public function canCreate($resourceType, ResourceInterface $resource, EncodingParametersInterface $parameters)
    {
        if (!$resource->getRelationships()->has('project')) {
            return false;
        }

        $projectId = $resource->getRelationships()->getRelationship('project')->getData()->getId();
        $project = Project::find($projectId);

        return Gate::allows('update', $project);
    }
}

Example of bad JSON package which cases server-side error:

data: {
  attributes: {
    name: 'Test'
  },
  relationships: {
    project: {
      data: null,
    },
    'task-type': {
      data: {
        type: 'task-types',
        id: '1'
      }
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions