What is the OAA provider object?¶
The provider object passed to hook functions is the in-memory graph of all OAA objects
loaded during the current run. It holds every user, group, role, resource, and employee
that has been created by the sources up to that point, keyed by their unique_id.
Hooks receive it via the provider keyword argument:
@record_transform_non_stream(target='roles')
def assign_roles(record, provider=None, **kwargs):
user = provider.local_users.get(record.user_id)
Attribute reference¶
Attribute |
Type |
Used when |
|---|---|---|
|
|
Custom App: look up users by |
|
|
Custom App: look up groups by |
|
|
Custom App: look up roles by |
|
|
Resource-scoped permissions: look up resources by |
|
|
HRIS destination: look up employees by |
|
|
IdP destination: look up identity users |
|
|
IdP destination: look up identity groups |
All dicts use the object’s unique_id as the key. .get(id) returns None if the ID
is not found — always check before using the result.
Common methods on loaded objects¶
LocalUser:
user.add_group('group-id')
user.add_role('role-id', apply_to_application=True)
user.add_role('role-id', resources=[resource], apply_to_application=False)
LocalGroup:
group.add_role('role-id', apply_to_application=True)
IdentityGroup (IdP):
group.add_user('identity@example.com')
LocalUser Built-in Field Mapping Reference¶
When creating field mappings in config.yaml, it’s critical to distinguish between
built-in LocalUser fields (which do NOT use custom_type) and custom properties
(which require custom_type: STRING/NUMBER/BOOLEAN).
Built-in LocalUser Fields¶
These fields are built-in to the OAA LocalUser model and have special handling. They
should NEVER use custom_type in field mappings.
destination_field |
Required? |
Notes |
Example |
|---|---|---|---|
unique_id |
✓ Yes |
Unique identifier for the user (primary key). Must be unique across all users. |
|
name |
— |
Display name. Use for full name. Do NOT use |
|
— |
Email address. Use for the email field. Do NOT use |
|
|
is_active |
— |
Boolean: |
|
user_type |
— |
Enum: |
|
last_login_at |
— |
ISO 8601 timestamp of last login. Use |
|
created_at |
— |
ISO 8601 timestamp when user was created. |
|
deactivated_at |
— |
ISO 8601 timestamp when user was deactivated/deleted. |
|
password_last_changed_at |
— |
ISO 8601 timestamp of last password change. |
|
Fields That Are NOT Built-in¶
These fields do not exist as built-in LocalUser attributes. Do NOT attempt to map
them directly. Instead, use custom fields or combine into name:
Field Name |
Why NOT built-in |
Solution |
|---|---|---|
first_name |
Not a built-in attribute |
Use |
last_name |
Not a built-in attribute |
Use |
identity |
Not a built-in attribute; confusing name |
Use |
identities |
Not a built-in attribute |
Use |
manager_id |
Use relationships instead |
Use PRE_PUSH_OAA hook: |
Correct vs. Incorrect Examples¶
Incorrect (using non-existent built-in fields):
field_mapping:
first_name:
destination_field: first_name # ❌ Not a built-in field
last_name:
destination_field: last_name # ❌ Not a built-in field
login:
destination_field: identity # ❌ Not a built-in field; confusing
pwd_change_date:
destination_field: last_login # ❌ Wrong field name (last_login_at)
Correct (using built-in fields properly):
field_mapping:
user_id:
destination_field: unique_id # ✓ Built-in, required
email_addr:
destination_field: email # ✓ Built-in
full_name:
destination_field: name # ✓ Built-in; use for full name
last_login_time:
destination_field: last_login_at # ✓ Built-in, correct name
transforms:
- name: date_convert
is_disabled:
destination_field: is_active # ✓ Built-in boolean
transforms:
- name: jinja_render
parameters:
template: "{{not row.is_disabled}}"
Correct (using custom fields for non-built-in data):
field_mapping:
user_id:
destination_field: unique_id
email:
destination_field: email
first_name:
destination_field: first_name # ✓ Custom field
custom_type: STRING
last_name:
destination_field: last_name # ✓ Custom field
custom_type: STRING
department_code:
destination_field: department # ✓ Custom field
custom_type: STRING
employee_start_date:
destination_field: hire_date # ✓ Custom field
custom_type: TIMESTAMP
transforms:
- name: date_convert
Timestamp Fields — Special Handling¶
Built-in timestamp fields (last_login_at, created_at, deactivated_at,
password_last_changed_at) must be either valid RFC3339 format or NULL.
Empty strings are rejected by Veza.
See What are Veza Custom Properties? for cleanup patterns.
For Other Entity Types¶
For LocalGroup, LocalRole, Employee, and other entity types, refer to the Veza Developer Documentation for built-in field lists.
Notes¶
All attributes are populated by the time
record_transforms_non_streamhooks run. oaa-runner loads all source records before running post-load hooks.provider.local_groupsholds Custom App groups (created bydestination: LocalGroupsources).provider.groupsholds IdP groups (created bydestination: IdpGroupsources). They are separate dicts even in multi-destination configs.Setting a field directly on a loaded object is also valid:
record.some_custom_field = 'value'