TALY comes with an ACL (Access Control List) implementation for the frontend. It's loosely based on the XACML Standard. ACL is one of the main pillars of TALY as it enables you to write flexible Building Blocks that can be heavily adjusted in generated journeys.
The idea of ACL is to enable developers to write very flexible Building Blocks that can be adjusted to a wide variety of applications' needs. It allows to control the visibility and editability of parts in frontend applications. The following subsections briefly explain the core concepts of ACL.
Everything is a "Resource". Resources are the things that make ACL work. You can think of resources as all the little things (mostly the visual ones) that make up a component (and thus a Building Block). A resource could be the headline of a Building Block or the first-name input of a form or anything else. Each resource is identified by a unique hierarchical path. Due to this hierarchical approach, two Building Blocks might very well both contain a resource with the same name (e.g. headline
). When used in an application, these resources would be identified by their full ACL paths like page-a/header-building-block/headline
or page-c/offerings/headline
.
A Condition allows to restrict an ACL rule to be active only in certain situations, depending for example on the state of the app.
Conditions are evaluated using the library filtrex
. Please refer to their documentation for details on how to write conditions. In addition to what filtrex
provides out of the box, you can use the function s()
to use values from the application state in your conditions.
By default every resource is visible
and editable
. Depending on the ACL configuration, a resource can be in (m)any of these states:
readonly
disabled
hidden
Take a look at the Example Policies at the bottom of this page for details.
The configuration for all resources is made up of "Rules". Each rule consists of a target resource, a condition and a desired state for that resource. A rule is written in plain text as a comma-separated-value in the following format:
<resource>, <condition>, <state>
Real life examples could look as simple as this (note the empty condition):
*/headline, , hidden
or could get as complex as this:
*/injured-driver-details/questions/*, s("$['expert-driver-selection'].selectedDriver") == "ANOTHER_DRIVER" and s("$['driver-details'].driver.party.partyType") == "INCOMPLETE_PERSON", hidden
The entirety of the rules that describe how resources need to behave is called the "Policy". Each application is expected to provide this in a file called policy.txt
. The sequence of the rules is important. It follows a top-to-bottom precedence. The rules that are placed above can overrule the below lines.
The ACL "engine" drives the ACL system. This engine receives an app's Policy and takes care of validating the rules' conditions to dynamically adjust the applied rules at run-time.
The ACL package is not tied to any other package in the library, making it easy to use in any Angular project.
ACL can be found in the @allianz/taly-acl
package, which contains an engine for parsing and evaluating policies against incoming requests to grant or deny access. The @allianz/taly-acl/angular
entry point provides all the necessary resources for implementing ACL in any Angular project.
If you are generating a TALY journey inside a worskpace, installing the package is the only step required to set up ACL. For journey generation outside of workspaces, no setup is needed.
In a non-generated application, you have to manually integrate the ACL-related modules, services, and providers into the AppModule
.
🧩 An example setup can be found here.
import {
ACL_POLICY_CONTENT_TOKEN,
AclInspectorService,
AclModule,
AclService
} from '@allianz/taly-acl/angular';
import { InputElementInjectorModule } from '@allianz/taly-acl/input-element-injector-directive';
@NgModule({
imports: [AclModule, InputElementInjectorModule],
providers: [
{
provide: ACL_POLICY_CONTENT_TOKEN,
useValue: POLICY_CONTENT
},
AclService,
AclInspectorService
]
})
export class AppModule {}
Optionally, you can write ACL conditions based on the application state. In order to do so, you can provide an implementation for an adapter via the ACL_STORE_ADAPTER_TOKEN
token.
For PFE applications, you can use the PfeAclExpressionAdapter
from the @allianz/taly-pfe-connector
package. In other cases, you can provide your own adapter implementation.
import { ACL_STORE_ADAPTER_TOKEN } from '@allianz/taly-acl/angular';
import { PfeAclExpressionAdapter } from '@allianz/taly-pfe-connector';
@NgModule({
providers: [
{
provide: ACL_STORE_ADAPTER_TOKEN,
useClass: PfeAclExpressionAdapter
}
]
})
export class AppModule {}
ACL was created as a means of ensuring that Building Blocks are configurable, which is particularly important when sharing these components with others. If a Building Block is only used within your own application and ACL is not necessary, then it can be omitted. However, if a Building Block is published in a library, such as those included in the ITMP documentation, then it's important to include ACL functionality so that others can configure the Building Block in their journeys.
To facilitate the consumption of ACL in Building Blocks, it is necessary to add a <building-block-name>.acl.yml
file to your Building Block folder (more info below). This file should list all the ACL resources defined in the Building Block.
*aclTag
Structural DirectiveElements in the Building Block's template can be marked as a resource by adding the *aclTag="'resource-name'"
structural directive to these elements. Such elements can then be hidden or shown using the hidden
or visible
state. In addition, form element resources can be disabled
or marked as readonly
. Take a look at the Forms section for details.
The *aclTag
directive is provided in the TalyCoreModule
that can be imported from @allianz/taly-core
and is meant to be imported in your Building Block's module. The *aclTag
directive accepts a string as value and can be used to mark an element in a component's template as an ACL resource. Elements with this directive can be targeted using ACL rules.
Example:
<h1 *aclTag="'headline'">This is my headline</h1>
ACL only supports Reactive Forms. By using the autoFormBindingFactory
method from @allianz/taly-acl/form-support
, you can conveniently enable ACL support for your form controls:
// Define `bindAclWithForm` before your Building Block component class
const bindAclWithForm = autoFormBindingFactory();
// Inside your Building Block component class
form = new FormGroup({
nested: new FormGroup({
myControl: new FormControl('')
})
});
this.aclFormBinding = autoFormBindingFactory()(this.acl, this.form);
this.aclFormBinding.stream$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
💡 Everything in this snippet is automatically generated when you use the Building Block generator.
Apart from that, a nested form control path like nested.myControl
will be expected by TALY to correspond to an ACL resource path nested/my-control
. There are 2 crucial points to note here:
myControl
-> my-control
.This is what a template including *aclTag
directives for the above form could look like to support the automatic form binding:
<div [formGroup]="formGroup">
<div *aclTag="'nested'" [formGroup]="nested">
<input *aclTag="'my-control'" formControlName="myControl" />
</div>
</div>
In some cases, not all form elements need to be bound to ACL, so we have included the option to exclude certain ACL paths from the binding process. This is particularly useful when using FormArray
with direct FormControls
as children, as only FormGroups
are supported, and console warnings may appear.
const formGroup = new FormGroup({
control1: new FormControl(),
group1: new FormGroup({
nestedControl1: new FormControl(),
nestedControl2: new FormControl()
}),
contactNumbers: new FormArray([new FormControl(), new FormControl()])
});
const bindAclWithForm = autoFormBindingFactory({
excludes: ['contactNumbers.0', 'contactNumbers.1']
});
bindAclWithForm(formGroup);
To enable ACL control over a form control or nested forms, a corresponding ACL tag must be added to the HTML template, following a similar structure to the DOM hierarchy. However, this structure prevents adding arbitrary ACL-enabled templates in between without affecting the ACL hierarchy. To address this limitation, transient ACL tags can be used. Transient ACL tags are invisible to nested ACL keys but still inherit parent ACL tags. They can be defined by setting the transient
boolean input to true
in the *aclTag
directive.
⚠️ It's important to use this advanced functionality sparingly to avoid creating an ACL hierarchy that doesn't follow the natural DOM structure, making it more difficult to maintain. Additionally, developers should ensure that ACL hierarchies are created correctly to avoid potential bugs in running applications.
Example:
<h1 *aclTag="'title'"></h1>
<form [formGroup]="formGroup">
<div formGroupName="person" *aclTag="'person'">
<ng-container *aclTag="'person-group'; transient: true;">
<input
type="text"
formControlName="firstName"
placeholder="First name"
*aclTag="'first-name'"
/>
<input type="text" formControlName="lastName" placeholder="Last name" *aclTag="'last-name'" />
<input type="file" formControlName="avatarImage" *aclTag="'avatar-image'" />
</ng-container>
</div>
</form>
With person-group
set as transient, the form structure is still respected in the ACL structure. person-group
does not become part of the ACL paths corresponding to the form controls, so any ACL rule applied to person/person-group
won't have any effect on the form controls:
// ACL hierarchy
title
person/person-group // transient: true
person/first-name
person/name-name
person/avatar-image
acl.yml
fileAll ACL resources of a Building Block need to be specified in a file called <bb-name>.acl.yml
that is stored and published alongside the Building Block sources. This file is needed to allow app generation through the TALY generators and executors which need to be aware of the available ACL resources in any given Building Block.
For each ACL resource there needs to be an entry in the corresponding *.acl.yml
file. Each entry needs to state
aclKey
display
for resources that can be shown/hidden but are not editable or form-control
for resources that can also be enabled/disabled and/or marked as readonlylabel
description
default
with possible values of readonly
, disabled
, or hidden
label
and description
can be used in a Building Block library documentation to explain what this resource is about. The default
can be used to state the default state of a resource. This allows to create opt-in resources that need to be shown/enabled explicitly.
Here is an example of such a file for a building block with the resources title
, title/icon
, message
, and input
:
- aclKey: title
aclType: display
label: Title
description: The title of this building block
- aclKey: title/icon
aclType: display
label: Title icon
description: The icon shown next to the title (hidden by default)
default: hidden
- aclKey: message
aclType: display
label: Message
description: The message of this building block
- aclKey: input
aclType: form-control
label: Input
description: The input of this building block
So far this file needs to be maintained manually so as a Building Block developer it is your responsibility to make sure that this file is in sync with the Building Block's ACL resources.
The ACL inspector is a dashboard that can be part of a generated TALY journey during development and for debugging purposes. To add the ACL inspector to the generated journey, a specific option needs to be provided in the journey generation setup (see here and here).
The ACL inspector includes a fixed button in the upper right corner, which expands into a floating window that can be dragged and resized. This window provides a set of tabs with different functionalities, which are explained in the following subsections.
The Policy Editor allows developers to verify and debug policies by disabling individual rules, changing resource or state values, and editing conditions.
The editor enables CRUD operations on all the rules from your policy file, as well as the ability to toggle those rules, manually refresh the policy, and copy updated policies for storage.
In addition, some Building Blocks define "default" ACL rules. These appear in the editor at the very end of the list of rules. They are read-only and can't be moved. If needed, they can be overridden by other rules, placed above them. Take a look at the acl.yml
file section for details on where the "default" ACL rules are coming from and why they exist.
In the case of TALY-generated journeys, if the environment variable PERSIST_ACL_POLICY_URL
is provided to the generation command, the policy editor will show a button to persist the policy. Clicking that button will trigger a POST request against the provided URL. The request body will contain the current policy in the officially supported format, ready to be used to generate the journey.
The reporter tab lists all Decisions, showing what ACL resources are currently present or have been evaluated in the past. It holds knowledge about all seen ACL resources, making it a useful place to fetch information for building *.acl.yml
files. However, it is important to ensure the accuracy of the list when copying it into the *.acl.yml
file, as incorrect ACL resources may cause the generation of incorrect policies.
You can optionally show a helping toolbar ("ACL Hint") for every ACL resource on a page:
There are two ways to enable these hints:
h
key anywhere on the page (this shortcut is disabled if an input element is focused):One can then conveniently click on the icons displayed on top of the resources to show or hide the corresponding resource.
Above we mentioned this regarding ACL rules in a policy:
The sequence of the rules is important. It follows a top-to-bottom precedence. The rules that are placed above can overrule the below lines.
To clarify this, here are some example policies and how they would be evaluated:
# policy.txt
my-resource, , hidden
my-other-resource, , readonly
yet-another-resource, , disabled
Result
my-resource
is always hidden
my-other-resource
is always readonly
yet-another-resource
is always disabled
# policy.txt
my-resource, , visible
my-resource, , hidden
my-other-resource, , editable
my-other-resource, , disabled
yet-another-resource, , editable
yet-another-resource, , readonly
Result
my-resource
is always visible
. The visible
rule appears before the hidden
rule and therefore overrules it.my-other-resource
is always editable
(i.e. not disabled
). The disabled
rule is overruled by the editable
rule above it.yet-another-resource
is always editable
(i.e. not readonly
). The readonly
rule is overruled by the editable
rule above it.# policy.txt
my-resource, , readonly
my-resource, , disabled
Result
my-resource
is both readonly
and disabled
. These states are not mutually exclusive. Visually and logically, the disabled
state wins and the readonly
state has no effect. To overrule the disabled
state, you have to add an editable
rule between the readonly
rule and the disabled
rule.
💡 This behavior exists mostly for historical reasons. Changing this would be a subtle but potentially breaking change for a lot of journeys. If you think this behavior is confusing and should be changed, please reach out to us.
# policy.txt
my-resource, , readonly
my-resource, , disabled
my-resource, , hidden
Result
my-resource
is always hidden
and readonly
and disabled
. Visually and logically, a hidden
resource can't be disabled
or readonly
, so the readonly
and disabled
don't have any effect in this case.
# policy.txt
my-resource, s("$.fullName") == "John Doe", editable
my-resource, "Laura" in s("$.names"), disabled
my-resource, , readonly
Result
my-resource
is readonly
if no other rule appliesmy-resource
is disabled
when "Laura"
is in the list of names at $.names
my-resource
is editable
(neither readonly
nor disabled
) if the name at $.fullName
equals "John Doe"
When a Building Block is hidden through ACL, the state of that Building Block is removed from the PFE state. However, the corresponding data will be preserved and will be restored when the Building Block becomes visible again. Any new state data that was added while the Building Block was hidden will take precedence over the restored back-up.