Skip to content
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

Detect Authorization plugin and include it in bake output #993

Merged
merged 5 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Command/ControllerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Datasource\ConnectionManager;

/**
Expand Down Expand Up @@ -219,6 +220,10 @@ public function getComponents(Arguments $args): array
if ($args->getOption('components')) {
$components = explode(',', $args->getOption('components'));
$components = array_values(array_filter(array_map('trim', $components)));
} else {
if (Plugin::isLoaded('Authorization')) {
$components[] = 'Authorization.Authorization';
}
}

return $components;
Expand Down
3 changes: 3 additions & 0 deletions templates/bake/element/Controller/add.twig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
public function add()
{
${{ singularName }} = $this->{{ currentModelName }}->newEmptyEntity();
{% if Bake.hasPlugin('Authorization') %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am curious about the use case for authorizing using an empty entity. Personally I have only authorized the "add" action.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applications could have roles or rules on who can create what. I agree that most of the time add() will have a policy of return true.

$this->Authorization->authorize(${{ singularName }});
{% endif %}
if ($this->request->is('post')) {
${{ singularName }} = $this->{{ currentModelName }}->patchEntity(${{ singularName }}, $this->request->getData());
if ($this->{{ currentModelName }}->save(${{ singularName }})) {
Expand Down
3 changes: 3 additions & 0 deletions templates/bake/element/Controller/delete.twig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
{
$this->request->allowMethod(['post', 'delete']);
${{ singularName }} = $this->{{ currentModelName }}->get($id);
{% if Bake.hasPlugin('Authorization') %}
$this->Authorization->authorize(${{ singularName }});
{% endif %}
if ($this->{{ currentModelName }}->delete(${{ singularName }})) {
$this->Flash->success(__('The {{ singularHumanName|lower }} has been deleted.'));
} else {
Expand Down
3 changes: 3 additions & 0 deletions templates/bake/element/Controller/edit.twig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
public function edit($id = null)
{
${{ singularName }} = $this->{{ currentModelName }}->get($id, contain: {{ Bake.exportArray(belongsToMany)|raw }});
{% if Bake.hasPlugin('Authorization') %}
$this->Authorization->authorize(${{ singularName }});
{% endif %}
if ($this->request->is(['patch', 'post', 'put'])) {
${{ singularName }} = $this->{{ currentModelName }}->patchEntity(${{ singularName }}, $this->request->getData());
if ($this->{{ currentModelName }}->save(${{ singularName }})) {
Expand Down
3 changes: 3 additions & 0 deletions templates/bake/element/Controller/index.twig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
->contain({{ Bake.exportArray(belongsTo)|raw }});
{% else %}
$query = $this->{{ currentModelName }}->find();
{% endif %}
{% if Bake.hasPlugin('Authorization') %}
$query = $this->Authorization->applyScope($query);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to bake stub code for these?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is helpful. I usually use this to build in permissions like 'view all the projects I have membership in'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, should we bake/generate the authorization code stubs to match this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, should we bake/generate the authorization code stubs to match this?

That can be done already with bake policy --type table Articles. If we wanted to get fancy we could make the bake code generation respect whether or not you have a table/entity policy generated.

{% endif %}
${{ pluralName }} = $this->paginate($query);

Expand Down
3 changes: 3 additions & 0 deletions templates/bake/element/Controller/view.twig
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@
public function view($id = null)
{
${{ singularName }} = $this->{{ currentModelName }}->get($id, contain: {{ Bake.exportArray(allAssociations)|raw }});
{% if Bake.hasPlugin('Authorization') %}
$this->Authorization->authorize(${{ singularName }});
{% endif %}
$this->set(compact('{{ singularName }}'));
}
36 changes: 35 additions & 1 deletion tests/TestCase/Command/ControllerCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function tearDown(): void
parent::tearDown();
$this->getTableLocator()->clear();

$this->removePlugins(['ControllerTest', 'Company/Pastry']);
$this->removePlugins(['ControllerTest', 'Company/Pastry', 'Authorization', 'BakeTest']);
}

/**
Expand Down Expand Up @@ -102,6 +102,25 @@ public function testGetComponents()
$this->assertSame(['Auth', 'RequestHandler'], $result);
}

/**
* test component generation with auto-detect for core plugins
*
* @return void
*/
public function testGetComponentsInferredDefaults()
{
$this->_loadTestPlugin('Authorization');

$command = new ControllerCommand();
$args = new Arguments([], [], []);
$result = $command->getComponents($args);
$this->assertSame(['Authorization.Authorization'], $result);

$args = new Arguments([], ['components' => 'Flash, FormProtection'], []);
$result = $command->getComponents($args);
$this->assertSame(['Flash', 'FormProtection'], $result);
}

