CakeDC Blog

TIPS, INSIGHTS AND THE LATEST FROM THE EXPERTS BEHIND CAKEPHP

Integrating Users and ACL plugins in CakePHP

In previous posts, we saw how CakeDC Users plugin can help you to build an application that manages everything related to users: registration, social login, permissions, etc. Recently it has been noted by the team that there are some use cases where a deeper control of permissions is needed - more than is offered in RBAC. Today we’ll go into this using the ACL approach.

ACL or Access Control List, refers to the application using a detailed list of objects to decide who can access what. It can be as detailed as particular users and rows through to specifying which action can be performed (i.e user XX has permissions to edit articles but does not have permissions to delete articles).

One of the big features of ACL is that both the accessed objects; and objects who ask for access, can be organized in trees.

There’s a good explanation of how ACL works in the CakePHP 2.x version of the Book.

ACL does not form part of CakePHP core V 3.0 and can be accessed through the use of the cakephp/acl plugin.

Let’s just refresh the key concepts of ACL:

  • ACL: Access Control List (the whole paradigm)

  • ACO: Access Control Object (a thing that is wanted), e.g. an action in a controller: creating an article

  • ARO: Access Request Object (a thing that wants to use stuff), e.g. a user or a group of users

  • Permission: relation between an ACO and an ARO

For the purpose of this article - we shall use this use case: You are using CakeDC/users plugin and now want to implement ACL in your application.

Installation

Starting with a brand new CakePHP app:

composer selfupdate && composer create-project --prefer-dist cakephp/app acl_app_demo && cd acl_app_demo

We are going to use CakeDC/users and cakephp/acl plugins. In a single step we can install them with composer:

composer require cakedc/users cakephp/acl

Create a DB and set its name and credentials in the config/app.php file of the just created app (in the Datasources/default section). This command can help you out if you are using MySQL:

mysql -u root -p -e "create user acl_demo; create database acl_demo; grant all privileges on acl_demo.* to acl_demo;"

Plugins will be loaded always with the app. Let’s set them on the bootstrap file:

bin/cake plugin load -br CakeDC/Users
bin/cake plugin load -b Acl

Now let’s insert a line in bootstrap.php before Users plugin loading, so cakedc/users will read the configuration from the config/users.php file of our app.

Configure::write('Users.config', ['users']);

This file does not exist yet. The plugin provides a default file which is very good to start with. Just copy it to your app running:

cp -i vendor/cakedc/users/config/users.php config/

Also, let’s copy the permissions file the same way to avoid warnings in our log files:

cp -i vendor/cakedc/users/config/permissions.php config/

We need to change cakedc/users config: remove RBAC, add ACL. In cakephp/acl there’s ActionsAuthorize & CrudAuthorize. We’ll start just using ActionsAuthorize. We will tell ActionsAuthorize that actions will be under the 'controllers/' node and that the users entity will be MyUsers (an override of the Users entity from the plugin).

Edit the Auth/authorize section of config/users.php so that it sets:

        'authorize' => [
            'CakeDC/Auth.Superuser',
            'Acl.Actions' => [
                'actionPath' => 'controllers/',
                'userModel' => 'MyUsers',
            ],
        ],

Add calls to load components both from Acl & Users plugin in the initialize() method in AppController:

class AppController extends Controller
{
    public function initialize()
    {
        parent::initialize();
        
        // (...)
        $this->loadComponent('Acl', [
            'className' => 'Acl.Acl'
        ]);
        $this->loadComponent('CakeDC/Users.UsersAuth');
        // (...)
    }
    
    // (...)
}

Database tables

Some tables are required in the database to let the plugins work. Those are created automatically just by running their own migrations:

bin/cake migrations migrate -p CakeDC/Users
bin/cake migrations migrate -p Acl

One table from the Acl plugin needs to be fixed because Users migration creates users.id as UUID (CHAR(36)) and Acl migrations creates AROs foreing keys as int(11). Types must match. Let’s fix it adapting the aros table field:

ALTER TABLE aros CHANGE foreign_key foreign_key CHAR(36) NULL DEFAULT NULL;

Now, it’s time to set our own tables as needed for our app. Let’s suppose we are developing a CMS app as specified in the CMS Tutorial from the CakePHP book.

Based on the tutorial, we can create a simplified articles table:

CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id CHAR(36) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
    title VARCHAR(255) NOT NULL,
    body TEXT,
    published BOOLEAN DEFAULT FALSE,
    created DATETIME,
    modified DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

Note: Specify CHARACTER SET and COLLATE for user_id only if the table CHARACTER SET and COLLATE of the table differ from users.id (than may happen running migrations). They must match.

Roles will be dynamic: admin will be allowed to manage them. That means that they has to be stored in a table.

CREATE TABLE roles (
    id CHAR(36) NOT NULL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    created DATETIME,
    modified DATETIME
);

Association between users and roles bill be belongsTo, so we’ll need a foreign key in the users table instead of a role varchar field:

ALTER TABLE users
    ADD role_id CHAR(36) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER role,
    ADD INDEX role_id (role_id),
    ADD FOREIGN KEY (role_id) REFERENCES roles(id);

ALTER TABLE users
    DROP role;

Baking

Time to think about what will be ACOs and AROs. In most cases, Users will be the only AROs. To do that, we need to link the Users entity and table to the ACL plugin. In this case that we are using CakeDC/users plugin, we first need to extend the plugin as it is explained in the docs. We will also add the behavior and parentNode() as shown in the cakephp/acl readme file, so at the end we’ll need to create those files:

src/Model/Entity/MyUser.php:

<?php
namespace App\Model\Entity;

use CakeDC\Users\Model\Entity\User;

/**
 * Application specific User Entity with non plugin conform field(s)
 */
class MyUser extends User
{
    public function parentNode() {
        return ['Roles' => ['id' => $this->role_id]];
    }
}

src/Model/Table/MyUsersTable.php:

<?php
namespace App\Model\Table;

use CakeDC\Users\Model\Table\UsersTable;

class MyUsersTable extends UsersTable
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->addBehavior('Acl.Acl', ['requester']);
        
        $this->belongsTo('Roles');
        $this->hasMany('Articles');
    }

}

Run bin/cake bake controller MyUsers (beware of case)

Then, edit the top of src/Controller/MyUsersController.php as:

<?php
namespace App\Controller;

use App\Controller\AppController;
use CakeDC\Users\Controller\Traits\LinkSocialTrait;
use CakeDC\Users\Controller\Traits\LoginTrait;
use CakeDC\Users\Controller\Traits\ProfileTrait;
use CakeDC\Users\Controller\Traits\ReCaptchaTrait;
use CakeDC\Users\Controller\Traits\RegisterTrait;
use CakeDC\Users\Controller\Traits\SimpleCrudTrait;
use CakeDC\Users\Controller\Traits\SocialTrait;

class MyUsersController extends AppController
{
    use LinkSocialTrait;
    use LoginTrait;
    use ProfileTrait;
    use ReCaptchaTrait;
    use RegisterTrait;
    use SimpleCrudTrait;
    use SocialTrait;
    
    // CRUD methods ...

To generate the template files for MyUsers we can run:

bin/cake bake template MyUsers

Next, just let Cake bake all objects for articles and roles:

bin/cake bake all Articles
bin/cake bake all Roles

Add behavior to their tables. ArticlesTable will act as controlled because it will represent ACOs:

class ArticlesTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);
        
        // (...)
        $this->addBehavior('Acl.Acl', ['controlled']);
        // (...)

The case of RolesTable will be similar but it will act as requester, as it will represent AROs:

class RolesTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);
        
        // (...)
        $this->addBehavior('Acl.Acl', ['requester']);
        // (...)

Create the parentNode() method in both entities: Article and Role.

    public function parentNode() {
        return null;
    }

Testing

Ok, time to test the whole system! At this point, the app should be ready to use. At least, for an administrator. Let’s quickly create one: it is as easy as running bin/cake users add_superuser. New credentials will appear on screen.

When accessing our app in the URL that we installed it, a login form will appear. Log as the just created admin.

First, let’s create some roles. Go to /roles in your app’s URL. Then, click on "New Role". Create the roles:

  • Author
  • Editor
  • Reader

Then, we can create two users an author and a reader. Head to /my-users and add them. Remember to select the Active checkbox and the proper role in the dropdown menu.

Because MyUsers has the AclBehavior, AROs has been automatically created while creating users, along with the created roles. Check it out with bin/cake acl view aro

