Released: Q3 2024
Highlights Portrait - 7.0.0
Security Improvements Notice
At Portrait, we take application security extremely seriously, which is why we continuously enhance the security features of our platform. We strive to strike a balance between automated configuration options and strict input validation, ensuring that our users can build flexible and dynamic web applications with confidence.
Please note that Portrait is designed to empower admins to create custom web applications. While we prioritize robust security, we also recognize the importance of flexibility and ease of use in our platform. As such, you may need to perform manual configuration or make informed decisions about trade-offs between security and functionality.
As an admin using Portrait to build your application, it's essential that you consider your own application's security needs and take responsibility for ensuring its security. With our latest update, we're introducing new configuration options that enable admins like you to further strengthen their application's security by adding custom input validation, permissions, conditions, and limiting form submissions to specific entries. These enhancements empower you to build applications that meet your unique needs while maintaining a high level of security.
Permissions
Sections can now be configured to be only accessible for admins.
- name: src-inventory role: ADMIN caption: Inventory
role |
If you don’t define it, role |
Admin only sections will be displayed in the administration view.
Actions 2.0
The Action inside a Section has been reworked and now support
conditional expression. see https://portrait.atlassian.net/wiki/spaces/PA7/pages/1029117068/Draft+7.0.0+-+Reisalpe#Conditions
Handlebar Support for Labels and Links.
Support to link multiple Forms within a single Section Entry
Example: So all Entries of I am logged in as user linda.jackson and therfore are only allowed to open the edit vacation form assigned to this user. ExampleActions for Links
- name: vacations
group: Demo
caption: Vacations
type: list
description: |
Übersicht der Urlaube
formId: create_vacation
icon: 'edit'
actions:
- label: 'Edit vacation' # Handlebar
key: 'edit_vacation'
type: 'FORM' # [LINK, FORM ]
condition:
- expression: "{{eq VACATION_EMAIL PORTRAIT_USER_EMAIL}}"
forwardFields:
- key
- VACATION_EMAIL
- VACATION_DATE
- VACATION_STATUS
src-inventory
have the same configured actions available. However not all actions are displayed equally. For example inventory items that are not in stock (amount <= 0) the link is not displayed. In addition, the value of the link is also different for each inventory itemActions for triggering Forms
actions:
- label: 'Edit vacation' # Handlebar
key: 'action1'
value: 'create_vacation'
type: 'FORM' # [LINK, FORM ]
condition:
- expression: "{{eq VACATION_EMAIL PORTRAIT_USER_EMAIL}}"
forwardFields:
- key
- VACATION_EMAIL
- VACATION_DATE
- VACATION_STATUS
Details, see: Actions
Forms
Permissions
Forms can now be configured to be only accessible for admins.
- id: createInventoryItem role: ADMIN onSubmit: type: ELO ... dialog: config/forms/createInventoryItem.json
role |
If you don’t define it, role |
Field Processor
Field Processors support has been extended and is now available for
ELO
Python
SQL
They work similar to the Field Processors in ELO.
Python
The evaluated Field processors are passed alongside the form fields to the python script via the --args flag.
SQL
The evaluated Field processors are available as variables for the SQL query.
Forms based on existing Entries - Mode and Scope
It is now possible to link Forms to existing Entries. This allows Portrait Administrators to further increase their application security and data consistency. To achieve this goal we introduced multiple configuration options:Mode Update (new for SQL and Python)
SubmitType | ELO | SQL | Python | BLP 5.1 |
---|---|---|---|---|
Supported |
|
|
|
|
With the Mode Update you can get the ‘previous’ attributes from an entry available as variables to be used for Field Processors, SQL Query or further processing in your Python script.
The attributes are prefixed with ORIGIN_
(STATUS → ORIGIN_STATUS
)
In addition a security check is applied:
If no entry is found for the configured section and key
Python/SQL
Two configs are required:
mode: UPDATE
source: sectionID
forward ‘key’ in the section action config. https://portrait.atlassian.net/wiki/spaces/PA7/pages/1029113865/Column+Types#Form
- id: edit_entry onSubmit: mode: UPDATE source: sectionID
ELO:
Three configs are required:
- id: edit_entry onSubmit: mode: UPDATE source: sectionID scope: STRICT
Scope (Strict / Loose)
SubmitType | ELO | SQL | Python | BLP 5.1 |
---|---|---|---|---|
Supported |
|
|
|
|
A Parameter scope
has been added to the onSubmit
configuration element.
This can either be STRICT
or LOOSE
- id: create_vacation onSubmit: type: ELO ... source: vacations scope: STRICT
STRICT:
The STRICT Scope is meant to protect your ELO Instance from being manipulated in ways that you did not configured in the first place: Like editing unrelated Sords which are not indexed. Therefore it is highly recommended to apply this configuration.
Please set all ELO Mask Attributes explicitly via FieldProcessors see https://portrait.atlassian.net/wiki/spaces/PA7/pages/1029112687/ELO#Mask-Attributes
For Mode Update/Delete there are additional requirements and checks:
Required Config for Mode UPDATE/DELETE:
The
source: <sectionID>
parameter needs to be set → this is the sectionID of the referenced section
For more details on the purpose and effect of the STRICT mode see:
Conditions
With Portrait 7.0 we introduce the concept of conditions, which allows for extensive input validation to keep your data consistent at all times.
A condition is a boolean expression when evaluated either allow or prevent:
Section Actions https://portrait.atlassian.net/wiki/x/AQBwPQ
Form submit (backend validated) https://portrait.atlassian.net/wiki/spaces/PA7/pages/1029112649/Post+Processing+onSubmit#Conditions
Example:
condition: - expression: "{{in SELECTED_STATUS 'STATUS1' 'STATUS2'}}" errorMessage: 'only STATUS1 or STATUS2 are allowed' - expression: "{{in SELECTED_EMAIL PORTRAIT_USER_EMAIL}}" errorMessage: 'you can only edit your own entries'
Multiple Conditions can be set. All conditions need to match. (Logical AND between all conditions). You can build more complex rules with nested handlebars.
- expression: "{{or (in SELECTED_STATUS 'STATUS1' 'STATUS2') (eq PORTRAIT_USER_ROLE 'ADMIN')}}" errorMessage: 'only STATUS1 or STATUS2 are allowed, Admin can do anything'
If needed, variables can also be preprocessed with fieldProcessors, this allows for a better readability.
- id: edit_entry onSubmit: type: ELO connection: eloconn mask: ELOMASK mode: UPDATE source: eloSource scope: STRICT condition: - expression: "{{or IS_VALID_STATUS IS_ADMIN_USER}}" errorMessage: 'only STATUS1 or STATUS2 are allowed, Admin can do anything' fieldProcessor: - field: STATUS value: "{{SELECTED_STATUS}}" - field: IS_VALID_STATUS value: "{{in SELECTED_STATUS 'STATUS1' 'STATUS2'}}" type: BOOLEAN - field: IS_ADMIN_USER value: "{{eq PORTRAIT_USER_ROLE 'ADMIN'}}" type: BOOLEAN
Conditional Support Currently on Forms https://portrait.atlassian.net/wiki/x/SQNXPQ currently works for
ELO
Python
SQL
For more details see: https://portrait.atlassian.net/wiki/x/AYDHPg
Section Actions
The Action inside a Section has been reworked and now support
conditional expression. These allow to write custom boolean expression to either display or hide a action on an entry to entry base.
Handlebar Support for Labels and Links.
Support to link multiple Forms within a single Section Entry
Details, see: Actions
Field Processor
Type Boolean
A new type boolean has been added. Example:
fieldProcessor: - field: BudgetLeft value: '{{gt Amount 0}}' type: BOOLEAN
Details Field Processors
In Helper
A new type helper has been added.
Check if a given value is contained in a given list of other values
{{in SELECT_VACATION_STATUS 'GENEHMIGT' 'ABGELEHNT'}}
Also a combination with various split methods is possible
{{in PORTRAIT_USER_EMAIL (splitBySemicolon EVENT_PARTICIPANTS) 'admin@portrait.com'}}
Details: https://portrait.atlassian.net/wiki/spaces/PA7/pages/1029114243/Field+Processors#In
File Download
It is now possible to download files linked to an indexed ELO Entry directly from the Table or Detail View.
Configuration
sourceSpecific: mask: Application Entry files: nesting: 1 mask: Application Attachment
Will be displayed like this:
Depending on the file type, a preview is available. All files are available to download.
Search
Elasticsearch Index Space Optimization
We optimized the required disk space for the search recommendations. To take full advantage of this, we recommend that you delete your index and rebuild them from scratch.
To do this:
Delete your sources that you want to optimize in the admin dashboard.
Restart Portrait
If mandatory, trigger a manual reindex if not configured periodic reindexing.
Breaking Changes
Breaking changes led to a major release. In this chapter, we summarize the breaking changes - which might affect your installation and need to be considered, whilst upgrading.
SQL Form Submit Parameter
We increased the security measurements whilst dealing with SQL write operations. This means, the SQL query will be parsed as prepared statement. For safety reasons, we enforce this style now for every SQL query.
→ achtung bitte nur hier editieren
im verlinkten ists es auch. ich will es mit / insertexcerpt verlinken anstatt alles doppelt zu haben im idealfall. Wenn moeglich halt
ok, dann halte ich mich kurz beim “promoten” der neuen Version.
Dann entfernen wir den Code unterhalb und beschreiben es in der Dok only?
so:
Given the example for the
- id: updateCompany onSubmit: type: SQL connection: organigram query: | UPDATE DemoOrganigram SET NAME = '{{name}}', PARENT_LABELS = '{{PARENT_LABELS}}', COUNTRY = '{{COUNTRY}}', REGION = '{{REGION}}', GF = '{{GF}}', SUB_COMPANIES = '{{SUB_COMPANIES}}' WHERE ID = '{{ID}}';
New Format
In this given example, the form-data will be inserted into the table The FieldProcessors can either be used to format fields or as a fallback in case of an optional field in the form. If not supplied the Query would fail as there would be no valid parameter :Name if not set previously. With the given FieldProcessor the fallback is an empty String. - id: createNewCompany
onSubmit:
type: SQL
connection: organigram
source: demo-organigram
fieldProcessor:
- field: Name
value: "{{Name}}"
- field: PARENT_IDS
value: "{{PARENT_IDS}}"
- field: PARENT_LABELS
value: "{{PARENT_LABELS}}"
query: |
INSERT INTO DemoOrganigram (Name, PARENT_IDS, PARENT_LABELS)
VALUES (:Name, :PARENT_IDS, :PARENT_LABELS);
dialog: config/forms/createNewCompany.json
DemoOrganigram
via the connection organigram
.
Details, see: https://portrait.atlassian.net/wiki/x/pQNXPQ
TODO remove this: New Format
- id: updateCompany onSubmit: type: SQL mode: UPDATE source: demo-organigram # used to get access to ORIGIN_ID for improved security connection: organigram # the database connection the update statement should run against fieldProcessor: # Useful for optional fields which otherwise could not be set in the SQL statement - field: NAME value: "{{NAME}}" - field: PARENT_LABELS value: "{{PARENT_LABELS}}" - field: COUNTRY value: "{{COUNTRY}}" - field: REGION value: "{{REGION}}" - field: GF value: "{{GF}}" - field: SUB_COMPANIES value: "{{SUB_COMPANIES}}" query: | UPDATE DemoOrganigram SET NAME = :NAME, PARENT_LABELS = :PARENT_LABELS, COUNTRY = :COUNTRY, REGION = :REGION, GF = :GF, SUB_COMPANIES = :SUB_COMPANIES WHERE ID = :ORIGIN_ID;
ELO Sources Files Indexing
When indexing files to the public folder, it is now required to set publicCache: true
.
This is relevant to you, if you use Portrait in public mode - without authentication.
See also: Set up Public Access
full example:
sourceSpecific: mask: Application Entry files: publicCache: true nesting: 1 mask: Application Attachment
Details, see: https://portrait.atlassian.net/wiki/spaces/PA7/pages/1029114146/ELO+sources#Index-Documents%2FFiles
Indexed Files are also displayed in the Section View and are available as a download. This behaviour can be disabled by setting the section Setting
- name: sectionid disableFiles: true
Details, see: Settings
ELO Sources Adminbase and Chaosablage indexing
When indexing Elements within the Administrationbase (“Administration” Folder) and “Chaosablage” results are per default now ignored this can be changed with
sourceSpecific: blacklistChaosablage: false #default, if not set: true blacklistAdministration: false #default, if not set: true
https://treskon.atlassian.net/browse/POR-871
In addition you can also provide own folder GUIDs that will be excluded:
ELO Sources IDs
With this version we switch to a more classic approach how ELO uses GUIDS. In ELO the identifier for an SORD is in there form of:
Object ID | GUID |
---|---|
17593 | (FC4B5EB2-359A-2FAE-C3DE-AC908A1A6BD9) |
Previously we removed the surrounding brackets artificially. So the ID inside portrait had been ‘FC4B5EB2-359A-2FAE-C3DE-AC908A1A6BD9’.
With Release 7.X we remove this artificially modification of the GUID.
To maintain a consistent database ,we recommend to clear the existing ELO indexes and reindex them!
There might be some changes required in your configuration when you already used the ELO-GUID directly by the substring handlebar.
Example
Before | After |
---|---|
value: '{{#if SHARE_PARENT_ELOGUID}}[Open](https://organigram.tuev.at/complete/{{substring SHARE_PARENT_ELOGUID 1 37 }}/hi){{/if}}' | value: '{{#if SHARE_PARENT_ELOGUID}}[Open](https://organigram.tuev.at/complete/{{SHARE_PARENT_ELOGUID}}/hi){{/if}}' |
|
|
|
|
API changes
The following API routes were removed in this release:
GET DATA / Search:
old URL: /data/{term}
and /data
was changed to
new URL: /search/{term}
and /search
GET DocDetails:
URL: /docDetails/{docId}
(where docID was similar to the entryID)
Desc: This allowed to retrieve the key value pairs for a single entry. Similar to /entries/{sectionID}/{entryID}
. However, it would lead to an issue when there would be the same entryID in two different sections. To guarantee uniqueness DocDetails was removed
Alternative: Use either /entries/{sectionID}/{entryID}
to query an unique entry. Or/data/{term}
to perform a global search.
GET Sources:
URL:sources/{index}/{entryID}
Desc: Method to retrieve a certain entry. This was similar to /entries/{sectionID}/{entryID}
.
Alternative: Use /entries/{sectionID}/{entryID}
INFO: Currently there is a strong 1:1 connection between source and section. In the next versions we plan to loosen up this restriction to connection a single source to multiple sections. This API change is one of the first steps towards that goal