/**
* test helper generation
*
Expand Down Expand Up @@ -193,6 +212,21 @@ public function testBakeActions()
$this->assertSameAsFile(__FUNCTION__ . '.php', $result);
}

/**
* Test the integration with Authorization plugin
*/
public function testBakeActionsAuthorizationPlugin()
{
$this->_loadTestPlugin('Authorization');

$this->generatedFile = APP . 'Controller/BakeArticlesController.php';
$this->exec('bake controller --connection test --no-test BakeArticles');

$this->assertExitSuccess();
$result = file_get_contents($this->generatedFile);
$this->assertSameAsFile(__FUNCTION__ . '.php', $result);
}

/**
* test bake actions prefixed.
*
Expand Down
123 changes: 123 additions & 0 deletions tests/comparisons/Controller/testBakeActionsAuthorizationPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);

namespace Bake\Test\App\Controller;

/**
* BakeArticles Controller
*
* @property \Bake\Test\App\Model\Table\BakeArticlesTable $BakeArticles
* @property \Authorization\Controller\Component\AuthorizationComponent $Authorization
*/
class BakeArticlesController extends AppController
{
/**
* Initialize controller
*
* @return void
*/
public function initialize(): void
{
parent::initialize();

$this->loadComponent('Authorization.Authorization');
}

/**
* Index method
*
* @return \Cake\Http\Response|null|void Renders view
*/
public function index()
{
$query = $this->BakeArticles->find()
->contain(['BakeUsers']);
$query = $this->Authorization->applyScope($query);
$bakeArticles = $this->paginate($query);

$this->set(compact('bakeArticles'));
}

/**
* View method
*
* @param string|null $id Bake Article id.
* @return \Cake\Http\Response|null|void Renders view
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$bakeArticle = $this->BakeArticles->get($id, contain: ['BakeUsers', 'BakeTags', 'BakeComments']);
$this->Authorization->authorize($bakeArticle);
$this->set(compact('bakeArticle'));
}

/**
* Add method
*
* @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
*/
public function add()
{
$bakeArticle = $this->BakeArticles->newEmptyEntity();
$this->Authorization->authorize($bakeArticle);
if ($this->request->is('post')) {
$bakeArticle = $this->BakeArticles->patchEntity($bakeArticle, $this->request->getData());
if ($this->BakeArticles->save($bakeArticle)) {
$this->Flash->success(__('The bake article has been saved.'));

return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The bake article could not be saved. Please, try again.'));
}
$bakeUsers = $this->BakeArticles->BakeUsers->find('list', limit: 200)->all();
$bakeTags = $this->BakeArticles->BakeTags->find('list', limit: 200)->all();
$this->set(compact('bakeArticle', 'bakeUsers', 'bakeTags'));
}

/**
* Edit method
*
* @param string|null $id Bake Article id.
* @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function edit($id = null)
{
$bakeArticle = $this->BakeArticles->get($id, contain: ['BakeTags']);
$this->Authorization->authorize($bakeArticle);
if ($this->request->is(['patch', 'post', 'put'])) {
$bakeArticle = $this->BakeArticles->patchEntity($bakeArticle, $this->request->getData());
if ($this->BakeArticles->save($bakeArticle)) {
$this->Flash->success(__('The bake article has been saved.'));

return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The bake article could not be saved. Please, try again.'));
}
$bakeUsers = $this->BakeArticles->BakeUsers->find('list', limit: 200)->all();
$bakeTags = $this->BakeArticles->BakeTags->find('list', limit: 200)->all();
$this->set(compact('bakeArticle', 'bakeUsers', 'bakeTags'));
}

/**
* Delete method
*
* @param string|null $id Bake Article id.
* @return \Cake\Http\Response|null Redirects to index.
* @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function delete($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$bakeArticle = $this->BakeArticles->get($id);
$this->Authorization->authorize($bakeArticle);
if ($this->BakeArticles->delete($bakeArticle)) {
$this->Flash->success(__('The bake article has been deleted.'));
} else {
$this->Flash->error(__('The bake article could not be deleted. Please, try again.'));
}

return $this->redirect(['action' => 'index']);
}
}
14 changes: 14 additions & 0 deletions tests/test_app/Plugin/Authorization/src/AuthorizationPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);

namespace Authorization;

use Cake\Core\BasePlugin;

/**
* Plugin class stub for Authorization tests
*/
class AuthorizationPlugin extends BasePlugin
{
protected ?string $name = 'Authorization';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);

namespace Authorization\Controller\Component;

use Cake\Controller\Component;

class AuthorizationComponent extends Component
{
}
Loading