Aro tree:
---------------------------------------------------------------
  [1] Roles.24c5646d-133d-496d-846b-af951ddc60f3
    [4] MyUsers.7c1ba036-f04b-4f7b-bc91-b468aa0b7c55
  [2] Roles.5b221256-0ca8-4021-b262-c6d279f192ad
  [3] Roles.25908824-15e7-4693-b340-238973f77b59
    [5] MyUsers.f512fcbe-af31-49ab-a5f6-94d25189dc78
---------------------------------------------------------------

Imagine that we decided that authors will be able to write new articles and readers will be able to view them. First, let’s create the root node for all controllers:

bin/cake acl create aco root controllers

Then, let’s inform ACL that there are such things as articles:

bin/cake acl create aco controllers Articles

Now, we will tell that there are 5 actions related to Articles:

bin/cake acl create aco Articles index

bin/cake acl create aco Articles view

bin/cake acl create aco Articles add

bin/cake acl create aco Articles edit

bin/cake acl create aco Articles delete

We can see the first branch of the ACOs tree here:

bin/cake acl view aco

Aco tree:
---------------------------------------------------------------
  [1] controllers
    [2] Articles
      [3] index
      [4] view
      [5] add
      [6] edit
      [7] delete
---------------------------------------------------------------

ACL knows that articles can be added, so let’s tell who can do that. We can check which aro.id belongs to role Author with:

mysql> select id from roles where name like 'Author';
+--------------------------------------+
| id                                   |
+--------------------------------------+
| 24c5646d-133d-496d-846b-af951ddc60f3 |
+--------------------------------------+
1 row in set (0.00 sec)

And the same with the Reader role::

mysql> select id from roles where name like 'Reader';
+--------------------------------------+
| id                                   |
+--------------------------------------+
| 25908824-15e7-4693-b340-238973f77b59 |
+--------------------------------------+
1 row in set (0.00 sec)

So, if we look up this id in the bin/cake acl view aro output, it turns out that aro.id 1 is Author and that aro.id 3 is Reader.

If we want to let authors (ARO 1) add articles (ACO 5), we must grant permission to Articles/add to editors by running:

bin/cake acl grant 1 5

And we'll grant readers (ARO 3) view articles (ACO 4) with:

bin/cake acl grant 3 4

Don't forget to grant access to Articles/index for all roles, or nobody would access /articles:

bin/cake acl grant 1 3

bin/cake acl grant 2 3

bin/cake acl grant 3 3

Note: Obviously, it would be easier to set a "super role" which includes the 3 roles and grant access to index to it, but we don't want to add too many steps in this tutorial. You can try it for yourself.

Then, aros_acos table becomes:

mysql> select * from aros_acos;
+----+--------+--------+---------+-------+---------+---------+
| id | aro_id | aco_id | _create | _read | _update | _delete |
+----+--------+--------+---------+-------+---------+---------+
|  1 |      1 |      5 | 1       | 1     | 1       | 1       |
|  2 |      3 |      4 | 1       | 1     | 1       | 1       |
|  3 |      1 |      3 | 1       | 1     | 1       | 1       |
|  4 |      2 |      3 | 1       | 1     | 1       | 1       |
|  5 |      3 |      3 | 1       | 1     | 1       | 1       |
+----+--------+--------+---------+-------+---------+---------+
5 rows in set (0.00 sec)

Let’s create a new article as the first user. To do that:

  • Log out (we are still logged in as superadmin) going to /logout
  • Log in as the first created user
  • Go to /articles
  • Create an article

Right now, author can add an article but not view it, since we only set the add permission. Check it out clicking in View next to the article.

Log in as a reader to check how the reader can really view the article.

Obviously, more than a couple of permissions have to be grant in a big app. This tutorial served just as an example to start.

Last words

That's all for now related to the use of ACL in a webapp made with CakePHP. A lot more can be done with ACL. Next step would be to use CrudAuthorize to specify which CRUD permissions are granted for any ARO to any ACO.

Keep visiting the blog for new articles!

This tutorial has been tested with:

  • CakePHP 3.5.10
  • CakeDC/users 6.0.0
  • cakephp/acl 0.2.6

An example app with the steps followed in this tutorial is available in this GitHub repo.

Please let us know if you use it, we are always improving on them - And happy to get issues and pull requests for our open source plugins. As part of our open source work in CakeDC, we maintain many open source plugins as well as contribute to the CakePHP Community.

Reference

Latest articles

When and why should you upgrade to CakePHP 5?

