# Identity Access Manager
## Introduction
The API monolith is shipped with its own identity access manager (IAM) to support authenticated user privileges.
## Object model
### User
The User class represents a user account that can connect to the Wynd API to be authenticated with a set of responsibilty. This document doesn’t redefine what the User is. It exists independently of the IAM feature but as the object is strongly interconnected with the Group notion we have to mention it here.
To be able to manage any kind of authenticated user the IAM only know about `AuthUser` without consideration for the underlying user kind.
### Group
The `Group` class represents a group of users with the same roles available for the same entities. The Group is the main entry point of the IAM system, as it knows the following information:
* the users in it
* the roles associated to its users
* the entities onto which the privileges applies (not yet)
For example, the Group “Paris West Administrators” will contain all the users for the shops located in “Paris West”, with the role “Shop Administrator”.
The Group “Paris East Administrators” will contain all the users for the shops located in “Paris East”, with the role “Shop Administrator”.
The `Group` class should bear the responsibility of the Group - User, Group - Entity, and Group - Role relationships. If a Group is deleted, none of the related objects should be deleted, only the relations. (i.e. deleting a Group does not delete its users, entities, or roles)
### Role
The `Role` class represents a set of privileges, i.e. it contains all the privileges that can be applied to a group of users.
The Role class should not bear the responsibility of the Role - Group relationship. Deleting a Role should not delete the related groups.
### Privilege
The `Privilege` class represents the permissions that can be granted by a role. The complete list of permissions are defined by the Wynd API itself, as they are constants like `oms.order.read`, `pim.product.create`, and so on. As the IAM is from the User point of view, we’ll say “Privileges” instead of “Permissions” throughout this document.
#### Privilege hierarchy and dotted notation
A Privilege can be represented by a dotted notation following this syntax: `<domain>.<object>.<permission>`
##### Privilege hierarchy
Privileges are hierarchical, with 3 levels:
* `domain`: the functional domain on which the privileges applies, e.g. `pim`, `cashing`, `picking`, etc.
* `object`: on which object/resource the privileges applies, e.g. `product`, `order`, `task-list`, etc.
* `permission`: the permission granted, e.g. `edit`, `cash-in`, `finish`, etc.
When puttint every part together we get:
* `pim.product.edit`: grants the privilege to edit a product
* `cashing.order.cash-in`: grants the privilege to cash-in an order
* `picking.task-list.finish`: grants the privilege to finish a picking task
##### Dotted notation syntax
This notation uses the dot to separate each part of the privilege, where `domain`, `object`, and `permission` are composed of alphanumeric characters. For more information about the syntaxic rules, please refers to the `\WyndApi\WyndApiCoreBundle\ValueObject\Iam\Privilege` implementation.
Valid examples include:
* `catalog.product.publish`
* `users.group.add-user`
* `o2p.payment-method.manage`
Invalid examples include:
* `users.group.add user`: space is not a valid character
* `users.group.remove_user`: underscore is not a valid character
* `users.group.3something`: identifier must not start with a DIGIT
### Implementation
#### IdentityAccessManager service
This is the only services that should be accessed from outside of the IAM system.
It's API should be updated in case a new need arises instead of relying on the other services in order to keep a clean isolation.
It exposes a method `IdentityAccessManager::hasPrivilege(AuthUserInterface, Privilege): bool` that allows to check if a User is granted a given privilege.
#### Repositories
By default Groups, Roles and Privileges are stored in database. A repository exist for each of these in order to fetch the corresponding data:
* `GroupRepositoryInterface`
* `RoleRepositoryInterface`
* `PrivilegeRepositoryInterface`
They are the extension points that a project will need to modify in order to change from where any of the data is retrieved, for example if a project uses an external identity provider.
#### Privileges
The `Privilege` class is a value object allowing to manipulate a privilege through its notation.
It's worth noticing that even if receiving a Privilege object guarantee that its notation is valid it does not guarantee that the Privilege is known by the application and handled by a voter.
#### Voters
To validate if the current user is granted a privilege we use the [voter system](https://symfony.com/doc/current/security/voters.html) from Symfony.
In addition the voters are also used to provide the privileges it's responsible for.
Any voter meant to be used with the IAM must implement the interface `IamVoterInterface` in order to be recognized, this interface only provide one method that must return the list of privileges handled by the voter.
A default implementation called `IamVoter` is avaiable and will grant access if the user owns the provided Privilege.
If a Privilege has a more complex business rule you can either extend the `IamVoter` or implement the `IamVoterInterface`.
To ease the construction of Privilege objects voters are also used as factory for the Privileges they handle. For instance calling `RoleVoter::addPrivilege()` will return a Privilege corresponding to the notation `iam.role.add-privilege`.
It's a best practice that we strongly advise in order to avoid duplicating the notation of the Privileges.
## Translation
### Privileges
In order to allow a consumer to print the Privileges in a way that can be understand for a human we want to provide a `label` and a `description` for each part of a Privilege (i.e. `domain`, `object` and `permission`).
To translate this information we use the [translation system](https://symfony.com/doc/current/translation.html) from Symfony, with a domain named `privileges`.
Each translation file is responsible for defining the translation of every `domain`, `object` and `permission`, the structure is as follow (words between `<` and `>` have to be replaced):
```yaml
<domain>:
_label: "Domain's label"
_description: "Domain's description"
<object>:
_label: "Object's label"
_descripiton: "Object's description"
<permission>:
_label: "Permission's label"
_description: "Permission's description"
```
The `_label` and `_description` are prefixed with and underscore to avoid any collision, for instance with a Privilege `pim.label.create`.
You can find some more meaningful examples in the translation files of the core bundle.
## Use cases
### Creating a new voter
To create a new voter that only check if a User is granted a Privilege:
```php
<?php
namespace WyndApi\WyndAcmeBundle\Security\Voter;
use WyndApi\WyndApiCoreBundle\Security\Voter\IamVoter;
use WyndApi\WyndApiCoreBundle\ValueObject\Iam\Privilege;
final class DummyVoter extends IamVoter
{
public static function doSomething(): Privilege
{
return self::createPrivilege('do-something');
}
/**
* {@inheritdoc}
*/
public static function getHandledPrivileges(): array
{
return [
self::doSomething(),
// List here the Privilege objects handled by your voter
];
}
protected static function createPrivilege(string $permission): Privilege
{
return Privilege::fromNotation("my-domain.dummy.$permission");
}
}
```
If you have a complex business rule to implement in order to grant access you only have to override the `IamVoterInterface::voteOnAttribute()` method:
```php
<?php
// ...
final class DummyVoter extends IamVoter
{
// .. privileges declaration ...
/**
* @param Privilege $attribute
* @param Dummy $subject
*
* @return bool
*/
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
if (!parent::voteOnAttribute($attribute, $subject, $token)) {
return false;
}
// additional logic here ...
}
}
```
### Adding a new Privilege
Adding a Privilege is as simple as registering it in a voter.
As soon as a Privilege is returned from a voters method `getHandledPrivileges()` it's known by the application and must be handled by the voter declaring it.
**Important** Don't forget to add the necessary [translations](#Translation).
### Restricting access in a controller
In our controller we have access to a helper method to deny access unless a Privilege is granted to the current user:
```php
<?php
namespace WyndApi\WyndAcmeBundle\Controller;
use WyndApi\WyndApiCoreBundle\Controller\AbstractController;
final class MyController extends AbstractController
{
function myAction($resource): Response
{
// The resource is optional
$this->denyAccessUnlessGranted(MyVoter::privilege(), $resource);
// ...
}
}
```
For more detailed examples you can check the controllers of the IAM.