Symfony 8.2 New Published on

Roles by family, with wildcards

ROLE_BLOG_*: a single role_hierarchy key covers a whole family of roles, including those declared nowhere.

Topics covered: SecurityRoles

New in Symfony 8.2 (in development). A role_hierarchy reads role by role: each key is one specific role, each value what it grants. Two sections that hand out the same roles, and the same sentence is written twice; a third section, and you write it again. Symfony 8.2 accepts wildcard keys, which name a whole family.

The wildcard

The * only counts as a wildcard in two places: surrounded by underscores, or at the end of the role after an underscore. ROLE_BLOG* and ROLE_*BLOG remain ordinary role names.

security:
    role_hierarchy:
        ROLE_*:             ROLE_USER
        ROLE_*_MODERATOR:   ROLE_MODERATOR
        ROLE_BLOG_*:        ROLE_BLOG_READER

A role then accumulates what every wildcard it matches gives it, and its own key if it has one. Above all, a wildcard also applies to roles you never declared: ROLE_BLOG_ARCHIVIST, invented tomorrow, already reads the blog.

The wildcard only exists on the keys side. is_granted('ROLE_BLOG_*') still looks for an exact role, and will find nothing.

Seeing what applied

Two overlapping wildcards, and you quickly doubt what a role really receives. Symfony 8.2 answers in the console:

php bin/console debug:roles ROLE_BLOG_MODERATOR --tree
ROLE_BLOG_MODERATOR
├── ROLE_*
│   └── ROLE_USER
├── ROLE_*_MODERATOR
│   └── ROLE_MODERATOR
├── ROLE_BLOG_*
│   └── ROLE_BLOG_READER
├── ROLE_BLOG_DELETE_ARTICLE
└── ROLE_BLOG_LOCK_ARTICLE

Each branch names the key that granted what it holds. Without --tree, the command gives a flat list; without an argument, the whole hierarchy.

Your task

The magazine has two sections, the blog and the forum, and their hierarchy repeats nine times what could fit in a few lines. In config/packages/security.yaml, rewrite role_hierarchy with wildcards, without changing what anyone receives — the /roles preview shows, for each role, its effective roles.

Once the wildcards are in place, ROLE_BLOG_ARCHIVIST, which the configuration mentions nowhere, must read the blog like the others.

Where the feature comes from: the original pull request.