CakePHP 5.0.0 was released on September 10th. The current version as of today is 5.0.3 (released Nov 28th and compatible with PHP 8.3 https://github.com/cakephp/cakephp/releases/tag/5.0.3). You might be asking yourself some questions related to the upgrade… here's what we've been recommending to our clients to do since version 5 was released. Leaving aside the obvious reasons for an upgrade, today we're going to categorize the decision from 2 different points of view: Your current CakePHP version, and your role in the project.

When should you upgrade? 

  We are going to use current CakePHP version as the main criteria: * If you are in CakePHP <= 2   * We strongly recommend an upgrade as soon as possible. If you are unable to upgrade, try to keep your PHP version and all the underlying dependencies as fresh as you can and isolate the application as much as possible. If your application is internal, consider using a VPN blocking all outside traffic. If your site is open to the public, consider using an isolated environment, hardened. Adding a web application firewall and a strict set of rules could also help to mitigate potential security issues. Even if CakePHP is very secure, the older versions of CakePHP, like  1 and 2  have a very old code base , and other vendors/ libraries could be a serious security risk for your project at this point.   * If you are in CakePHP 3.x   * The effort to upgrade at least to CakePHP 4.x should not be a blocker. We would recommend upgrading at least to the latest CakePHP 4.5.x. You can actually "ignore" the deprecations for now, you don't need to plan for upgrading your authentication/authorization layers just yet, focus on getting your project stable and up to CakePHP 4.5.x in the first round.   * If you are in CakePHP 4.x   * Upgrading to CakePHP 5.x is not an immediate priority for you.   * I would say, 2024 is a good time to start planning for an upgrade. Feature and bugfix releases for 4.x will continue until September 2025. Security fixes will continue for 4.x until September 2026. You have plenty of time to consider an upgrade, and take advantage of newer (and faster!) PHP versions.  

Why should you upgrade? 

  We are going to use your role in the project to provide some good reasons: * If you are a developer   * More strict types, meaning better IDE support and more errors catched at development time.   * New features in CakePHP 5.x will make your code more readable, like Typed finder parameters https://book.cakephp.org/5/en/appendices/5-0-migration-guide.html#typed-finder-parameters      * Quality of life features, reducing development time like https://book.cakephp.org/5/en/appendices/5-0-migration-guide.html#plugin-installer   * Compatibility with PHP 8.3 for extra performance & support   * If you are a manager   * Ensure your development team is forced to drop old auth code and embrace the new authentication/authorization layer https://book.cakephp.org/5/en/appendices/5-0-migration-guide.html#auth   * The new authentication layer will allow you to easily integrate features like single sign on, two factor authentication or hardware keys (like Yubikeys), as there are plugins available handling all these features.   * Get an extended support window. CakePHP is one of the longest maintained frameworks out there, upgrading to CakePHP 5 will keep your core maintained past 2026.   * Upgrade to PHP 8.3 and force legacy vendors to be up to date with the new version, this will also push your team to get familiar with the new PHP core features.   * If you are an investor, not directly related with the project day-to-day operations   * Secure your inversion for a longer period.   * Reduce your exposure to security issues.   * Send a strong message to your partners, keeping your product updated with the latest technology trends.   * Send a strong message to your team, investing in the upgrade of your application will let them know the project is aiming for a long term future.   In conclusion, upgrading to CakePHP 5 is a good move for 2024 whether you're a developer, manager, or investor. The version 5 is stable and ready to go. Staying current becomes not just a best practice but a strategic advantage.   If you are in doubt, feel free to contact us. We'll review your case (for free) and provide an actionable recommendation based on your current situation in the next business day.  

A quick CakePHP Local environment with DDEV

In the realm of web development, a seamless local environment is the bedrock for efficient and stress-free coding. Enter DDEV, a powerful tool that simplifies the setup process and empowers developers to dive into their projects with ease. In this blog post, we'll embark on a journey to demystify the process of setting up a local development environment using DDEV. Whether you're a seasoned developer or just starting in the world of web development, optimizing your local environment can significantly enhance your workflow.

Pre Conditions :

Install Docker https://docs.docker.com/get-docker/ and install DDEV https://ddev.readthedocs.io/en/stable/

Step 1: Create a new CakePHP project skeleton 

composer create-project cakephp/app myproject A new folder "myproject" will be created with a CakePHP project skeleton inside. Go to this new directory and proceed with the following instructions.

Step 2: Initial ddev setup

Run ddev config
This will do the initial ddev setup, press enter for all questions.  Run ddev auth ssh
This will add ssh key authentication to the ddev-ssh-auth container

Step 3: Adjust the settings

Inside "myproject" a new .ddev folder will be created, open config.yaml  and adjust there: php version, database and the database url environment.  For PHP:
php_version: "8.1"

For the database: database: type: mysql version: "8.0" For the environment variable: web_environment: - DATABASE_URL=mysql://db:db@db/db

Step 4: Start ddev

ddev start  This will spin up the project.

Step 5: Open your application

ddev launch This will open your project in a browser.   Once you have the application up and running, some useful commands you could run are:
  • ddev composer to execure composer
  • ddev mysql to get into the database
  • ddev ssh takes you into the web container.
In this link https://ddev.readthedocs.io/en/latest/users/usage/cli/ you can see more useful commands.   Hope you enjoy playing with DDEV!

 

   

 

 

CakeFest 2023 Recap

CakeFest 2023 once again brought together developers from around the world for a weekend of baking (code), insightful talks, and community building. This year's event, featuring speakers from eight different countries and attendees joining both in person and via live streaming, was a refreshing convergence of passion and technology.

International Flavor:

One of the highlights of CakeFest 2023 was its international reach. Speakers from the United States, Canada, the Netherlands, Austria, Germany, Brazil, and Spain graced the virtual and physical stages, sharing experiences in the world of CakePHP and software development. This lineup added a diverse perspective to the event's discussions.  

A Weekend of Learning and Networking:

CakeFest attendees enjoyed a weekend filled with learning opportunities, covering things from beginner workshops to cutting-edge software development trends, to the future of CakePHP. Workshops, panels, and presentations provided valuable insights and knowledge of the framework. We hope that everyone had the chance to expand their skills, connect with other developers, and forge new professional relationships. When CakeFest wasn't in session, the core/CakePHP team and attendees spent a lot of time getting to know each other… Groups went to lunch and dinner every day of the event. This is probably my favorite part of the physical conferences.    

Baking and Code:

Day 1 consisted of 2 full workshops from Jorge González and lead core developer Mark Story. The third workshop presented by Kevin Pfeifer was included in day 2’s hybrid model. Followed by talks from: John Killcommons (keynote) of Zulucare/Zulucloud, Rafael Queiroz (Github actions for beginners and applied to CakePHP basic projects), Celso Fontes (PGE Digital, a successful CakePHP project in Rio de Janeiro's Attorney), and Andres Campanario (Integration of inertiajs on CakePHP to bake CRUD SPA).    Day 3’s speakers included: Remy Bertot (keynote) of Passbolt, Mark Scherer  (How to use your IDE effectively for CakePHP), Alejandro Ibarra (Unveiling the Ultimate Showdown: A Comparative Analysis of Local Development Tools), Stefan Koopmanschap (Domain-Driven Design: The Basics), Umer Salman (Agile Deployment of CakePHP Web Applications in a Hybrid Kubernetes Cluster), and Wim Godden (Websockets as the glue to interactivity).    It was a weekend worth remembering for sure. The unique blend of networking and code at CakeFest allowed attendees to see just how far CakePHP has come, and will go, plus some pretty cool things built with the framework.     

The Cake Ceremony:

No CakeFest would be complete without the much-anticipated cake ceremony. We hope that those attending virtually were able to enjoy some with us. In LA, we carried on the tradition of allowing Mark Story to be the “cutter of the cake”. Now that I think about it, he never asks to cut the cake, but it’s a honor nonetheless. 

   

Conclusion:

CakeFest 2023 was a celebration of passion, knowledge, and creativity. With its global reach, experienced speaker lineup, and faithful community, we believe that the event left attendees inspired and eager to continue their works using Cake. We also learned about all of the cool places that CakePHP is being utilized, from the attorney general's office in Brazil, all the way to nasa using some CakePHP in space (more or less). We also heard from some of our CakePHP core developers - specifically their thoughts on CakePHP 5 and what’s to come. You can see a lot of photos on Facebook and Twitter and the edited presentation videos will be posted to YouTube soon. We are working on getting slides uploaded into the CakeFest site as we speak (or type).    Someone said to me that there's a little bit of magic in every slice of cake and every line of code… I think that pretty well sums up our great weekend at CakeFest.

We Bake with CakePHP