mirror of
https://github.com/mattermost/mattermost.git
synced 2026-10-16 15:18:10 +08:00
* MM: Add PropertySyncer API for LDAP/SAML sync of text, select and multiselect attributes Introduces the shared sync engine the enterprise LDAP and SAML packages will call instead of carrying their own text-only logic: - model: PropertyFieldType.SupportsExternalSync, sync source constants and helpers, PropertySync result types, 500-option cap constant. - app: PropertySyncer (NewPropertySyncer, SyncUser, PruneOrphanedOptions) that matches source values to options by exact name, provisions missing options with optimistic concurrency, clears values for absent/empty attributes, and prunes options no live value references. - properties: UpdatePropertyFieldsOpts.ExpectedUpdateAts extends optimistic concurrency to the caller's own read; validation hook now keeps ldap/saml on select/multiselect, accepts empty options on synced fields, locks the option set of a synced field to its sync (colors stay editable), and grandfathers managed=admin so the sync can provision options; read access gives the owning sync the full option list. - store: PropertyValueStore.GetReferencedOptionIDs plus a partial index on PropertyValues(GroupID, FieldID) WHERE DeleteAt = 0. * Address lint findings in PropertySyncer Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Drop the option re-provisioning retry from PropertySyncer value writes The batch upsert already falls back to one write per field so a single bad value cannot block the rest. The extra path that re-read the field and re-provisioned options when a concurrent sync pruned one between provisioning and the write covered a narrow race, was untested, and the next sync run or login corrects it anyway. Removing it drops ~80 lines and an i18n key. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Give linked fields their template's sync source, and only user fields createPropertyField copied the template's ldap/saml attrs onto every field linking to it, whatever its object type, and kept a sync source the caller sent when the template had none. A channel or post field of an attribute synced from AD/LDAP or SAML then carried the link, so the sync lock refused every write to its values -- values no sync ever writes, since an identity source reports attributes of users. A linked field's sync source is now exactly its template's, and only a user field takes it. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Refuse property values for a deleted field UpsertPropertyValues checked that each value's field exists and has the requested object type, but a soft-deleted field passed both checks: GetMany returns deleted rows. A writer holding a field it read before the deletion -- the AD/LDAP sync keeps its fields for a whole run -- would write live values onto it. A deleted field is now reported as not found, like a missing one. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Sync custom profile attributes through their Global Attribute definition Custom profile attributes are now Global Attributes: a template defines the attribute -- its type, its options and the AD/LDAP or SAML attribute it is synced from -- and user, channel and post fields link to it, serving its options. The syncer followed the old shape, a user field holding its own options, and would have provisioned options onto linked fields (which cannot own them through a field write), pruned options a channel's value still references, and missed a sync link an admin added to the template after the user field existed. The syncer now resolves each user field to its definition -- the template it links to, or the field itself -- and takes the external attribute from it. Options are created on and pruned from the definition through the per-option API, which compare-and-swaps every change in the property service; a sync that loses the race re-reads the option list and retries, so the ExpectedUpdateAts extension to UpdatePropertyFields is no longer needed and is removed. An option is orphaned only when no live value of the definition or any field linking to it references it, so GetReferencedOptionIDs takes a set of fields. Also: - A definition naming both sources is synced from AD/LDAP only, the only sync the value lock admits for it; SAML used to fail on every login. - Fields and definitions are read from the master: the LDAP job keeps them for its whole run. - A field deleted during a run is dropped after its first failed write rather than failing for every remaining user. - The legacy custom profile attribute event withholds values of restricted fields, like every other broadcast. - The syncer logs each field outcome itself, at the LDAP levels for the AD/LDAP source, so the enterprise adapters no longer duplicate that logging. - A value_type skip no longer quotes the value, and prune results no longer carry option names; both are attribute values. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Enforce the synced-options rule on templates and the options endpoints Only an attribute's own sync may add, rename or remove its options. The rule was checked on field writes only, and on the field carrying the options -- which for a Global Attribute is the template, where Attribute Management edits them. It now also runs as the PreChangePropertyFieldOptions hook, so the per-option endpoints are no longer a way around it. That hook sees the field but not the change, so it refuses every change but the sync's, a recolor included; recoloring stays possible through the field's option list. The sync writes options through those endpoints now, so it no longer updates fields and the managed=admin exemption added for it is reverted. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Extract i18n strings for the enterprise sync adapters The AD/LDAP and SAML adapters no longer build their own attribute-to-field maps and pass the LDAP entry straight to the shared syncer, so their CPA field-listing and empty-entry errors are gone. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Say what a prune race leaves behind for channel values A value written between the prune's read of references and its removal keeps naming a deleted option. A user's is rewritten by the next sync; a channel's has no sync and stays unset until someone sets it again. Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com> * Drop retired CPA sync strings from the locale catalogs The enterprise AD/LDAP and SAML adapters no longer raise ent.ldap.cpa_field_mapping.list_error, ent.ldap.update_cpa.empty_attribute or ent.saml.cpa_field_mapping.list_error, so en.json dropped them. The 21 locale catalogs still carried their translations, which make i18n-verify rejects as ids en.json no longer has. * Attribute Management: sync select and multiselect attributes from AD/LDAP and SAML The server now syncs text, select and multiselect attributes through the shared PropertySyncer, but the System Console still treated sync as text-only: linking a source converted the attribute to Text, switching to any other type cleared the links, and the link modal and the AD/LDAP/SAML settings section warned that everything else would be converted. - supportsExternalSync mirrors PropertyFieldType.SupportsExternalSync. Linking keeps a type it accepts (phone, URL and email included, which are text on the server); only a type it rejects is converted to Text, and only a switch to one clears the links. - A synced select or multiselect shows its options read-only, with a note naming the source that manages them, and no longer requires an option before Save, since the sync provisions them. - Saving an attribute that was already synced leaves its options out of the patch: the server refuses anyone but the sync adding, renaming or removing one, and a list loaded before the sync added an option would read as removing it. Linking for the first time still saves the options the admin seeded. - Adding a link is no longer locked by an Applies-to resource when the type would not change. - The picker tells a Type switch from a chip removal by whether the type changed, not by the type being Text. - The AD/LDAP/SAML "User attributes sync" section patches only the link for a syncable type instead of converting it to Text and resending every attr, keeps linked select/multiselect fields editable, and links to Attribute Management. * Attribute Management: hold Save on a synced select until it applies to Users The sync provisions a select or multiselect's options from the values it writes to users, and the server lets nothing but the sync change the options of a synced attribute. A synced select that applies only to Channels or Posts was therefore left with an option list nobody could add to. Save now waits, with an inline note, until the attribute applies to Users or the link is removed, including after Users is removed from an attribute that was already synced. The locked Type control's accessible name also announces the type the attribute keeps, instead of always saying Text, now that linking a select or multiselect no longer converts it. * Update the E2E specs for select and multiselect sync The AD/LDAP and SAML settings page and the Attribute Management form specs still expected linking a select to convert it to Text. They now check that a linked select keeps its type and saves only its link, that a type sync cannot populate (Ranked) is still disabled or converted with a warning, and that a synced select holds Save until it applies to Users. * Docs: document select and multiselect sync in Attribute Management The Attribute Management page still said linking an external source converts an attribute to Text. Describe what linking does from v12.1: Select and Multiselect keep their type and get their options from the sync, which needs the attribute to apply to Users; a type kept by the link can be linked while the attribute applies to a resource; and a value the identity provider stops sending is removed at the next sync. * Give the external-source Type lock label a new message id Its text gained the {value} placeholder, which every existing translation of the old id lacks, so the translation check rejected them all. The old id is dropped from the locale catalogs, where it would otherwise be an extra key. * Fix a race in TestCheckLdapUserPasswordConcurrency's LDAP mock Each goroutine registered the DoLogin expectation itself. testify adds an expectation with On() and sets its return values in Return(), so a goroutine calling DoLogin in between matched an expectation with no return values and panicked, failing the Postgres shard that ran it. The expectation is now registered once, before the attempts start. The check also no longer dereferences an attempt's error before checking it is set. * Let a field's own sync clear it when required, and keep a sync off unsynced options A synced user field marked required (possible through the API) refused the sync's clearing write, so the user kept a value the directory no longer has. The field's own sync may now clear it: the source decides the value, including that there is none. A user field linked to a template it has no copy of the link of is judged by the template's link. The options endpoints let any caller change the options of a field that is not synced, so a sync job that loaded the field before an admin unlinked it could still add or remove its options. A sync now changes only the options of a field synced from its own source. * Clear a synced value the source reports but that cannot be stored When the sync could not store a user's new value (too long, past the option cap, or its option could not be created), it skipped the field and kept the old value, so someone who moved out of Finance could still pass a Finance-only policy. The stored value is now cleared instead, as for an attribute the source no longer reports; the outcome still says skipped or error, and why. A failure caused by the attribute being unlinked while the syncer ran is the exception: the field is the admin's again, so its value is left alone and the syncer stops syncing it. For the same reason the prune now skips a definition that is no longer synced from its source, instead of deleting options the admin added after unlinking it. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>