limoncello-php / app

Quick start JSON API application

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Authorization on single resource

dreamsbond opened this issue · comments

I have a scenario where user may access only certain record which are related to their user id.
it is capable to do so on editing,
but how about on viewing single resource.

and is it capable to do it on all resources but only returns record related to the users as a collection?

thanks

You have more than one way how to deal with it. The way I do is the following:

  • Require a user to be authenticated for viewing any records (index and read methods).
  • On API level take current user ID and change an SQL query so the result includes rows visible for the given user ID.

Default CRUD implementation has method builderOnIndex which is called just before execution of the SQL query. You can override the method and add extra SQL conditions that meet your requirements.

    protected function builderOnIndex(QueryBuilder $builder): QueryBuilder
    {
        $builder = parent::builderOnIndex($builder);

        // add extra conditions to $builder

        return $builder;
    }

@dreamsbond let me know it the explanation was sufficient

@neomerx checking on whether i could bring the current user into the the api

OK, let's use the sample app as a base.

Have a look at the line where it requires current use to have view permission. The actual permission check to be executed is this.

You can modify the permission check and require the current user to be signed in.

    public static function canViewPosts(ContextInterface $context): bool
    {
        $userId = self::getCurrentUserIdentity($context);

        return $userId !== null;
    }

Alright, now you can be sure that current user must be authenticated otherwise a security exception occurs and JSON API Error would be returned. But how can you use current user ID to change the query to the database?

You override method in PostsApi

    /**
     * @inheritdoc
     */
    protected function builderOnIndex(QueryBuilder $builder): QueryBuilder
    {
        // Version 1

        /** @var AccountManagerInterface $manager */
        /** @var PassportAccountInterface $account */
        $manager = $this->getContainer()->get(AccountManagerInterface::class);
        $account = $manager->getAccount();
        $userId  = $account->getUserIdentity();
        assert($userId !== null);

        // only own posts are visible $condition = 'posts.user_id=:dcValue1'
        $condition = Model::TABLE_NAME . '.' . Model::FIELD_ID_USER . '=' . $builder->createNamedParameter($userId);

        return parent::builderOnIndex($builder->andWhere($condition));
    }

PSR ContainerInterface obtainable throuh $this->getContainer() is a cornerstone of framework design. You get everything from it. Anything from standard components/services to your own are taken through container. When you ask 'Where should I get XXX from?' In 99.99999% cases the answer is 'from the container'.

Now version 2. It could be a bit simplified to

    /**
     * @inheritdoc
     */
    protected function builderOnIndex(QueryBuilder $builder): QueryBuilder
    {
        // Version 2

        // only own posts are visible $condition = 'posts.user_id=:dcValue1'
        $condition = Model::TABLE_NAME . '.' . Model::FIELD_ID_USER . '=' .
            $builder->createNamedParameter($this->getCurrentUserIdentity());

        return parent::builderOnIndex($builder->andWhere($condition));
    }

@neomerx your explanation are sufficient, thanks.
btw, i got another issue, a use case of having conditional select for not only user id in api, but also its relationship (e.g. group of user)

I preliminary write a sql statement selecting the user's group id and add to the conditional select in api.
is it a proper approach to do so?

@neomerx and also, is it possible to bring in:

protected static function isAdministrators(ContextInterface $context): bool
{
    $isAdministrators = false;

    if (($userId = self::getCurrentUserIdentity($context) !== null)) {
        $container = self::ctxGetContainer($context);
        $factory = $container->get(FactoryInterface::class);
        $api = $factory->createApi(UsersApi::class);
        $user = $api->readResource($userId);

        $isAdministrators = $user !== null && $user->{User::FIELD_ID_ROLE} === RolesSeed::ROLE_ID_ADMINISTRATORS;
    }

    return $isAdministrators;
}

into Api for builderOnIndex?

i am thinking if i could do a check on authenticated user, as as well identifying there corresponding role and restrict the return of data they could retrieve.

There is a better way to check the current user properties. The logged in user already has all the properties loaded. Try to experiment in \App\Json\Api\PostsApi::create it has some usefull methods already
image

and you can take all user properties like this
image

@neomerx thanks and really helpful. It makes me have a better understanding on the usage of the framework. Thanks again!^^

@dreamsbond I've added descriptions for Request-Response lifecycle, container, and settings. I hope it provides a better understanding of how limoncello application works. Feel free to ask questions and propose changes to the documentation.