mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-18 18:18:23 -05:00
[MM-66836] Integrate PropertyAccessService into API and app layers (#34818)
Updates all Custom Profile Attribute endpoints and app layer methods to pass caller user IDs through to the PropertyAccessService. This connects the access control service introduced in #34812 to the REST API, Plugin API, and internal app operations. Also updates the OpenAPI spec to document the new field attributes (protected, source_plugin_id, access_mode) and adds notes about protected field restrictions.
This commit is contained in:
parent
63c3c70fe9
commit
1cfe3d92b6
22 changed files with 1860 additions and 368 deletions
|
|
@ -83,6 +83,17 @@
|
|||
saml:
|
||||
type: string
|
||||
description: "SAML attribute for syncing"
|
||||
protected:
|
||||
type: boolean
|
||||
description: "If true, the field is read-only and cannot be modified."
|
||||
source_plugin_id:
|
||||
type: string
|
||||
description: "The ID of the plugin that created this field. This attribute cannot be changed."
|
||||
access_mode:
|
||||
type: string
|
||||
description: "Access mode of the field"
|
||||
enum: ["", "source_only", "shared_only"]
|
||||
default: ""
|
||||
responses:
|
||||
"201":
|
||||
description: Custom Profile Attribute field creation successful
|
||||
|
|
@ -108,6 +119,9 @@
|
|||
updated. The fields that can be updated are defined in the
|
||||
request body, all other provided fields will be ignored.
|
||||
|
||||
**Note:** Fields with `attrs.protected = true` cannot be
|
||||
modified and will return an error.
|
||||
|
||||
_This endpoint is experimental._
|
||||
|
||||
__Minimum server version__: 10.5
|
||||
|
|
@ -167,6 +181,17 @@
|
|||
saml:
|
||||
type: string
|
||||
description: "SAML attribute for syncing"
|
||||
protected:
|
||||
type: boolean
|
||||
description: "If true, the field is read-only and cannot be modified."
|
||||
source_plugin_id:
|
||||
type: string
|
||||
description: "The ID of the plugin that created this field. This attribute cannot be changed."
|
||||
access_mode:
|
||||
type: string
|
||||
description: "Access mode of the field"
|
||||
enum: ["", "source_only", "shared_only"]
|
||||
default: ""
|
||||
responses:
|
||||
"200":
|
||||
description: Custom Profile Attribute field patch successful
|
||||
|
|
@ -229,6 +254,9 @@
|
|||
that can be updated are defined in the request body, all other
|
||||
provided fields will be ignored.
|
||||
|
||||
**Note:** Values for fields with `attrs.protected = true` cannot be
|
||||
updated and will return an error.
|
||||
|
||||
_This endpoint is experimental._
|
||||
|
||||
__Minimum server version__: 10.5
|
||||
|
|
@ -356,6 +384,9 @@
|
|||
description: |
|
||||
Update Custom Profile Attribute field values for a specific user.
|
||||
|
||||
**Note:** Values for fields with `attrs.protected = true` cannot be
|
||||
updated and will return an error.
|
||||
|
||||
_This endpoint is experimental._
|
||||
|
||||
__Minimum server version__: 11
|
||||
|
|
|
|||
|
|
@ -711,7 +711,7 @@ func getFieldsAutocomplete(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
var ac []*model.PropertyField
|
||||
var appErr *model.AppError
|
||||
|
||||
ac, appErr = c.App.GetAccessControlFieldsAutocomplete(c.AppContext, after, limit)
|
||||
ac, appErr = c.App.GetAccessControlFieldsAutocomplete(c.AppContext, after, limit, c.AppContext.Session().UserId)
|
||||
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ func listCPAFields(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
fields, appErr := c.App.ListCPAFields()
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
fields, appErr := c.App.ListCPAFields(callerUserID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -66,7 +67,8 @@ func createCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
defer c.LogAuditRec(auditRec)
|
||||
model.AddEventParameterAuditableToAuditRec(auditRec, "property_field", pf)
|
||||
|
||||
createdField, appErr := c.App.CreateCPAField(pf)
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
createdField, appErr := c.App.CreateCPAField(callerUserID, pf)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -121,7 +123,8 @@ func patchCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
defer c.LogAuditRec(auditRec)
|
||||
model.AddEventParameterAuditableToAuditRec(auditRec, "property_field_patch", patch)
|
||||
|
||||
originalField, appErr := c.App.GetCPAField(c.Params.FieldId)
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
originalField, appErr := c.App.GetCPAField(callerUserID, c.Params.FieldId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -129,7 +132,7 @@ func patchCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
auditRec.AddEventPriorState(originalField)
|
||||
|
||||
patchedField, appErr := c.App.PatchCPAField(c.Params.FieldId, patch)
|
||||
patchedField, appErr := c.App.PatchCPAField(callerUserID, c.Params.FieldId, patch)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -164,14 +167,15 @@ func deleteCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
defer c.LogAuditRec(auditRec)
|
||||
model.AddEventParameterToAuditRec(auditRec, "field_id", c.Params.FieldId)
|
||||
|
||||
field, appErr := c.App.GetCPAField(c.Params.FieldId)
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
field, appErr := c.App.GetCPAField(callerUserID, c.Params.FieldId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
auditRec.AddEventPriorState(field)
|
||||
|
||||
if appErr := c.App.DeleteCPAField(c.Params.FieldId); appErr != nil {
|
||||
if appErr := c.App.DeleteCPAField(callerUserID, c.Params.FieldId); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
|
@ -224,8 +228,9 @@ func patchCPAValues(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// if the user is not an admin, we need to check that there are no
|
||||
// admin-managed fields
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
fields, appErr := c.App.ListCPAFields()
|
||||
session := *c.AppContext.Session()
|
||||
if !c.App.SessionHasPermissionTo(session, model.PermissionManageSystem) {
|
||||
fields, appErr := c.App.ListCPAFields(session.UserId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -242,9 +247,10 @@ func patchCPAValues(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
results := make(map[string]json.RawMessage, len(updates))
|
||||
for fieldID, rawValue := range updates {
|
||||
patchedValue, appErr := c.App.PatchCPAValue(userID, fieldID, rawValue, false)
|
||||
patchedValue, appErr := c.App.PatchCPAValue(callerUserID, userID, fieldID, rawValue, false)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -271,17 +277,19 @@ func listCPAValues(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
userID := c.Params.UserId
|
||||
targetUserID := c.Params.UserId
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
|
||||
// we check unrestricted sessions to allow local mode requests to go through
|
||||
if !c.AppContext.Session().IsUnrestricted() {
|
||||
canSee, err := c.App.UserCanSeeOtherUser(c.AppContext, c.AppContext.Session().UserId, userID)
|
||||
canSee, err := c.App.UserCanSeeOtherUser(c.AppContext, callerUserID, targetUserID)
|
||||
if err != nil || !canSee {
|
||||
c.SetPermissionError(model.PermissionViewMembers)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
values, appErr := c.App.ListCPAValues(userID)
|
||||
values, appErr := c.App.ListCPAValues(callerUserID, targetUserID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
@ -298,7 +306,7 @@ func listCPAValues(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func patchCPAValuesForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
|
||||
c.Err = model.NewAppError("Api4.patchCPAValues", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
|
||||
c.Err = model.NewAppError("Api4.patchCPAValuesForUser", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -324,29 +332,35 @@ func patchCPAValuesForUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||
defer c.LogAuditRec(auditRec)
|
||||
model.AddEventParameterToAuditRec(auditRec, "user_id", userID)
|
||||
|
||||
// if the user is not an admin, we need to check that there are no
|
||||
// admin-managed fields
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
|
||||
fields, appErr := c.App.ListCPAFields()
|
||||
// Check for admin-managed fields
|
||||
session := *c.AppContext.Session()
|
||||
isAdmin := c.App.SessionHasPermissionTo(session, model.PermissionManageSystem)
|
||||
if !isAdmin {
|
||||
fields, appErr := c.App.ListCPAFields(session.UserId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
// Check if any of the fields being updated are admin-managed
|
||||
for _, field := range fields {
|
||||
if _, isBeingUpdated := updates[field.ID]; isBeingUpdated {
|
||||
if field.IsAdminManaged() {
|
||||
c.Err = model.NewAppError("Api4.patchCPAValues", "app.custom_profile_attributes.property_field_is_managed.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if _, isBeingUpdated := updates[field.ID]; !isBeingUpdated {
|
||||
continue
|
||||
}
|
||||
// Check for admin-managed fields
|
||||
if field.IsAdminManaged() {
|
||||
c.Err = model.NewAppError("Api4.patchCPAValuesForUser",
|
||||
"app.custom_profile_attributes.property_field_is_managed.app_error",
|
||||
nil, "",
|
||||
http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callerUserID := c.AppContext.Session().UserId
|
||||
results := make(map[string]json.RawMessage, len(updates))
|
||||
for fieldID, rawValue := range updates {
|
||||
patchedValue, appErr := c.App.PatchCPAValue(userID, fieldID, rawValue, false)
|
||||
patchedValue, appErr := c.App.PatchCPAValue(callerUserID, userID, fieldID, rawValue, false)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ func TestListCPAFields(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ func TestListCPAFields(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("the endpoint should only list non deleted fields", func(t *testing.T) {
|
||||
require.Nil(t, th.App.DeleteCPAField(createdField.ID))
|
||||
require.Nil(t, th.App.DeleteCPAField("", createdField.ID))
|
||||
fields, resp, err := th.Client.ListCPAFields(context.Background())
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -184,7 +184,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -307,7 +307,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -388,7 +388,7 @@ func TestDeleteCPAField(t *testing.T) {
|
|||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedField, appErr := th.App.GetCPAField(createdField.ID)
|
||||
deletedField, appErr := th.App.GetCPAField("", createdField.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, deletedField.DeleteAt)
|
||||
|
||||
|
|
@ -430,11 +430,11 @@ func TestListCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
_, appErr = th.App.PatchCPAValue(th.BasicUser.Id, createdField.ID, json.RawMessage(`"Field Value"`), true)
|
||||
_, appErr = th.App.PatchCPAValue("", th.BasicUser.Id, createdField.ID, json.RawMessage(`"Field Value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("endpoint should not work if no valid license is present", func(t *testing.T) {
|
||||
|
|
@ -474,11 +474,11 @@ func TestListCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdArrayField, appErr := th.App.CreateCPAField(arrayField)
|
||||
createdArrayField, appErr := th.App.CreateCPAField("", arrayField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdArrayField)
|
||||
|
||||
_, appErr = th.App.PatchCPAValue(th.BasicUser.Id, createdArrayField.ID, json.RawMessage(fmt.Sprintf(`["%s", "%s"]`, optionID1, optionID2)), true)
|
||||
_, appErr = th.App.PatchCPAValue("", th.BasicUser.Id, createdArrayField.ID, json.RawMessage(fmt.Sprintf(`["%s", "%s"]`, optionID1, optionID2)), true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
values, resp, err := th.Client.ListCPAValues(context.Background(), th.BasicUser.Id)
|
||||
|
|
@ -515,7 +515,7 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -618,7 +618,7 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdArrayField, appErr := th.App.CreateCPAField(arrayField)
|
||||
createdArrayField, appErr := th.App.CreateCPAField("", arrayField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdArrayField)
|
||||
|
||||
|
|
@ -656,7 +656,7 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdLDAPField, appErr := th.App.CreateCPAField(ldapField)
|
||||
createdLDAPField, appErr := th.App.CreateCPAField("", ldapField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdLDAPField)
|
||||
|
||||
|
|
@ -670,7 +670,7 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdSAMLField, appErr := th.App.CreateCPAField(samlField)
|
||||
createdSAMLField, appErr := th.App.CreateCPAField("", samlField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdSAMLField)
|
||||
|
||||
|
|
@ -710,7 +710,7 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -796,10 +796,10 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
t.Run("batch update with managed fields fails for regular user", func(t *testing.T) {
|
||||
// First set some initial values to ensure we can verify they don't change
|
||||
// Set initial values for both fields using th.App (admins can set managed field values)
|
||||
_, appErr := th.App.PatchCPAValue(th.BasicUser.Id, createdRegularField.ID, json.RawMessage(`"Initial Regular Value"`), false)
|
||||
_, appErr := th.App.PatchCPAValue("", th.BasicUser.Id, createdRegularField.ID, json.RawMessage(`"Initial Regular Value"`), false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
_, appErr = th.App.PatchCPAValue(th.BasicUser.Id, createdManagedField.ID, json.RawMessage(`"Initial Managed Value"`), true)
|
||||
_, appErr = th.App.PatchCPAValue("", th.BasicUser.Id, createdManagedField.ID, json.RawMessage(`"Initial Managed Value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Try to batch update both managed and regular fields - this should fail
|
||||
|
|
@ -814,7 +814,7 @@ func TestPatchCPAValues(t *testing.T) {
|
|||
CheckErrorID(t, err, "app.custom_profile_attributes.property_field_is_managed.app_error")
|
||||
|
||||
// Verify that no values were updated when the batch operation failed
|
||||
currentValues, appErr := th.App.ListCPAValues(th.BasicUser.Id)
|
||||
currentValues, appErr := th.App.ListCPAValues("", th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Check that values remain unchanged - both fields should retain their initial values
|
||||
|
|
@ -880,7 +880,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -983,7 +983,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdArrayField, appErr := th.App.CreateCPAField(arrayField)
|
||||
createdArrayField, appErr := th.App.CreateCPAField("", arrayField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdArrayField)
|
||||
|
||||
|
|
@ -1021,7 +1021,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdLDAPField, appErr := th.App.CreateCPAField(ldapField)
|
||||
createdLDAPField, appErr := th.App.CreateCPAField("", ldapField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdLDAPField)
|
||||
|
||||
|
|
@ -1035,7 +1035,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdSAMLField, appErr := th.App.CreateCPAField(samlField)
|
||||
createdSAMLField, appErr := th.App.CreateCPAField("", samlField)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdSAMLField)
|
||||
|
||||
|
|
@ -1075,7 +1075,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField("", field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdField)
|
||||
|
||||
|
|
@ -1145,7 +1145,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
// Set initial value through the app layer that we will be replacing during the test
|
||||
_, appErr := th.App.PatchCPAValue(th.SystemAdminUser.Id, createdManagedField.ID, json.RawMessage(`"Initial Admin Value"`), true)
|
||||
_, appErr := th.App.PatchCPAValue("", th.SystemAdminUser.Id, createdManagedField.ID, json.RawMessage(`"Initial Admin Value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
values := map[string]json.RawMessage{
|
||||
|
|
@ -1202,10 +1202,10 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
t.Run("batch update with managed fields fails for regular user", func(t *testing.T) {
|
||||
// First set some initial values to ensure we can verify they don't change
|
||||
// Set initial values for both fields using th.App (admins can set managed field values)
|
||||
_, appErr := th.App.PatchCPAValue(th.BasicUser.Id, createdRegularField.ID, json.RawMessage(`"Initial Regular Value"`), false)
|
||||
_, appErr := th.App.PatchCPAValue("", th.BasicUser.Id, createdRegularField.ID, json.RawMessage(`"Initial Regular Value"`), false)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
_, appErr = th.App.PatchCPAValue(th.BasicUser.Id, createdManagedField.ID, json.RawMessage(`"Initial Managed Value"`), true)
|
||||
_, appErr = th.App.PatchCPAValue("", th.BasicUser.Id, createdManagedField.ID, json.RawMessage(`"Initial Managed Value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Try to batch update both managed and regular fields - this should fail
|
||||
|
|
@ -1220,7 +1220,7 @@ func TestPatchCPAValuesForUser(t *testing.T) {
|
|||
CheckErrorID(t, err, "app.custom_profile_attributes.property_field_is_managed.app_error")
|
||||
|
||||
// Verify that no values were updated when the batch operation failed
|
||||
currentValues, appErr := th.App.ListCPAValues(th.BasicUser.Id)
|
||||
currentValues, appErr := th.App.ListCPAValues("", th.BasicUser.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Check that values remain unchanged - both fields should retain their initial values
|
||||
|
|
|
|||
|
|
@ -283,14 +283,14 @@ func (a *App) GetAccessControlPolicyAttributes(rctx request.CTX, channelID strin
|
|||
return attributes, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlFieldsAutocomplete(rctx request.CTX, after string, limit int) ([]*model.PropertyField, *model.AppError) {
|
||||
func (a *App) GetAccessControlFieldsAutocomplete(rctx request.CTX, after string, limit int, callerID string) ([]*model.PropertyField, *model.AppError) {
|
||||
cpaGroupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAccessControlAutoComplete", "app.pap.get_access_control_auto_complete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
fields, err := a.Srv().Store().PropertyField().SearchPropertyFields(model.PropertyFieldSearchOpts{
|
||||
GroupID: cpaGroupID,
|
||||
// Use PropertyAccessService instead of direct Store access to enforce access control
|
||||
fields, err := a.PropertyAccessService().SearchPropertyFields(callerID, cpaGroupID, model.PropertyFieldSearchOpts{
|
||||
Cursor: model.PropertyFieldSearchCursor{
|
||||
PropertyFieldID: after,
|
||||
CreateAt: 1,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/mattermost/mattermost/server/public/shared/httpservice"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/timezones"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/properties"
|
||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/imageproxy"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
||||
|
|
@ -158,10 +157,6 @@ func (a *App) SetServer(srv *Server) {
|
|||
a.ch.srv = srv
|
||||
}
|
||||
|
||||
func (a *App) PropertyService() *properties.PropertyService {
|
||||
return a.Srv().propertyService
|
||||
}
|
||||
|
||||
func (a *App) PropertyAccessService() *PropertyAccessService {
|
||||
return a.Srv().propertyAccessService
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ func (a *App) FlagPost(rctx request.CTX, post *model.Post, teamId, reportingUser
|
|||
})
|
||||
}
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
_, err = a.Srv().propertyAccessService.CreatePropertyValues(anonymousCallerId, propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("FlagPost", "app.content_flagging.create_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ func (a *App) setContentFlaggingPropertiesForThreadReplies(post *model.Post, con
|
|||
})
|
||||
}
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
_, err = a.Srv().propertyAccessService.CreatePropertyValues(anonymousCallerId, propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("setContentFlaggingPropertiesForThreadReplies", "app.content_flagging.set_thread_replies_properties.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ func (a *App) setContentFlaggingPropertiesForThreadReplies(post *model.Post, con
|
|||
}
|
||||
|
||||
func (a *App) ContentFlaggingGroupId() (string, *model.AppError) {
|
||||
group, err := a.Srv().propertyService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
group, err := a.Srv().propertyAccessService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
if err != nil {
|
||||
return "", model.NewAppError("getContentFlaggingGroupId", "app.content_flagging.get_group.error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
|
@ -246,13 +246,13 @@ func (a *App) GetPostContentFlaggingPropertyValue(postId, propertyFieldName stri
|
|||
return nil, appErr
|
||||
}
|
||||
|
||||
statusPropertyField, err := a.Srv().propertyService.GetPropertyFieldByName(groupId, "", propertyFieldName)
|
||||
statusPropertyField, err := a.Srv().propertyAccessService.GetPropertyFieldByName(anonymousCallerId, groupId, "", propertyFieldName)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetPostContentFlaggingPropertyValue", "app.content_flagging.get_status_property.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
searchOptions := model.PropertyValueSearchOpts{TargetIDs: []string{postId}, PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES, FieldID: statusPropertyField.ID}
|
||||
propertyValues, err := a.Srv().propertyService.SearchPropertyValues(groupId, searchOptions)
|
||||
propertyValues, err := a.Srv().propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, searchOptions)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetPostContentFlaggingPropertyValue", "app.content_flagging.search_status_property.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -291,7 +291,7 @@ func (a *App) canFlagPost(groupId, postId, userLocal string) *model.AppError {
|
|||
}
|
||||
|
||||
func (a *App) GetContentFlaggingMappedFields(groupId string) (map[string]*model.PropertyField, *model.AppError) {
|
||||
fields, err := a.Srv().propertyService.SearchPropertyFields(groupId, model.PropertyFieldSearchOpts{PerPage: CONTENT_FLAGGING_MAX_PROPERTY_FIELDS})
|
||||
fields, err := a.Srv().propertyAccessService.SearchPropertyFields(anonymousCallerId, groupId, model.PropertyFieldSearchOpts{PerPage: CONTENT_FLAGGING_MAX_PROPERTY_FIELDS})
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetContentFlaggingMappedFields", "app.content_flagging.search_property_fields.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -365,7 +365,7 @@ func (a *App) createContentReviewPost(rctx request.CTX, flaggedPostId, teamId, r
|
|||
FieldID: flaggedPostIdFieldId,
|
||||
Value: json.RawMessage(fmt.Sprintf(`"%s"`, flaggedPostId)),
|
||||
}
|
||||
_, err := a.Srv().propertyService.CreatePropertyValue(propertyValue)
|
||||
_, err := a.Srv().propertyAccessService.CreatePropertyValue(anonymousCallerId, propertyValue)
|
||||
if err != nil {
|
||||
rctx.Logger().Error("Failed to create content review post property value in one of the channels", mlog.Err(err), mlog.String("channel_id", channel.Id), mlog.String("team_id", teamId), mlog.String("post_id", createdPost.Id))
|
||||
}
|
||||
|
|
@ -528,7 +528,7 @@ func (a *App) GetPostContentFlaggingPropertyValues(postId string) ([]*model.Prop
|
|||
return nil, appErr
|
||||
}
|
||||
|
||||
propertyValues, err := a.Srv().propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{TargetIDs: []string{postId}, PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES})
|
||||
propertyValues, err := a.PropertyAccessService().SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{TargetIDs: []string{postId}, PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES})
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetPostContentFlaggingPropertyValues", "app.content_flagging.search_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -654,13 +654,13 @@ func (a *App) PermanentDeleteFlaggedPost(rctx request.CTX, actionRequest *model.
|
|||
},
|
||||
}
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
_, err = a.Srv().propertyAccessService.CreatePropertyValues(anonymousCallerId, propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.create_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
status.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
||||
_, err = a.Srv().propertyService.UpdatePropertyValue(groupId, status)
|
||||
_, err = a.Srv().propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, status)
|
||||
if err != nil {
|
||||
return model.NewAppError("PermanentlyRemoveFlaggedPost", "app.content_flagging.permanently_delete.update_property_value.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -765,13 +765,13 @@ func (a *App) KeepFlaggedPost(rctx request.CTX, actionRequest *model.FlagContent
|
|||
},
|
||||
}
|
||||
|
||||
_, err = a.Srv().propertyService.CreatePropertyValues(propertyValues)
|
||||
_, err = a.Srv().propertyAccessService.CreatePropertyValues(anonymousCallerId, propertyValues)
|
||||
if err != nil {
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.create_property_values.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
status.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
||||
_, err = a.Srv().propertyService.UpdatePropertyValue(groupId, status)
|
||||
_, err = a.Srv().propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, status)
|
||||
if err != nil {
|
||||
return model.NewAppError("KeepFlaggedPost", "app.content_flagging.keep_post.status_update.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -977,14 +977,14 @@ func (a *App) AssignFlaggedPostReviewer(rctx request.CTX, flaggedPostId, flagged
|
|||
Value: json.RawMessage(fmt.Sprintf(`"%s"`, reviewerId)),
|
||||
}
|
||||
|
||||
assigneePropertyValue, err := a.Srv().propertyService.UpsertPropertyValue(assigneePropertyValue)
|
||||
assigneePropertyValue, err := a.Srv().propertyAccessService.UpsertPropertyValue(anonymousCallerId, assigneePropertyValue)
|
||||
if err != nil {
|
||||
return model.NewAppError("AssignFlaggedPostReviewer", "app.content_flagging.assign_reviewer.upsert_property_value.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if status == model.ContentFlaggingStatusPending {
|
||||
statusPropertyValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusAssigned))
|
||||
statusPropertyValue, err = a.Srv().propertyService.UpdatePropertyValue(groupId, statusPropertyValue)
|
||||
statusPropertyValue, err = a.Srv().propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, statusPropertyValue)
|
||||
if err != nil {
|
||||
return model.NewAppError("AssignFlaggedPostReviewer", "app.content_flagging.assign_reviewer.update_status_property_value.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -1201,7 +1201,7 @@ func (a *App) getReviewerPostsForFlaggedPost(contentFlaggingGroupId, flaggedPost
|
|||
var propertyValues []*model.PropertyValue
|
||||
|
||||
for {
|
||||
batch, err := a.Srv().propertyService.SearchPropertyValues(contentFlaggingGroupId, searchOptions)
|
||||
batch, err := a.Srv().propertyAccessService.SearchPropertyValues(anonymousCallerId, contentFlaggingGroupId, searchOptions)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("getReviewerPostsForFlaggedPost", "app.content_flagging.search_reviewer_posts.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
reviewerValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
||||
|
|
@ -221,7 +221,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
reviewerValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
||||
|
|
@ -265,7 +265,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
reviewerValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
||||
|
|
@ -295,7 +295,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
reviewerValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
||||
|
|
@ -325,7 +325,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
|
||||
// Set the status to Assigned
|
||||
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusAssigned))
|
||||
_, err := th.App.Srv().propertyService.UpdatePropertyValue(groupId, statusValue)
|
||||
_, err := th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
|
|
@ -337,7 +337,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
|
||||
// Set the status to Removed
|
||||
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
||||
_, err = th.App.Srv().propertyService.UpdatePropertyValue(groupId, statusValue)
|
||||
_, err = th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
|
|
@ -349,7 +349,7 @@ func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|||
|
||||
// Set the status to Retained
|
||||
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
||||
_, err = th.App.Srv().propertyService.UpdatePropertyValue(groupId, statusValue)
|
||||
_, err = th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
||||
|
|
@ -988,10 +988,10 @@ func TestCanFlagPost(t *testing.T) {
|
|||
groupId, appErr := th.App.ContentFlaggingGroupId()
|
||||
require.Nil(t, appErr)
|
||||
|
||||
statusField, err := th.Server.propertyService.GetPropertyFieldByName(groupId, "", ContentFlaggingPropertyNameStatus)
|
||||
statusField, err := th.Server.propertyAccessService.GetPropertyFieldByName(anonymousCallerId, groupId, "", ContentFlaggingPropertyNameStatus)
|
||||
require.NoError(t, err)
|
||||
|
||||
propertyValue, err := th.Server.propertyService.CreatePropertyValue(&model.PropertyValue{
|
||||
propertyValue, err := th.Server.propertyAccessService.CreatePropertyValue(anonymousCallerId, &model.PropertyValue{
|
||||
TargetID: post.Id,
|
||||
GroupID: groupId,
|
||||
FieldID: statusField.ID,
|
||||
|
|
@ -1007,7 +1007,7 @@ func TestCanFlagPost(t *testing.T) {
|
|||
|
||||
// Can't fleg when post already flagged in assigned status
|
||||
propertyValue.Value = json.RawMessage(`"` + model.ContentFlaggingStatusAssigned + `"`)
|
||||
_, err = th.Server.propertyService.UpdatePropertyValue(groupId, propertyValue)
|
||||
_, err = th.Server.propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, propertyValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
||||
|
|
@ -1015,7 +1015,7 @@ func TestCanFlagPost(t *testing.T) {
|
|||
|
||||
// Can't fleg when post already flagged in retained status
|
||||
propertyValue.Value = json.RawMessage(`"` + model.ContentFlaggingStatusRetained + `"`)
|
||||
_, err = th.Server.propertyService.UpdatePropertyValue(groupId, propertyValue)
|
||||
_, err = th.Server.propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, propertyValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
||||
|
|
@ -1023,7 +1023,7 @@ func TestCanFlagPost(t *testing.T) {
|
|||
|
||||
// Can't fleg when post already flagged in removed status
|
||||
propertyValue.Value = json.RawMessage(`"` + model.ContentFlaggingStatusRemoved + `"`)
|
||||
_, err = th.Server.propertyService.UpdatePropertyValue(groupId, propertyValue)
|
||||
_, err = th.Server.propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, propertyValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
||||
|
|
@ -1069,7 +1069,7 @@ func TestFlagPost(t *testing.T) {
|
|||
require.Nil(t, appErr)
|
||||
|
||||
// Check status property
|
||||
statusValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
statusValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[ContentFlaggingPropertyNameStatus].ID,
|
||||
|
|
@ -1079,7 +1079,7 @@ func TestFlagPost(t *testing.T) {
|
|||
require.Equal(t, `"`+model.ContentFlaggingStatusPending+`"`, string(statusValues[0].Value))
|
||||
|
||||
// Check reporting user property
|
||||
userValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
userValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReportingUserID].ID,
|
||||
|
|
@ -1089,7 +1089,7 @@ func TestFlagPost(t *testing.T) {
|
|||
require.Equal(t, `"`+th.BasicUser2.Id+`"`, string(userValues[0].Value))
|
||||
|
||||
// Check reason property
|
||||
reasonValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
reasonValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReportingReason].ID,
|
||||
|
|
@ -1099,7 +1099,7 @@ func TestFlagPost(t *testing.T) {
|
|||
require.Equal(t, `"spam"`, string(reasonValues[0].Value))
|
||||
|
||||
// Check comment property
|
||||
commentValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReportingComment].ID,
|
||||
|
|
@ -1253,7 +1253,7 @@ func TestFlagPost(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
commentValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReportingComment].ID,
|
||||
|
|
@ -1287,7 +1287,7 @@ func TestFlagPost(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
timeValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
timeValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameReportingTime].ID,
|
||||
|
|
@ -2223,7 +2223,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
require.Nil(t, appErr)
|
||||
|
||||
// Check actor user property
|
||||
actorValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
actorValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameActorUserID].ID,
|
||||
|
|
@ -2233,7 +2233,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
require.Equal(t, `"`+th.SystemAdminUser.Id+`"`, string(actorValues[0].Value))
|
||||
|
||||
// Check actor comment property
|
||||
commentValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
||||
|
|
@ -2243,7 +2243,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
require.Equal(t, `"This post violates community guidelines"`, string(commentValues[0].Value))
|
||||
|
||||
// Check action time property
|
||||
timeValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
timeValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameActionTime].ID,
|
||||
|
|
@ -2309,7 +2309,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
require.Nil(t, appErr)
|
||||
|
||||
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
||||
_, err := th.App.Srv().propertyService.UpdatePropertyValue(groupId, statusValue)
|
||||
_, err := th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
actionRequest := &model.FlagContentActionRequest{
|
||||
|
|
@ -2333,7 +2333,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
require.Nil(t, appErr)
|
||||
|
||||
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
||||
_, err := th.App.Srv().propertyService.UpdatePropertyValue(groupId, statusValue)
|
||||
_, err := th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
actionRequest := &model.FlagContentActionRequest{
|
||||
|
|
@ -2375,7 +2375,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
commentValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
||||
|
|
@ -2403,7 +2403,7 @@ func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|||
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
commentValues, err := th.Server.propertyService.SearchPropertyValues(groupId, model.PropertyValueSearchOpts{
|
||||
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{post.Id},
|
||||
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
||||
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
||||
|
|
|
|||
|
|
@ -21,14 +21,18 @@ const (
|
|||
|
||||
var cpaGroupID string
|
||||
|
||||
func (a *App) CpaGroupID() (string, error) {
|
||||
return getCpaGroupID(a.Srv().propertyAccessService)
|
||||
}
|
||||
|
||||
// ToDo: we should explore moving this to the database cache layer
|
||||
// instead of maintaining the ID cached at the application level
|
||||
func (a *App) CpaGroupID() (string, error) {
|
||||
func getCpaGroupID(service *PropertyAccessService) (string, error) {
|
||||
if cpaGroupID != "" {
|
||||
return cpaGroupID, nil
|
||||
}
|
||||
|
||||
cpaGroup, err := a.Srv().propertyService.RegisterPropertyGroup(model.CustomProfileAttributesPropertyGroupName)
|
||||
cpaGroup, err := service.RegisterPropertyGroup(model.CustomProfileAttributesPropertyGroupName)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "cannot register Custom Profile Attributes property group")
|
||||
}
|
||||
|
|
@ -37,13 +41,13 @@ func (a *App) CpaGroupID() (string, error) {
|
|||
return cpaGroupID, nil
|
||||
}
|
||||
|
||||
func (a *App) GetCPAField(fieldID string) (*model.CPAField, *model.AppError) {
|
||||
func (a *App) GetCPAField(callerId, fieldID string) (*model.CPAField, *model.AppError) {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetCPAField", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
field, err := a.Srv().propertyService.GetPropertyField(groupID, fieldID)
|
||||
field, err := a.Srv().propertyAccessService.GetPropertyField(callerId, groupID, fieldID)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
|
|
@ -61,7 +65,7 @@ func (a *App) GetCPAField(fieldID string) (*model.CPAField, *model.AppError) {
|
|||
return cpaField, nil
|
||||
}
|
||||
|
||||
func (a *App) ListCPAFields() ([]*model.CPAField, *model.AppError) {
|
||||
func (a *App) ListCPAFields(callerID string) ([]*model.CPAField, *model.AppError) {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("ListCPAFields", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
|
|
@ -72,7 +76,7 @@ func (a *App) ListCPAFields() ([]*model.CPAField, *model.AppError) {
|
|||
PerPage: CustomProfileAttributesFieldLimit,
|
||||
}
|
||||
|
||||
fields, err := a.Srv().propertyService.SearchPropertyFields(groupID, opts)
|
||||
fields, err := a.Srv().propertyAccessService.SearchPropertyFields(callerID, groupID, opts)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("ListCPAFields", "app.custom_profile_attributes.search_property_fields.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -94,13 +98,13 @@ func (a *App) ListCPAFields() ([]*model.CPAField, *model.AppError) {
|
|||
return cpaFields, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateCPAField(field *model.CPAField) (*model.CPAField, *model.AppError) {
|
||||
func (a *App) CreateCPAField(callerId string, field *model.CPAField) (*model.CPAField, *model.AppError) {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("CreateCPAField", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
fieldCount, err := a.Srv().propertyService.CountActivePropertyFieldsForGroup(groupID)
|
||||
fieldCount, err := a.Srv().propertyAccessService.CountActivePropertyFieldsForGroup(groupID)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("CreateCPAField", "app.custom_profile_attributes.count_property_fields.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -115,7 +119,7 @@ func (a *App) CreateCPAField(field *model.CPAField) (*model.CPAField, *model.App
|
|||
return nil, appErr
|
||||
}
|
||||
|
||||
newField, err := a.Srv().propertyService.CreatePropertyField(field.ToPropertyField())
|
||||
newField, err := a.Srv().propertyAccessService.CreatePropertyField(callerId, field.ToPropertyField())
|
||||
if err != nil {
|
||||
var appErr *model.AppError
|
||||
switch {
|
||||
|
|
@ -138,8 +142,8 @@ func (a *App) CreateCPAField(field *model.CPAField) (*model.CPAField, *model.App
|
|||
return cpaField, nil
|
||||
}
|
||||
|
||||
func (a *App) PatchCPAField(fieldID string, patch *model.PropertyFieldPatch) (*model.CPAField, *model.AppError) {
|
||||
existingField, appErr := a.GetCPAField(fieldID)
|
||||
func (a *App) PatchCPAField(callerID string, fieldID string, patch *model.PropertyFieldPatch) (*model.CPAField, *model.AppError) {
|
||||
existingField, appErr := a.GetCPAField(callerID, fieldID)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
|
@ -162,7 +166,7 @@ func (a *App) PatchCPAField(fieldID string, patch *model.PropertyFieldPatch) (*m
|
|||
return nil, model.NewAppError("PatchCPAField", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
patchedField, err := a.Srv().propertyService.UpdatePropertyField(groupID, existingField.ToPropertyField())
|
||||
patchedField, err := a.Srv().propertyAccessService.UpdatePropertyField(callerID, groupID, existingField.ToPropertyField())
|
||||
if err != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
|
|
@ -179,7 +183,7 @@ func (a *App) PatchCPAField(fieldID string, patch *model.PropertyFieldPatch) (*m
|
|||
}
|
||||
|
||||
if shouldDeleteValues {
|
||||
if dErr := a.Srv().propertyService.DeletePropertyValuesForField(groupID, cpaField.ID); dErr != nil {
|
||||
if dErr := a.Srv().propertyAccessService.DeletePropertyValuesForField(callerID, groupID, cpaField.ID); dErr != nil {
|
||||
a.Log().Error("Error deleting property values when updating field",
|
||||
mlog.String("fieldID", cpaField.ID),
|
||||
mlog.Err(dErr),
|
||||
|
|
@ -195,13 +199,13 @@ func (a *App) PatchCPAField(fieldID string, patch *model.PropertyFieldPatch) (*m
|
|||
return cpaField, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteCPAField(id string) *model.AppError {
|
||||
func (a *App) DeleteCPAField(callerID string, id string) *model.AppError {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return model.NewAppError("DeleteCPAField", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.Srv().propertyService.DeletePropertyField(groupID, id); err != nil {
|
||||
if err := a.Srv().propertyAccessService.DeletePropertyField(callerID, groupID, id); err != nil {
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
case errors.As(err, &nfErr):
|
||||
|
|
@ -218,14 +222,14 @@ func (a *App) DeleteCPAField(id string) *model.AppError {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ListCPAValues(userID string) ([]*model.PropertyValue, *model.AppError) {
|
||||
func (a *App) ListCPAValues(callerID, targetUserID string) ([]*model.PropertyValue, *model.AppError) {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("ListCPAValues", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
values, err := a.Srv().propertyService.SearchPropertyValues(groupID, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{userID},
|
||||
values, err := a.Srv().propertyAccessService.SearchPropertyValues(callerID, groupID, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{targetUserID},
|
||||
PerPage: CustomProfileAttributesFieldLimit,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -235,13 +239,13 @@ func (a *App) ListCPAValues(userID string) ([]*model.PropertyValue, *model.AppEr
|
|||
return values, nil
|
||||
}
|
||||
|
||||
func (a *App) GetCPAValue(valueID string) (*model.PropertyValue, *model.AppError) {
|
||||
func (a *App) GetCPAValue(callerId, valueID string) (*model.PropertyValue, *model.AppError) {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetCPAValue", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
value, err := a.Srv().propertyService.GetPropertyValue(groupID, valueID)
|
||||
value, err := a.Srv().propertyAccessService.GetPropertyValue(callerId, groupID, valueID)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetCPAValue", "app.custom_profile_attributes.get_property_field.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -249,8 +253,8 @@ func (a *App) GetCPAValue(valueID string) (*model.PropertyValue, *model.AppError
|
|||
return value, nil
|
||||
}
|
||||
|
||||
func (a *App) PatchCPAValue(userID string, fieldID string, value json.RawMessage, allowSynced bool) (*model.PropertyValue, *model.AppError) {
|
||||
values, appErr := a.PatchCPAValues(userID, map[string]json.RawMessage{fieldID: value}, allowSynced)
|
||||
func (a *App) PatchCPAValue(callerID string, userID string, fieldID string, value json.RawMessage, allowSynced bool) (*model.PropertyValue, *model.AppError) {
|
||||
values, appErr := a.PatchCPAValues(callerID, userID, map[string]json.RawMessage{fieldID: value}, allowSynced)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
|
@ -258,7 +262,7 @@ func (a *App) PatchCPAValue(userID string, fieldID string, value json.RawMessage
|
|||
return values[0], nil
|
||||
}
|
||||
|
||||
func (a *App) PatchCPAValues(userID string, fieldValueMap map[string]json.RawMessage, allowSynced bool) ([]*model.PropertyValue, *model.AppError) {
|
||||
func (a *App) PatchCPAValues(callerID string, userID string, fieldValueMap map[string]json.RawMessage, allowSynced bool) ([]*model.PropertyValue, *model.AppError) {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("PatchCPAValues", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
|
|
@ -267,7 +271,7 @@ func (a *App) PatchCPAValues(userID string, fieldValueMap map[string]json.RawMes
|
|||
valuesToUpdate := []*model.PropertyValue{}
|
||||
for fieldID, rawValue := range fieldValueMap {
|
||||
// make sure field exists in this group
|
||||
cpaField, appErr := a.GetCPAField(fieldID)
|
||||
cpaField, appErr := a.GetCPAField(callerID, fieldID)
|
||||
if appErr != nil {
|
||||
return nil, model.NewAppError("PatchCPAValues", "app.custom_profile_attributes.property_field_not_found.app_error", nil, "", http.StatusNotFound).Wrap(appErr)
|
||||
} else if cpaField.DeleteAt > 0 {
|
||||
|
|
@ -293,7 +297,7 @@ func (a *App) PatchCPAValues(userID string, fieldValueMap map[string]json.RawMes
|
|||
valuesToUpdate = append(valuesToUpdate, value)
|
||||
}
|
||||
|
||||
updatedValues, err := a.Srv().propertyService.UpsertPropertyValues(valuesToUpdate)
|
||||
updatedValues, err := a.Srv().propertyAccessService.UpsertPropertyValues(callerID, valuesToUpdate)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("PatchCPAValues", "app.custom_profile_attributes.property_value_upsert.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
|
@ -311,13 +315,13 @@ func (a *App) PatchCPAValues(userID string, fieldValueMap map[string]json.RawMes
|
|||
return updatedValues, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteCPAValues(userID string) *model.AppError {
|
||||
func (a *App) DeleteCPAValues(callerId, userID string) *model.AppError {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return model.NewAppError("DeleteCPAValues", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
if err := a.Srv().propertyService.DeletePropertyValuesForTarget(groupID, "user", userID); err != nil {
|
||||
if err := a.Srv().propertyAccessService.DeletePropertyValuesForTarget(callerId, groupID, "user", userID); err != nil {
|
||||
return model.NewAppError("DeleteCPAValues", "app.custom_profile_attributes.delete_property_values_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ func TestGetCPAField(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
t.Run("should fail when getting a non-existent field", func(t *testing.T) {
|
||||
field, appErr := th.App.GetCPAField(model.NewId())
|
||||
field, appErr := th.App.GetCPAField(anonymousCallerId, model.NewId())
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_not_found.app_error", appErr.Id)
|
||||
require.Empty(t, field)
|
||||
|
|
@ -34,10 +34,10 @@ func TestGetCPAField(t *testing.T) {
|
|||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(field)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, field)
|
||||
require.NoError(t, err)
|
||||
|
||||
fetchedField, appErr := th.App.GetCPAField(createdField.ID)
|
||||
fetchedField, appErr := th.App.GetCPAField(anonymousCallerId, createdField.ID)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_not_found.app_error", appErr.Id)
|
||||
require.Empty(t, fetchedField)
|
||||
|
|
@ -45,18 +45,18 @@ func TestGetCPAField(t *testing.T) {
|
|||
|
||||
t.Run("should get an existing CPA field", func(t *testing.T) {
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Test Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{model.CustomProfileAttributesPropertyAttrsVisibility: model.CustomProfileAttributesVisibilityHidden},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotEmpty(t, createdField.ID)
|
||||
|
||||
fetchedField, appErr := th.App.GetCPAField(createdField.ID)
|
||||
fetchedField, appErr := th.App.GetCPAField(anonymousCallerId, createdField.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, createdField.ID, fetchedField.ID)
|
||||
require.Equal(t, "Test Field", fetchedField.Name)
|
||||
|
|
@ -66,16 +66,16 @@ func TestGetCPAField(t *testing.T) {
|
|||
t.Run("should initialize default attrs when field has nil Attrs", func(t *testing.T) {
|
||||
// Create a field with nil Attrs directly via property service (bypassing CPA validation)
|
||||
field := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Field with nil attrs",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: nil,
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(field)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, field)
|
||||
require.NoError(t, err)
|
||||
|
||||
// GetCPAField should initialize Attrs with defaults
|
||||
fetchedField, appErr := th.App.GetCPAField(createdField.ID)
|
||||
fetchedField, appErr := th.App.GetCPAField(anonymousCallerId, createdField.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, model.CustomProfileAttributesVisibilityDefault, fetchedField.Attrs.Visibility)
|
||||
require.Equal(t, float64(0), fetchedField.Attrs.SortOrder)
|
||||
|
|
@ -84,16 +84,16 @@ func TestGetCPAField(t *testing.T) {
|
|||
t.Run("should initialize default attrs when field has empty Attrs", func(t *testing.T) {
|
||||
// Create a field with empty Attrs directly via property service
|
||||
field := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Field with empty attrs",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{},
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(field)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, field)
|
||||
require.NoError(t, err)
|
||||
|
||||
// GetCPAField should add missing default attrs
|
||||
fetchedField, appErr := th.App.GetCPAField(createdField.ID)
|
||||
fetchedField, appErr := th.App.GetCPAField(anonymousCallerId, createdField.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, model.CustomProfileAttributesVisibilityDefault, fetchedField.Attrs.Visibility)
|
||||
require.Equal(t, float64(0), fetchedField.Attrs.SortOrder)
|
||||
|
|
@ -102,7 +102,7 @@ func TestGetCPAField(t *testing.T) {
|
|||
t.Run("should validate LDAP/SAML synced fields", func(t *testing.T) {
|
||||
// Create LDAP synced field
|
||||
ldapField, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "LDAP Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
|
|
@ -110,12 +110,12 @@ func TestGetCPAField(t *testing.T) {
|
|||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdLDAPField, appErr := th.App.CreateCPAField(ldapField)
|
||||
createdLDAPField, appErr := th.App.CreateCPAField(anonymousCallerId, ldapField)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Create SAML synced field
|
||||
samlField, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "SAML Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
|
|
@ -123,31 +123,31 @@ func TestGetCPAField(t *testing.T) {
|
|||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdSAMLField, appErr := th.App.CreateCPAField(samlField)
|
||||
createdSAMLField, appErr := th.App.CreateCPAField(anonymousCallerId, samlField)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Test with allowSynced=false
|
||||
userID := model.NewId()
|
||||
|
||||
// Test LDAP field
|
||||
_, appErr = th.App.PatchCPAValue(userID, createdLDAPField.ID, json.RawMessage(`"test value"`), false)
|
||||
_, appErr = th.App.PatchCPAValue(anonymousCallerId, userID, createdLDAPField.ID, json.RawMessage(`"test value"`), false)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_is_synced.app_error", appErr.Id)
|
||||
|
||||
// Test SAML field
|
||||
_, appErr = th.App.PatchCPAValue(userID, createdSAMLField.ID, json.RawMessage(`"test value"`), false)
|
||||
_, appErr = th.App.PatchCPAValue(anonymousCallerId, userID, createdSAMLField.ID, json.RawMessage(`"test value"`), false)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_is_synced.app_error", appErr.Id)
|
||||
|
||||
// Test with allowSynced=true
|
||||
// LDAP field should work
|
||||
patchedValue, appErr := th.App.PatchCPAValue(userID, createdLDAPField.ID, json.RawMessage(`"test value"`), true)
|
||||
patchedValue, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdLDAPField.ID, json.RawMessage(`"test value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, patchedValue)
|
||||
require.Equal(t, json.RawMessage(`"test value"`), patchedValue.Value)
|
||||
|
||||
// SAML field should work
|
||||
patchedValue, appErr = th.App.PatchCPAValue(userID, createdSAMLField.ID, json.RawMessage(`"test value"`), true)
|
||||
patchedValue, appErr = th.App.PatchCPAValue(anonymousCallerId, userID, createdSAMLField.ID, json.RawMessage(`"test value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, patchedValue)
|
||||
require.Equal(t, json.RawMessage(`"test value"`), patchedValue.Value)
|
||||
|
|
@ -158,18 +158,18 @@ func TestListCPAFields(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
t.Run("should list the CPA property fields", func(t *testing.T) {
|
||||
field1 := model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Field 1",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{model.CustomProfileAttributesPropertyAttrsSortOrder: 1},
|
||||
}
|
||||
|
||||
_, err := th.App.Srv().propertyService.CreatePropertyField(&field1)
|
||||
_, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, &field1)
|
||||
require.NoError(t, err)
|
||||
|
||||
field2 := &model.PropertyField{
|
||||
|
|
@ -177,19 +177,19 @@ func TestListCPAFields(t *testing.T) {
|
|||
Name: "Field 2",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
_, err = th.App.Srv().propertyService.CreatePropertyField(field2)
|
||||
_, err = th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, field2)
|
||||
require.NoError(t, err)
|
||||
|
||||
field3 := model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Field 3",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{model.CustomProfileAttributesPropertyAttrsSortOrder: 0},
|
||||
}
|
||||
_, err = th.App.Srv().propertyService.CreatePropertyField(&field3)
|
||||
_, err = th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, &field3)
|
||||
require.NoError(t, err)
|
||||
|
||||
fields, appErr := th.App.ListCPAFields()
|
||||
fields, appErr := th.App.ListCPAFields(anonymousCallerId)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fields, 2)
|
||||
require.Equal(t, "Field 3", fields[0].Name)
|
||||
|
|
@ -199,26 +199,26 @@ func TestListCPAFields(t *testing.T) {
|
|||
t.Run("should initialize default attrs for fields with nil or empty Attrs", func(t *testing.T) {
|
||||
// Create a field with nil Attrs
|
||||
fieldWithNilAttrs := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Field with nil attrs",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: nil,
|
||||
}
|
||||
_, err := th.App.Srv().propertyService.CreatePropertyField(fieldWithNilAttrs)
|
||||
_, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, fieldWithNilAttrs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a field with empty Attrs
|
||||
fieldWithEmptyAttrs := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Field with empty attrs",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{},
|
||||
}
|
||||
_, err = th.App.Srv().propertyService.CreatePropertyField(fieldWithEmptyAttrs)
|
||||
_, err = th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, fieldWithEmptyAttrs)
|
||||
require.NoError(t, err)
|
||||
|
||||
// ListCPAFields should initialize Attrs with defaults
|
||||
fields, appErr := th.App.ListCPAFields()
|
||||
fields, appErr := th.App.ListCPAFields(anonymousCallerId)
|
||||
require.Nil(t, appErr)
|
||||
require.NotEmpty(t, fields)
|
||||
|
||||
|
|
@ -239,7 +239,7 @@ func TestListCPAFields(t *testing.T) {
|
|||
Attrs: model.StringInterface{}, // Empty attrs - no visibility or sort_order
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdFieldMinimal, appErr := th.App.CreateCPAField(fieldMinimal)
|
||||
createdFieldMinimal, appErr := th.App.CreateCPAField(anonymousCallerId, fieldMinimal)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdFieldMinimal)
|
||||
|
||||
|
|
@ -253,12 +253,12 @@ func TestListCPAFields(t *testing.T) {
|
|||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdFieldNormal, appErr := th.App.CreateCPAField(fieldNormal)
|
||||
createdFieldNormal, appErr := th.App.CreateCPAField(anonymousCallerId, fieldNormal)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, createdFieldNormal)
|
||||
|
||||
// List all fields
|
||||
fields, appErr := th.App.ListCPAFields()
|
||||
fields, appErr := th.App.ListCPAFields(anonymousCallerId)
|
||||
require.Nil(t, appErr)
|
||||
require.NotEmpty(t, fields)
|
||||
|
||||
|
|
@ -288,14 +288,14 @@ func TestCreateCPAField(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
t.Run("should fail if the field is not valid", func(t *testing.T) {
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{Name: model.NewId()})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, err := th.App.CreateCPAField(field)
|
||||
createdField, err := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, createdField)
|
||||
})
|
||||
|
|
@ -308,27 +308,27 @@ func TestCreateCPAField(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, cpaGroupID, createdField.GroupID)
|
||||
require.Equal(t, cpaID, createdField.GroupID)
|
||||
})
|
||||
|
||||
t.Run("should correctly create a CPA field", func(t *testing.T) {
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{model.CustomProfileAttributesPropertyAttrsVisibility: model.CustomProfileAttributesVisibilityHidden},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, createdField.ID)
|
||||
require.Equal(t, cpaGroupID, createdField.GroupID)
|
||||
require.Equal(t, cpaID, createdField.GroupID)
|
||||
require.Equal(t, model.CustomProfileAttributesVisibilityHidden, createdField.Attrs.Visibility)
|
||||
|
||||
fetchedField, gErr := th.App.Srv().propertyService.GetPropertyField("", createdField.ID)
|
||||
fetchedField, gErr := th.App.PropertyAccessService().GetPropertyField(anonymousCallerId, "", createdField.ID)
|
||||
require.NoError(t, gErr)
|
||||
require.Equal(t, field.Name, fetchedField.Name)
|
||||
require.NotZero(t, fetchedField.CreateAt)
|
||||
|
|
@ -338,7 +338,7 @@ func TestCreateCPAField(t *testing.T) {
|
|||
t.Run("should create CPA field with DeleteAt set to 0 even if input has non-zero DeleteAt", func(t *testing.T) {
|
||||
// Create a CPAField with DeleteAt != 0
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{model.CustomProfileAttributesPropertyAttrsVisibility: model.CustomProfileAttributesVisibilityHidden},
|
||||
|
|
@ -349,16 +349,16 @@ func TestCreateCPAField(t *testing.T) {
|
|||
field.DeleteAt = time.Now().UnixMilli()
|
||||
require.NotZero(t, field.DeleteAt, "Pre-condition: field should have non-zero DeleteAt")
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, createdField.ID)
|
||||
require.Equal(t, cpaGroupID, createdField.GroupID)
|
||||
require.Equal(t, cpaID, createdField.GroupID)
|
||||
|
||||
// Verify that DeleteAt has been reset to 0
|
||||
require.Zero(t, createdField.DeleteAt, "DeleteAt should be 0 after creation")
|
||||
|
||||
// Double-check by fetching the field from the database
|
||||
fetchedField, gErr := th.App.Srv().propertyService.GetPropertyField("", createdField.ID)
|
||||
fetchedField, gErr := th.App.PropertyAccessService().GetPropertyField(anonymousCallerId, "", createdField.ID)
|
||||
require.NoError(t, gErr)
|
||||
require.Zero(t, fetchedField.DeleteAt, "DeleteAt should be 0 in database")
|
||||
})
|
||||
|
|
@ -375,7 +375,7 @@ func TestCreateCPAField(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, createdField.ID)
|
||||
}
|
||||
|
|
@ -387,7 +387,7 @@ func TestCreateCPAField(t *testing.T) {
|
|||
Type: model.PropertyFieldTypeText,
|
||||
},
|
||||
}
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, http.StatusUnprocessableEntity, appErr.StatusCode)
|
||||
require.Zero(t, createdField)
|
||||
|
|
@ -395,12 +395,12 @@ func TestCreateCPAField(t *testing.T) {
|
|||
|
||||
t.Run("deleted fields should not count for the limit", func(t *testing.T) {
|
||||
// we retrieve the list of fields and check we've reached the limit
|
||||
fields, appErr := th.App.ListCPAFields()
|
||||
fields, appErr := th.App.ListCPAFields(anonymousCallerId)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, fields, CustomProfileAttributesFieldLimit)
|
||||
|
||||
// then we delete one field
|
||||
require.Nil(t, th.App.DeleteCPAField(fields[0].ID))
|
||||
require.Nil(t, th.App.DeleteCPAField(anonymousCallerId, fields[0].ID))
|
||||
|
||||
// creating a new one should work now
|
||||
field := &model.CPAField{
|
||||
|
|
@ -409,7 +409,7 @@ func TestCreateCPAField(t *testing.T) {
|
|||
Type: model.PropertyFieldTypeText,
|
||||
},
|
||||
}
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, createdField.ID)
|
||||
})
|
||||
|
|
@ -420,18 +420,18 @@ func TestPatchCPAField(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
newField, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{model.CustomProfileAttributesPropertyAttrsVisibility: model.CustomProfileAttributesVisibilityHidden},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(newField)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, newField)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
patch := &model.PropertyFieldPatch{
|
||||
|
|
@ -442,7 +442,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("should fail if the field doesn't exist", func(t *testing.T) {
|
||||
updatedField, appErr := th.App.PatchCPAField(model.NewId(), patch)
|
||||
updatedField, appErr := th.App.PatchCPAField(anonymousCallerId, model.NewId(), patch)
|
||||
require.NotNil(t, appErr)
|
||||
require.Empty(t, updatedField)
|
||||
})
|
||||
|
|
@ -454,10 +454,10 @@ func TestPatchCPAField(t *testing.T) {
|
|||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
|
||||
field, err := th.App.Srv().propertyService.CreatePropertyField(newField)
|
||||
field, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, newField)
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedField, uErr := th.App.PatchCPAField(field.ID, patch)
|
||||
updatedField, uErr := th.App.PatchCPAField(anonymousCallerId, field.ID, patch)
|
||||
require.NotNil(t, uErr)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_not_found.app_error", uErr.Id)
|
||||
require.Empty(t, updatedField)
|
||||
|
|
@ -466,7 +466,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
t.Run("should correctly patch the CPA property field", func(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond) // ensure the UpdateAt is different than CreateAt
|
||||
|
||||
updatedField, appErr := th.App.PatchCPAField(createdField.ID, patch)
|
||||
updatedField, appErr := th.App.PatchCPAField(anonymousCallerId, createdField.ID, patch)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, createdField.ID, updatedField.ID)
|
||||
require.Equal(t, "Patched name", updatedField.Name)
|
||||
|
|
@ -479,7 +479,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
t.Run("should preserve option IDs when patching select field options", func(t *testing.T) {
|
||||
// Create a select field with options
|
||||
selectField, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Select Field",
|
||||
Type: model.PropertyFieldTypeSelect,
|
||||
Attrs: map[string]any{
|
||||
|
|
@ -497,7 +497,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdSelectField, appErr := th.App.CreateCPAField(selectField)
|
||||
createdSelectField, appErr := th.App.CreateCPAField(anonymousCallerId, selectField)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Get the original option IDs
|
||||
|
|
@ -530,7 +530,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
}),
|
||||
}
|
||||
|
||||
updatedSelectField, appErr := th.App.PatchCPAField(createdSelectField.ID, selectPatch)
|
||||
updatedSelectField, appErr := th.App.PatchCPAField(anonymousCallerId, createdSelectField.ID, selectPatch)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
updatedOptions := updatedSelectField.Attrs.Options
|
||||
|
|
@ -552,7 +552,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
t.Run("Should not delete the values of a field after patching it if the type has not changed", func(t *testing.T) {
|
||||
// Create a select field with options
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Select Field with values",
|
||||
Type: model.PropertyFieldTypeSelect,
|
||||
Attrs: model.StringInterface{
|
||||
|
|
@ -569,7 +569,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Get the option IDs
|
||||
|
|
@ -580,7 +580,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
|
||||
// Create values for this field using the first option
|
||||
userID := model.NewId()
|
||||
value, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(fmt.Sprintf(`"%s"`, optionID)), false)
|
||||
value, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(fmt.Sprintf(`"%s"`, optionID)), false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, value)
|
||||
|
||||
|
|
@ -605,13 +605,13 @@ func TestPatchCPAField(t *testing.T) {
|
|||
},
|
||||
}),
|
||||
}
|
||||
updatedField, appErr := th.App.PatchCPAField(createdField.ID, patch)
|
||||
updatedField, appErr := th.App.PatchCPAField(anonymousCallerId, createdField.ID, patch)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, "Updated select field name", updatedField.Name)
|
||||
require.Equal(t, model.PropertyFieldTypeSelect, updatedField.Type)
|
||||
|
||||
// Verify values still exist
|
||||
values, appErr := th.App.ListCPAValues(userID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, values, 1)
|
||||
require.Equal(t, json.RawMessage(fmt.Sprintf(`"%s"`, optionID)), values[0].Value)
|
||||
|
|
@ -620,7 +620,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
t.Run("Should delete the values of a field after patching it if the type has changed", func(t *testing.T) {
|
||||
// Create a select field with options
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: "Select Field with type change",
|
||||
Type: model.PropertyFieldTypeSelect,
|
||||
Attrs: model.StringInterface{
|
||||
|
|
@ -637,7 +637,7 @@ func TestPatchCPAField(t *testing.T) {
|
|||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Get the option IDs
|
||||
|
|
@ -648,12 +648,12 @@ func TestPatchCPAField(t *testing.T) {
|
|||
|
||||
// Create values for this field
|
||||
userID := model.NewId()
|
||||
value, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(fmt.Sprintf(`"%s"`, optionID)), false)
|
||||
value, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(fmt.Sprintf(`"%s"`, optionID)), false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, value)
|
||||
|
||||
// Verify value exists before type change
|
||||
values, appErr := th.App.ListCPAValues(userID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, values, 1)
|
||||
|
||||
|
|
@ -661,12 +661,12 @@ func TestPatchCPAField(t *testing.T) {
|
|||
patch := &model.PropertyFieldPatch{
|
||||
Type: model.NewPointer(model.PropertyFieldTypeText),
|
||||
}
|
||||
updatedField, appErr := th.App.PatchCPAField(createdField.ID, patch)
|
||||
updatedField, appErr := th.App.PatchCPAField(anonymousCallerId, createdField.ID, patch)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, model.PropertyFieldTypeText, updatedField.Type)
|
||||
|
||||
// Verify values have been deleted
|
||||
values, appErr = th.App.ListCPAValues(userID)
|
||||
values, appErr = th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Empty(t, values)
|
||||
})
|
||||
|
|
@ -676,34 +676,34 @@ func TestDeleteCPAField(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
newField, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createdField, appErr := th.App.CreateCPAField(newField)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, newField)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
for i := range 3 {
|
||||
newValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: model.PropertyValueTargetTypeUser,
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
FieldID: createdField.ID,
|
||||
Value: json.RawMessage(fmt.Sprintf(`"Value %d"`, i)),
|
||||
}
|
||||
value, err := th.App.Srv().propertyService.CreatePropertyValue(newValue)
|
||||
value, err := th.App.PropertyAccessService().CreatePropertyValue(anonymousCallerId, newValue)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, value.ID)
|
||||
}
|
||||
|
||||
t.Run("should fail if the field doesn't exist", func(t *testing.T) {
|
||||
err := th.App.DeleteCPAField(model.NewId())
|
||||
err := th.App.DeleteCPAField(anonymousCallerId, model.NewId())
|
||||
require.NotNil(t, err)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_delete.app_error", err.Id)
|
||||
})
|
||||
|
|
@ -714,10 +714,10 @@ func TestDeleteCPAField(t *testing.T) {
|
|||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
field, err := th.App.Srv().propertyService.CreatePropertyField(newField)
|
||||
field, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, newField)
|
||||
require.NoError(t, err)
|
||||
|
||||
dErr := th.App.DeleteCPAField(field.ID)
|
||||
dErr := th.App.DeleteCPAField(anonymousCallerId, field.ID)
|
||||
require.NotNil(t, dErr)
|
||||
require.Equal(t, "app.custom_profile_attributes.property_field_delete.app_error", dErr.Id)
|
||||
})
|
||||
|
|
@ -725,25 +725,25 @@ func TestDeleteCPAField(t *testing.T) {
|
|||
t.Run("should correctly delete the field", func(t *testing.T) {
|
||||
// check that we have the associated values to the field prior deletion
|
||||
opts := model.PropertyValueSearchOpts{PerPage: 10, FieldID: createdField.ID}
|
||||
values, err := th.App.Srv().propertyService.SearchPropertyValues(cpaGroupID, opts)
|
||||
values, err := th.App.PropertyAccessService().SearchPropertyValues(anonymousCallerId, cpaID, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 3)
|
||||
|
||||
// delete the field
|
||||
require.Nil(t, th.App.DeleteCPAField(createdField.ID))
|
||||
require.Nil(t, th.App.DeleteCPAField(anonymousCallerId, createdField.ID))
|
||||
|
||||
// check that it is marked as deleted
|
||||
fetchedField, err := th.App.Srv().propertyService.GetPropertyField("", createdField.ID)
|
||||
fetchedField, err := th.App.PropertyAccessService().GetPropertyField(anonymousCallerId, "", createdField.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, fetchedField.DeleteAt)
|
||||
|
||||
// ensure that the associated fields have been marked as deleted too
|
||||
values, err = th.App.Srv().propertyService.SearchPropertyValues(cpaGroupID, opts)
|
||||
values, err = th.App.PropertyAccessService().SearchPropertyValues(anonymousCallerId, cpaID, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 0)
|
||||
|
||||
opts.IncludeDeleted = true
|
||||
values, err = th.App.Srv().propertyService.SearchPropertyValues(cpaGroupID, opts)
|
||||
values, err = th.App.PropertyAccessService().SearchPropertyValues(anonymousCallerId, cpaID, opts)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 3)
|
||||
for _, value := range values {
|
||||
|
|
@ -756,13 +756,20 @@ func TestGetCPAValue(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
fieldID := model.NewId()
|
||||
field := &model.PropertyField{
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, field)
|
||||
require.NoError(t, err)
|
||||
fieldID := createdField.ID
|
||||
|
||||
t.Run("should fail if the value doesn't exist", func(t *testing.T) {
|
||||
pv, appErr := th.App.GetCPAValue(model.NewId())
|
||||
pv, appErr := th.App.GetCPAValue(anonymousCallerId, model.NewId())
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, pv)
|
||||
})
|
||||
|
|
@ -775,10 +782,11 @@ func TestGetCPAValue(t *testing.T) {
|
|||
FieldID: fieldID,
|
||||
Value: json.RawMessage(`"Value"`),
|
||||
}
|
||||
propertyValue, err := th.App.Srv().propertyService.CreatePropertyValue(propertyValue)
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyValue(anonymousCallerId, propertyValue)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, created)
|
||||
|
||||
pv, appErr := th.App.GetCPAValue(propertyValue.ID)
|
||||
pv, appErr := th.App.GetCPAValue(anonymousCallerId, created.ID)
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, pv)
|
||||
})
|
||||
|
|
@ -787,38 +795,38 @@ func TestGetCPAValue(t *testing.T) {
|
|||
propertyValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: model.PropertyValueTargetTypeUser,
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
FieldID: fieldID,
|
||||
Value: json.RawMessage(`"Value"`),
|
||||
}
|
||||
propertyValue, err := th.App.Srv().propertyService.CreatePropertyValue(propertyValue)
|
||||
propertyValue, err := th.App.PropertyAccessService().CreatePropertyValue(anonymousCallerId, propertyValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
pv, appErr := th.App.GetCPAValue(propertyValue.ID)
|
||||
pv, appErr := th.App.GetCPAValue(anonymousCallerId, propertyValue.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, pv)
|
||||
})
|
||||
|
||||
t.Run("should handle array values correctly", func(t *testing.T) {
|
||||
arrayField := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeMultiselect,
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(arrayField)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, arrayField)
|
||||
require.NoError(t, err)
|
||||
|
||||
propertyValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: model.PropertyValueTargetTypeUser,
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
FieldID: createdField.ID,
|
||||
Value: json.RawMessage(`["option1", "option2", "option3"]`),
|
||||
}
|
||||
propertyValue, err = th.App.Srv().propertyService.CreatePropertyValue(propertyValue)
|
||||
propertyValue, err = th.App.PropertyAccessService().CreatePropertyValue(anonymousCallerId, propertyValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
pv, appErr := th.App.GetCPAValue(propertyValue.ID)
|
||||
pv, appErr := th.App.GetCPAValue(anonymousCallerId, propertyValue.ID)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, pv)
|
||||
var arrayValues []string
|
||||
|
|
@ -833,13 +841,13 @@ func TestListCPAValues(t *testing.T) {
|
|||
cfg.FeatureFlags.CustomProfileAttributes = true
|
||||
}).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
userID := model.NewId()
|
||||
|
||||
t.Run("should return empty list when user has no values", func(t *testing.T) {
|
||||
values, appErr := th.App.ListCPAValues(userID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Empty(t, values)
|
||||
})
|
||||
|
|
@ -849,27 +857,27 @@ func TestListCPAValues(t *testing.T) {
|
|||
|
||||
for i := 1; i <= CustomProfileAttributesFieldLimit; i++ {
|
||||
field := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: fmt.Sprintf("Field %d", i),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
_, err := th.App.Srv().propertyService.CreatePropertyField(field)
|
||||
_, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, field)
|
||||
require.NoError(t, err)
|
||||
|
||||
value := &model.PropertyValue{
|
||||
TargetID: userID,
|
||||
TargetType: model.PropertyValueTargetTypeUser,
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
FieldID: field.ID,
|
||||
Value: json.RawMessage(fmt.Sprintf(`"Value %d"`, i)),
|
||||
}
|
||||
_, err = th.App.Srv().propertyService.CreatePropertyValue(value)
|
||||
_, err = th.App.PropertyAccessService().CreatePropertyValue(anonymousCallerId, value)
|
||||
require.NoError(t, err)
|
||||
expectedValues = append(expectedValues, value.Value)
|
||||
}
|
||||
|
||||
// List values for original user
|
||||
values, appErr := th.App.ListCPAValues(userID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, values, CustomProfileAttributesFieldLimit)
|
||||
|
||||
|
|
@ -877,7 +885,7 @@ func TestListCPAValues(t *testing.T) {
|
|||
for i, value := range values {
|
||||
require.Equal(t, userID, value.TargetID)
|
||||
require.Equal(t, "user", value.TargetType)
|
||||
require.Equal(t, cpaGroupID, value.GroupID)
|
||||
require.Equal(t, cpaID, value.GroupID)
|
||||
actualValues[i] = value.Value
|
||||
}
|
||||
require.ElementsMatch(t, expectedValues, actualValues)
|
||||
|
|
@ -888,33 +896,33 @@ func TestPatchCPAValue(t *testing.T) {
|
|||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
t.Run("should fail if the field doesn't exist", func(t *testing.T) {
|
||||
invalidFieldID := model.NewId()
|
||||
_, appErr := th.App.PatchCPAValue(model.NewId(), invalidFieldID, json.RawMessage(`"fieldValue"`), true)
|
||||
_, appErr := th.App.PatchCPAValue(anonymousCallerId, model.NewId(), invalidFieldID, json.RawMessage(`"fieldValue"`), true)
|
||||
require.NotNil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("should create value if new field value", func(t *testing.T) {
|
||||
newField := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(newField)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, newField)
|
||||
require.NoError(t, err)
|
||||
|
||||
userID := model.NewId()
|
||||
patchedValue, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(`"test value"`), true)
|
||||
patchedValue, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(`"test value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, patchedValue)
|
||||
require.Equal(t, json.RawMessage(`"test value"`), patchedValue.Value)
|
||||
require.Equal(t, userID, patchedValue.TargetID)
|
||||
|
||||
t.Run("should correctly patch the CPA property value", func(t *testing.T) {
|
||||
patch2, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(`"new patched value"`), true)
|
||||
patch2, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(`"new patched value"`), true)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, patch2)
|
||||
require.Equal(t, patchedValue.ID, patch2.ID)
|
||||
|
|
@ -925,17 +933,17 @@ func TestPatchCPAValue(t *testing.T) {
|
|||
|
||||
t.Run("should fail if field is deleted", func(t *testing.T) {
|
||||
newField := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(newField)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, newField)
|
||||
require.NoError(t, err)
|
||||
err = th.App.Srv().propertyService.DeletePropertyField(cpaGroupID, createdField.ID)
|
||||
err = th.App.PropertyAccessService().DeletePropertyField(anonymousCallerId, cpaID, createdField.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
userID := model.NewId()
|
||||
patchedValue, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(`"test value"`), true)
|
||||
patchedValue, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(`"test value"`), true)
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, patchedValue)
|
||||
})
|
||||
|
|
@ -943,7 +951,7 @@ func TestPatchCPAValue(t *testing.T) {
|
|||
t.Run("should handle array values correctly", func(t *testing.T) {
|
||||
optionsID := []string{model.NewId(), model.NewId(), model.NewId(), model.NewId()}
|
||||
arrayField := &model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeMultiselect,
|
||||
Attrs: model.StringInterface{
|
||||
|
|
@ -955,14 +963,14 @@ func TestPatchCPAValue(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
createdField, err := th.App.Srv().propertyService.CreatePropertyField(arrayField)
|
||||
createdField, err := th.App.PropertyAccessService().CreatePropertyField(anonymousCallerId, arrayField)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a JSON array with option IDs (not names)
|
||||
optionJSON := fmt.Sprintf(`["%s", "%s", "%s"]`, optionsID[0], optionsID[1], optionsID[2])
|
||||
|
||||
userID := model.NewId()
|
||||
patchedValue, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(optionJSON), true)
|
||||
patchedValue, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(optionJSON), true)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, patchedValue)
|
||||
var arrayValues []string
|
||||
|
|
@ -972,7 +980,7 @@ func TestPatchCPAValue(t *testing.T) {
|
|||
|
||||
// Update array values with valid option IDs
|
||||
updatedOptionJSON := fmt.Sprintf(`["%s", "%s"]`, optionsID[1], optionsID[3])
|
||||
updatedValue, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(updatedOptionJSON), true)
|
||||
updatedValue, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(updatedOptionJSON), true)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, updatedValue)
|
||||
require.Equal(t, patchedValue.ID, updatedValue.ID)
|
||||
|
|
@ -986,21 +994,21 @@ func TestPatchCPAValue(t *testing.T) {
|
|||
invalidID := model.NewId()
|
||||
invalidOptionJSON := fmt.Sprintf(`["%s", "%s"]`, optionsID[0], invalidID)
|
||||
|
||||
invalidValue, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(invalidOptionJSON), true)
|
||||
invalidValue, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(invalidOptionJSON), true)
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, invalidValue)
|
||||
require.Equal(t, "app.custom_profile_attributes.validate_value.app_error", appErr.Id)
|
||||
|
||||
// Test with completely invalid JSON format
|
||||
invalidJSON := `[not valid json]`
|
||||
invalidValue, appErr = th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(invalidJSON), true)
|
||||
invalidValue, appErr = th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(invalidJSON), true)
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, invalidValue)
|
||||
require.Equal(t, "app.custom_profile_attributes.validate_value.app_error", appErr.Id)
|
||||
|
||||
// Test with wrong data type (sending string instead of array)
|
||||
wrongTypeJSON := `"not an array"`
|
||||
invalidValue, appErr = th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(wrongTypeJSON), true)
|
||||
invalidValue, appErr = th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(wrongTypeJSON), true)
|
||||
require.NotNil(t, appErr)
|
||||
require.Nil(t, invalidValue)
|
||||
require.Equal(t, "app.custom_profile_attributes.validate_value.app_error", appErr.Id)
|
||||
|
|
@ -1014,7 +1022,7 @@ func TestDeleteCPAValues(t *testing.T) {
|
|||
cfg.FeatureFlags.CustomProfileAttributes = true
|
||||
}).InitBasic(t)
|
||||
|
||||
cpaGroupID, cErr := th.App.CpaGroupID()
|
||||
cpaID, cErr := th.App.CpaGroupID()
|
||||
require.NoError(t, cErr)
|
||||
|
||||
userID := model.NewId()
|
||||
|
|
@ -1024,56 +1032,56 @@ func TestDeleteCPAValues(t *testing.T) {
|
|||
var createdFields []*model.CPAField
|
||||
for i := 1; i <= 3; i++ {
|
||||
field, err := model.NewCPAFieldFromPropertyField(&model.PropertyField{
|
||||
GroupID: cpaGroupID,
|
||||
GroupID: cpaID,
|
||||
Name: fmt.Sprintf("Field %d", i),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
createdField, appErr := th.App.CreateCPAField(field)
|
||||
createdField, appErr := th.App.CreateCPAField(anonymousCallerId, field)
|
||||
require.Nil(t, appErr)
|
||||
createdFields = append(createdFields, createdField)
|
||||
|
||||
// Create a value for this field
|
||||
value, appErr := th.App.PatchCPAValue(userID, createdField.ID, json.RawMessage(fmt.Sprintf(`"Value %d"`, i)), false)
|
||||
value, appErr := th.App.PatchCPAValue(anonymousCallerId, userID, createdField.ID, json.RawMessage(fmt.Sprintf(`"Value %d"`, i)), false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, value)
|
||||
}
|
||||
|
||||
// Verify values exist before deletion
|
||||
values, appErr := th.App.ListCPAValues(userID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, values, 3)
|
||||
|
||||
// Test deleting values for user
|
||||
t.Run("should delete all values for a user", func(t *testing.T) {
|
||||
appErr := th.App.DeleteCPAValues(userID)
|
||||
appErr := th.App.DeleteCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Verify values are gone
|
||||
values, appErr := th.App.ListCPAValues(userID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
require.Empty(t, values)
|
||||
})
|
||||
|
||||
t.Run("should handle deleting values for a user with no values", func(t *testing.T) {
|
||||
appErr := th.App.DeleteCPAValues(otherUserID)
|
||||
appErr := th.App.DeleteCPAValues(anonymousCallerId, otherUserID)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("should not affect values for other users", func(t *testing.T) {
|
||||
// Create values for another user
|
||||
for _, field := range createdFields {
|
||||
value, appErr := th.App.PatchCPAValue(otherUserID, field.ID, json.RawMessage(`"Other user value"`), false)
|
||||
value, appErr := th.App.PatchCPAValue(anonymousCallerId, otherUserID, field.ID, json.RawMessage(`"Other user value"`), false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, value)
|
||||
}
|
||||
|
||||
// Delete values for original user
|
||||
appErr := th.App.DeleteCPAValues(userID)
|
||||
appErr := th.App.DeleteCPAValues(anonymousCallerId, userID)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Verify other user's values still exist
|
||||
values, appErr := th.App.ListCPAValues(otherUserID)
|
||||
values, appErr := th.App.ListCPAValues(anonymousCallerId, otherUserID)
|
||||
require.Nil(t, appErr)
|
||||
require.Len(t, values, 3)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -617,14 +617,14 @@ func (s *Server) doSetupContentFlaggingProperties() error {
|
|||
}
|
||||
|
||||
// RegisterPropertyGroup is idempotent, so no need to check if group is already registered
|
||||
group, err := s.propertyService.RegisterPropertyGroup(model.ContentFlaggingGroupName)
|
||||
group, err := s.propertyAccessService.RegisterPropertyGroup(model.ContentFlaggingGroupName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register Content Flagging group: %w", err)
|
||||
}
|
||||
|
||||
// Using page size of 100 and not iterating through all pages because the
|
||||
// number of fields are static and defined here and not expected to be more than 100 for now.
|
||||
existingProperties, appErr := s.propertyService.SearchPropertyFields(group.ID, model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
existingProperties, appErr := s.propertyAccessService.SearchPropertyFields(anonymousCallerId, group.ID, model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
if appErr != nil {
|
||||
return fmt.Errorf("failed to search for existing content flagging properties: %w", appErr)
|
||||
}
|
||||
|
|
@ -718,13 +718,13 @@ func (s *Server) doSetupContentFlaggingProperties() error {
|
|||
}
|
||||
|
||||
for _, property := range propertiesToCreate {
|
||||
if _, err := s.propertyService.CreatePropertyField(property); err != nil {
|
||||
if _, err := s.propertyAccessService.CreatePropertyField(anonymousCallerId, property); err != nil {
|
||||
return fmt.Errorf("failed to create content flagging property: %q, error: %w", property.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(propertiesToUpdate) > 0 {
|
||||
if _, err := s.propertyService.UpdatePropertyFields(group.ID, propertiesToUpdate); err != nil {
|
||||
if _, err := s.propertyAccessService.UpdatePropertyFields(anonymousCallerId, group.ID, propertiesToUpdate); err != nil {
|
||||
return fmt.Errorf("failed to update content flagging property fields: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ func TestDoSetupContentFlaggingProperties(t *testing.T) {
|
|||
//settings, setting up the store and initializing services used in store such as property services.
|
||||
th := Setup(t)
|
||||
|
||||
group, err := th.Server.propertyService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
group, err := th.Server.propertyAccessService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, group)
|
||||
require.Equal(t, model.ContentFlaggingGroupName, group.Name)
|
||||
|
||||
propertyFields, err := th.Server.propertyService.SearchPropertyFields(group.ID, model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
propertyFields, err := th.Server.propertyAccessService.SearchPropertyFields(anonymousCallerId, group.ID, model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, propertyFields, 11)
|
||||
|
||||
|
|
@ -43,11 +43,11 @@ func TestDoSetupContentFlaggingProperties(t *testing.T) {
|
|||
err = th.Server.doSetupContentFlaggingProperties()
|
||||
require.NoError(t, err)
|
||||
|
||||
group, err := th.Server.propertyService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
group, err := th.Server.propertyAccessService.GetPropertyGroup(model.ContentFlaggingGroupName)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, model.ContentFlaggingGroupName, group.Name)
|
||||
|
||||
propertyFields, err := th.Server.propertyService.SearchPropertyFields(group.ID, model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
propertyFields, err := th.Server.propertyAccessService.SearchPropertyFields(anonymousCallerId, group.ID, model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, propertyFields, 11)
|
||||
|
||||
|
|
|
|||
|
|
@ -1533,100 +1533,99 @@ func (api *PluginAPI) CreatePropertyField(field *model.PropertyField) (*model.Pr
|
|||
if field == nil {
|
||||
return nil, fmt.Errorf("invalid input: property field parameter is required")
|
||||
}
|
||||
|
||||
return api.app.PropertyService().CreatePropertyField(field)
|
||||
return api.app.PropertyAccessService().CreatePropertyFieldForPlugin(api.manifest.Id, field)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetPropertyField(groupID, fieldID string) (*model.PropertyField, error) {
|
||||
return api.app.PropertyService().GetPropertyField(groupID, fieldID)
|
||||
return api.app.PropertyAccessService().GetPropertyField(api.manifest.Id, groupID, fieldID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetPropertyFields(groupID string, ids []string) ([]*model.PropertyField, error) {
|
||||
return api.app.PropertyService().GetPropertyFields(groupID, ids)
|
||||
return api.app.PropertyAccessService().GetPropertyFields(api.manifest.Id, groupID, ids)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpdatePropertyField(groupID string, field *model.PropertyField) (*model.PropertyField, error) {
|
||||
return api.app.PropertyService().UpdatePropertyField(groupID, field)
|
||||
return api.app.PropertyAccessService().UpdatePropertyField(api.manifest.Id, groupID, field)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) DeletePropertyField(groupID, fieldID string) error {
|
||||
return api.app.PropertyService().DeletePropertyField(groupID, fieldID)
|
||||
return api.app.PropertyAccessService().DeletePropertyField(api.manifest.Id, groupID, fieldID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) SearchPropertyFields(groupID string, opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
return api.app.PropertyService().SearchPropertyFields(groupID, opts)
|
||||
return api.app.PropertyAccessService().SearchPropertyFields(api.manifest.Id, groupID, opts)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) CountPropertyFields(groupID string, includeDeleted bool) (int64, error) {
|
||||
if includeDeleted {
|
||||
return api.app.PropertyService().CountAllPropertyFieldsForGroup(groupID)
|
||||
return api.app.PropertyAccessService().CountAllPropertyFieldsForGroup(groupID)
|
||||
}
|
||||
return api.app.PropertyService().CountActivePropertyFieldsForGroup(groupID)
|
||||
return api.app.PropertyAccessService().CountActivePropertyFieldsForGroup(groupID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) CountPropertyFieldsForTarget(groupID, targetType, targetID string, includeDeleted bool) (int64, error) {
|
||||
if includeDeleted {
|
||||
return api.app.PropertyService().CountAllPropertyFieldsForTarget(groupID, targetType, targetID)
|
||||
return api.app.PropertyAccessService().CountAllPropertyFieldsForTarget(groupID, targetType, targetID)
|
||||
}
|
||||
return api.app.PropertyService().CountActivePropertyFieldsForTarget(groupID, targetType, targetID)
|
||||
return api.app.PropertyAccessService().CountActivePropertyFieldsForTarget(groupID, targetType, targetID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) CreatePropertyValue(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().CreatePropertyValue(value)
|
||||
return api.app.PropertyAccessService().CreatePropertyValue(api.manifest.Id, value)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetPropertyValue(groupID, valueID string) (*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().GetPropertyValue(groupID, valueID)
|
||||
return api.app.PropertyAccessService().GetPropertyValue(api.manifest.Id, groupID, valueID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetPropertyValues(groupID string, ids []string) ([]*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().GetPropertyValues(groupID, ids)
|
||||
return api.app.PropertyAccessService().GetPropertyValues(api.manifest.Id, groupID, ids)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpdatePropertyValue(groupID string, value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().UpdatePropertyValue(groupID, value)
|
||||
return api.app.PropertyAccessService().UpdatePropertyValue(api.manifest.Id, groupID, value)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpsertPropertyValue(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().UpsertPropertyValue(value)
|
||||
return api.app.PropertyAccessService().UpsertPropertyValue(api.manifest.Id, value)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) DeletePropertyValue(groupID, valueID string) error {
|
||||
return api.app.PropertyService().DeletePropertyValue(groupID, valueID)
|
||||
return api.app.PropertyAccessService().DeletePropertyValue(api.manifest.Id, groupID, valueID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) SearchPropertyValues(groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().SearchPropertyValues(groupID, opts)
|
||||
return api.app.PropertyAccessService().SearchPropertyValues(api.manifest.Id, groupID, opts)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) RegisterPropertyGroup(name string) (*model.PropertyGroup, error) {
|
||||
return api.app.PropertyService().RegisterPropertyGroup(name)
|
||||
return api.app.PropertyAccessService().RegisterPropertyGroup(name)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetPropertyGroup(name string) (*model.PropertyGroup, error) {
|
||||
return api.app.PropertyService().GetPropertyGroup(name)
|
||||
return api.app.PropertyAccessService().GetPropertyGroup(name)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) GetPropertyFieldByName(groupID, targetID, name string) (*model.PropertyField, error) {
|
||||
return api.app.PropertyService().GetPropertyFieldByName(groupID, targetID, name)
|
||||
return api.app.PropertyAccessService().GetPropertyFieldByName(api.manifest.Id, groupID, targetID, name)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpdatePropertyFields(groupID string, fields []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
return api.app.PropertyService().UpdatePropertyFields(groupID, fields)
|
||||
return api.app.PropertyAccessService().UpdatePropertyFields(api.manifest.Id, groupID, fields)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpdatePropertyValues(groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().UpdatePropertyValues(groupID, values)
|
||||
return api.app.PropertyAccessService().UpdatePropertyValues(api.manifest.Id, groupID, values)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) UpsertPropertyValues(values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
return api.app.PropertyService().UpsertPropertyValues(values)
|
||||
return api.app.PropertyAccessService().UpsertPropertyValues(api.manifest.Id, values)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) DeletePropertyValuesForTarget(groupID, targetType, targetID string) error {
|
||||
return api.app.PropertyService().DeletePropertyValuesForTarget(groupID, targetType, targetID)
|
||||
return api.app.PropertyAccessService().DeletePropertyValuesForTarget(api.manifest.Id, groupID, targetType, targetID)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) DeletePropertyValuesForField(groupID, fieldID string) error {
|
||||
return api.app.PropertyService().DeletePropertyValuesForField(groupID, fieldID)
|
||||
return api.app.PropertyAccessService().DeletePropertyValuesForField(api.manifest.Id, groupID, fieldID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -434,4 +434,682 @@ func TestPluginProperties(t *testing.T) {
|
|||
appErr := th.App.ch.RemovePlugin(pluginIDs[0])
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("test plugin-created CPA field gets source_plugin_id", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Create a CPA field
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "CPA Test Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
}
|
||||
|
||||
createdField, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CPA field: %w", err)
|
||||
}
|
||||
|
||||
// Verify source_plugin_id was automatically set
|
||||
if createdField.Attrs == nil {
|
||||
return fmt.Errorf("field attrs is nil")
|
||||
}
|
||||
|
||||
sourcePluginID, ok := createdField.Attrs["source_plugin_id"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("source_plugin_id not found in attrs")
|
||||
}
|
||||
|
||||
if sourcePluginID != p.API.GetPluginID() {
|
||||
return fmt.Errorf("source_plugin_id mismatch: expected %s, got %s", p.API.GetPluginID(), sourcePluginID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 1)
|
||||
require.NoError(t, activationErrors[0])
|
||||
|
||||
// Clean up
|
||||
err2 := th.App.DisablePlugin(pluginIDs[0])
|
||||
require.Nil(t, err2)
|
||||
appErr := th.App.ch.RemovePlugin(pluginIDs[0])
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("test plugin can update its own protected field", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Create a protected CPA field
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Protected Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"protected": true,
|
||||
},
|
||||
}
|
||||
|
||||
createdField, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create protected field: %w", err)
|
||||
}
|
||||
|
||||
// Try to update the protected field (should succeed since we created it)
|
||||
createdField.Name = "Updated Protected Field"
|
||||
updatedField, err := p.API.UpdatePropertyField("` + cpaID + `", createdField)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update own protected field: %w", err)
|
||||
}
|
||||
|
||||
if updatedField.Name != "Updated Protected Field" {
|
||||
return fmt.Errorf("field name not updated correctly")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 1)
|
||||
require.NoError(t, activationErrors[0])
|
||||
|
||||
// Clean up
|
||||
err2 := th.App.DisablePlugin(pluginIDs[0])
|
||||
require.Nil(t, err2)
|
||||
appErr := th.App.ch.RemovePlugin(pluginIDs[0])
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("test plugin cannot update another plugin's protected field", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both plugins in same environment
|
||||
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t, []string{
|
||||
// Plugin 1: creates a protected field
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Create a protected CPA field
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Plugin1 Protected Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"protected": true,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create protected field: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
// Plugin 2: tries to update plugin1's field
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Search for plugin1's protected field
|
||||
fields, err := p.API.SearchPropertyFields("` + cpaID + `", model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to search fields: %w", err)
|
||||
}
|
||||
|
||||
var plugin1Field *model.PropertyField
|
||||
for _, field := range fields {
|
||||
if field.Name == "Plugin1 Protected Field" {
|
||||
plugin1Field = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if plugin1Field == nil {
|
||||
return fmt.Errorf("plugin1 field not found")
|
||||
}
|
||||
|
||||
// Attempt to update it (should fail)
|
||||
plugin1Field.Name = "Hacked By Plugin2"
|
||||
_, err = p.API.UpdatePropertyField("` + cpaID + `", plugin1Field)
|
||||
if err == nil {
|
||||
return fmt.Errorf("expected error when updating another plugin's protected field, but got none")
|
||||
}
|
||||
|
||||
// Error is expected
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 2)
|
||||
require.NoError(t, activationErrors[0])
|
||||
require.NoError(t, activationErrors[1])
|
||||
})
|
||||
|
||||
t.Run("test plugin can delete its own protected field", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Create a protected CPA field
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Field To Delete",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"protected": true,
|
||||
},
|
||||
}
|
||||
|
||||
createdField, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create protected field: %w", err)
|
||||
}
|
||||
|
||||
// Try to delete the protected field (should succeed since we created it)
|
||||
err = p.API.DeletePropertyField("` + cpaID + `", createdField.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete own protected field: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 1)
|
||||
require.NoError(t, activationErrors[0])
|
||||
|
||||
// Clean up
|
||||
err2 := th.App.DisablePlugin(pluginIDs[0])
|
||||
require.Nil(t, err2)
|
||||
appErr := th.App.ch.RemovePlugin(pluginIDs[0])
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("test plugin cannot delete another plugin's protected field", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both plugins in same environment
|
||||
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t, []string{
|
||||
// Plugin 1: creates a protected field
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Plugin1 Field To Keep",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"protected": true,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create protected field: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
// Plugin 2: tries to delete plugin1's field
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Search for plugin1's protected field
|
||||
fields, err := p.API.SearchPropertyFields("` + cpaID + `", model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to search fields: %w", err)
|
||||
}
|
||||
|
||||
var plugin1Field *model.PropertyField
|
||||
for _, field := range fields {
|
||||
if field.Name == "Plugin1 Field To Keep" {
|
||||
plugin1Field = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if plugin1Field == nil {
|
||||
return fmt.Errorf("plugin1 field not found")
|
||||
}
|
||||
|
||||
// Attempt to delete it (should fail)
|
||||
err = p.API.DeletePropertyField("` + cpaID + `", plugin1Field.ID)
|
||||
if err == nil {
|
||||
return fmt.Errorf("expected error when deleting another plugin's protected field, but got none")
|
||||
}
|
||||
|
||||
// Error is expected
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 2)
|
||||
require.NoError(t, activationErrors[0])
|
||||
require.NoError(t, activationErrors[1])
|
||||
})
|
||||
|
||||
t.Run("test plugin can update values for its own protected field", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Create a protected CPA field
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Protected Field With Values",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"protected": true,
|
||||
},
|
||||
}
|
||||
|
||||
createdField, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create protected field: %w", err)
|
||||
}
|
||||
|
||||
// Create a value for this field
|
||||
targetID := model.NewId()
|
||||
value := &model.PropertyValue{
|
||||
GroupID: "` + cpaID + `",
|
||||
FieldID: createdField.ID,
|
||||
TargetID: targetID,
|
||||
TargetType: "user",
|
||||
Value: []byte("\"initial value\""),
|
||||
}
|
||||
|
||||
createdValue, err := p.API.CreatePropertyValue(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create value: %w", err)
|
||||
}
|
||||
|
||||
// Update the value (should succeed)
|
||||
createdValue.Value = []byte("\"updated value\"")
|
||||
updatedValue, err := p.API.UpdatePropertyValue("` + cpaID + `", createdValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update value for own protected field: %w", err)
|
||||
}
|
||||
|
||||
if string(updatedValue.Value) != "\"updated value\"" {
|
||||
return fmt.Errorf("value not updated correctly")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 1)
|
||||
require.NoError(t, activationErrors[0])
|
||||
|
||||
// Clean up
|
||||
err2 := th.App.DisablePlugin(pluginIDs[0])
|
||||
require.Nil(t, err2)
|
||||
appErr := th.App.ch.RemovePlugin(pluginIDs[0])
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("test plugin cannot update values for another plugin's protected field", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
testTargetID := model.NewId()
|
||||
|
||||
// Both plugins in same environment
|
||||
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t, []string{
|
||||
// Plugin 1: creates a protected field with a value
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Plugin1 Field With Protected Values",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: map[string]any{
|
||||
"protected": true,
|
||||
},
|
||||
}
|
||||
|
||||
createdField, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create protected field: %w", err)
|
||||
}
|
||||
|
||||
// Create a value
|
||||
value := &model.PropertyValue{
|
||||
GroupID: "` + cpaID + `",
|
||||
FieldID: createdField.ID,
|
||||
TargetID: "` + testTargetID + `",
|
||||
TargetType: "user",
|
||||
Value: []byte("\"plugin1 value\""),
|
||||
}
|
||||
|
||||
_, err = p.API.CreatePropertyValue(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create value: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
// Plugin 2: tries to update plugin1's field value
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Search for plugin1's protected field
|
||||
fields, err := p.API.SearchPropertyFields("` + cpaID + `", model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to search fields: %w", err)
|
||||
}
|
||||
|
||||
var plugin1Field *model.PropertyField
|
||||
for _, field := range fields {
|
||||
if field.Name == "Plugin1 Field With Protected Values" {
|
||||
plugin1Field = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if plugin1Field == nil {
|
||||
return fmt.Errorf("plugin1 field not found")
|
||||
}
|
||||
|
||||
// Try to update the value (should fail)
|
||||
value := &model.PropertyValue{
|
||||
GroupID: "` + cpaID + `",
|
||||
FieldID: plugin1Field.ID,
|
||||
TargetID: "` + testTargetID + `",
|
||||
TargetType: "user",
|
||||
Value: []byte("\"hacked by plugin2\""),
|
||||
}
|
||||
|
||||
_, err = p.API.UpsertPropertyValue(value)
|
||||
if err == nil {
|
||||
return fmt.Errorf("expected error when updating another plugin's protected field value, but got none")
|
||||
}
|
||||
|
||||
// Error is expected
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 2)
|
||||
require.NoError(t, activationErrors[0])
|
||||
require.NoError(t, activationErrors[1])
|
||||
})
|
||||
|
||||
t.Run("test plugin can modify non-protected CPA fields from other plugins", func(t *testing.T) {
|
||||
cpaID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Both plugins in same environment
|
||||
tearDown, _, activationErrors := SetAppEnvironmentWithPlugins(t, []string{
|
||||
// Plugin 1: creates a NON-protected field
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
field := &model.PropertyField{
|
||||
GroupID: "` + cpaID + `",
|
||||
Name: "Non-Protected Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
// Note: protected is not set
|
||||
}
|
||||
|
||||
_, err := p.API.CreatePropertyField(field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create non-protected field: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
// Plugin 2: modifies plugin1's non-protected field
|
||||
`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattermost/mattermost/server/public/plugin"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
type MyPlugin struct {
|
||||
plugin.MattermostPlugin
|
||||
}
|
||||
|
||||
func (p *MyPlugin) OnActivate() error {
|
||||
// Search for plugin1's non-protected field
|
||||
fields, err := p.API.SearchPropertyFields("` + cpaID + `", model.PropertyFieldSearchOpts{PerPage: 100})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to search fields: %w", err)
|
||||
}
|
||||
|
||||
var plugin1Field *model.PropertyField
|
||||
for _, field := range fields {
|
||||
if field.Name == "Non-Protected Field" {
|
||||
plugin1Field = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if plugin1Field == nil {
|
||||
return fmt.Errorf("plugin1 field not found")
|
||||
}
|
||||
|
||||
// Update it (should succeed since it's not protected)
|
||||
plugin1Field.Name = "Modified By Plugin2"
|
||||
_, err = p.API.UpdatePropertyField("` + cpaID + `", plugin1Field)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update non-protected field: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
plugin.ClientMain(&MyPlugin{})
|
||||
}
|
||||
`,
|
||||
}, th.App, th.NewPluginAPI)
|
||||
defer tearDown()
|
||||
require.Len(t, activationErrors, 2)
|
||||
require.NoError(t, activationErrors[0])
|
||||
require.NoError(t, activationErrors[1])
|
||||
|
||||
// Verify the field was actually updated
|
||||
updatedFields, appErr := th.App.ListCPAFields("")
|
||||
require.Nil(t, appErr)
|
||||
var fieldWasUpdated bool
|
||||
for _, field := range updatedFields {
|
||||
if field.Name == "Modified By Plugin2" {
|
||||
fieldWasUpdated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, fieldWasUpdated, "Non-protected field should have been updated by plugin2")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,22 +32,37 @@ const (
|
|||
propertyAccessPaginationPageSize = 100
|
||||
// propertyAccessMaxPaginationIterations is the maximum number of pagination iterations before returning an error
|
||||
propertyAccessMaxPaginationIterations = 10
|
||||
// anonymousCallerId can be used for calls to the service that aren't tied to a specific entity
|
||||
// These calls will not be able to access any data that has access control restrictions.
|
||||
anonymousCallerId = ""
|
||||
)
|
||||
|
||||
// PluginChecker is a function type that checks if a plugin is installed.
|
||||
// Returns true if the plugin exists and is installed, false otherwise.
|
||||
type PluginChecker func(pluginID string) bool
|
||||
|
||||
// PropertyAccessService is a decorator around PropertyService that enforces
|
||||
// access control based on caller identity. All property operations go through
|
||||
// this service to ensure consistent access control enforcement.
|
||||
type PropertyAccessService struct {
|
||||
propertyService *properties.PropertyService
|
||||
pluginChecker PluginChecker
|
||||
}
|
||||
|
||||
// NewPropertyAccessService creates a new PropertyAccessService wrapping the given PropertyService.
|
||||
func NewPropertyAccessService(ps *properties.PropertyService) *PropertyAccessService {
|
||||
// The pluginChecker function is used to verify plugin installation status when checking access
|
||||
// to protected fields. Pass nil if plugin checking is not needed (e.g., in tests).
|
||||
func NewPropertyAccessService(ps *properties.PropertyService, pluginChecker PluginChecker) *PropertyAccessService {
|
||||
return &PropertyAccessService{
|
||||
propertyService: ps,
|
||||
pluginChecker: pluginChecker,
|
||||
}
|
||||
}
|
||||
|
||||
func (pas *PropertyAccessService) setPluginCheckerForTests(pluginChecker PluginChecker) {
|
||||
pas.pluginChecker = pluginChecker
|
||||
}
|
||||
|
||||
// Property Group Methods
|
||||
|
||||
// RegisterPropertyGroup registers a new property group.
|
||||
|
|
@ -65,6 +80,12 @@ func (pas *PropertyAccessService) GetPropertyGroup(name string) (*model.Property
|
|||
// CreatePropertyField creates a new property field.
|
||||
// This method rejects any attempt to set source_plugin_id - only plugins can set this via CreatePropertyFieldForPlugin.
|
||||
func (pas *PropertyAccessService) CreatePropertyField(callerID string, field *model.PropertyField) (*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(field.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("CreatePropertyField: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.CreatePropertyField(field)
|
||||
}
|
||||
|
||||
// Reject if source_plugin_id is set to a non-empty value - only plugins can set this via CreatePropertyFieldForPlugin
|
||||
if pas.getSourcePluginID(field) != "" {
|
||||
return nil, fmt.Errorf("CreatePropertyField: source_plugin_id cannot be set directly, it is only set automatically for plugin-created fields")
|
||||
|
|
@ -91,6 +112,12 @@ func (pas *PropertyAccessService) CreatePropertyField(callerID string, field *mo
|
|||
// This method automatically sets the source_plugin_id to the provided pluginID.
|
||||
// Only use this method when creating fields through the Plugin API.
|
||||
func (pas *PropertyAccessService) CreatePropertyFieldForPlugin(pluginID string, field *model.PropertyField) (*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(field.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("CreatePropertyFieldForPlugin: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.CreatePropertyField(field)
|
||||
}
|
||||
|
||||
if pluginID == "" {
|
||||
return nil, fmt.Errorf("CreatePropertyFieldForPlugin: pluginID is required")
|
||||
}
|
||||
|
|
@ -118,6 +145,12 @@ func (pas *PropertyAccessService) CreatePropertyFieldForPlugin(pluginID string,
|
|||
// GetPropertyField retrieves a property field by group and field ID.
|
||||
// Field details are filtered based on the caller's access permissions.
|
||||
func (pas *PropertyAccessService) GetPropertyField(callerID string, groupID, id string) (*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyField: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.GetPropertyField(groupID, id)
|
||||
}
|
||||
|
||||
field, err := pas.propertyService.GetPropertyField(groupID, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyField: %w", err)
|
||||
|
|
@ -129,6 +162,12 @@ func (pas *PropertyAccessService) GetPropertyField(callerID string, groupID, id
|
|||
// GetPropertyFields retrieves multiple property fields by their IDs.
|
||||
// Field details are filtered based on the caller's access permissions.
|
||||
func (pas *PropertyAccessService) GetPropertyFields(callerID string, groupID string, ids []string) ([]*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyFields: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.GetPropertyFields(groupID, ids)
|
||||
}
|
||||
|
||||
fields, err := pas.propertyService.GetPropertyFields(groupID, ids)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyFields: %w", err)
|
||||
|
|
@ -140,6 +179,12 @@ func (pas *PropertyAccessService) GetPropertyFields(callerID string, groupID str
|
|||
// GetPropertyFieldByName retrieves a property field by name.
|
||||
// Field details are filtered based on the caller's access permissions.
|
||||
func (pas *PropertyAccessService) GetPropertyFieldByName(callerID string, groupID, targetID, name string) (*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyFieldByName: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.GetPropertyFieldByName(groupID, targetID, name)
|
||||
}
|
||||
|
||||
field, err := pas.propertyService.GetPropertyFieldByName(groupID, targetID, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyFieldByName: %w", err)
|
||||
|
|
@ -171,6 +216,12 @@ func (pas *PropertyAccessService) CountAllPropertyFieldsForTarget(groupID, targe
|
|||
// SearchPropertyFields searches for property fields based on the given options.
|
||||
// Field details are filtered based on the caller's access permissions.
|
||||
func (pas *PropertyAccessService) SearchPropertyFields(callerID string, groupID string, opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("SearchPropertyFields: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.SearchPropertyFields(groupID, opts)
|
||||
}
|
||||
|
||||
fields, err := pas.propertyService.SearchPropertyFields(groupID, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SearchPropertyFields: %w", err)
|
||||
|
|
@ -182,6 +233,12 @@ func (pas *PropertyAccessService) SearchPropertyFields(callerID string, groupID
|
|||
// UpdatePropertyField updates a property field.
|
||||
// Checks write access and ensures source_plugin_id is not changed.
|
||||
func (pas *PropertyAccessService) UpdatePropertyField(callerID string, groupID string, field *model.PropertyField) (*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("UpdatePropertyField: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.UpdatePropertyField(groupID, field)
|
||||
}
|
||||
|
||||
// Get existing field to check access
|
||||
existingField, existsErr := pas.propertyService.GetPropertyField(groupID, field.ID)
|
||||
if existsErr != nil {
|
||||
|
|
@ -213,6 +270,12 @@ func (pas *PropertyAccessService) UpdatePropertyField(callerID string, groupID s
|
|||
// UpdatePropertyFields updates multiple property fields.
|
||||
// Checks write access for all fields atomically before updating any.
|
||||
func (pas *PropertyAccessService) UpdatePropertyFields(callerID string, groupID string, fields []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("UpdatePropertyFields: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.UpdatePropertyFields(groupID, fields)
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -267,16 +330,22 @@ func (pas *PropertyAccessService) UpdatePropertyFields(callerID string, groupID
|
|||
}
|
||||
|
||||
// DeletePropertyField deletes a property field and all its values.
|
||||
// Checks write access before allowing deletion.
|
||||
// Checks delete access before allowing deletion.
|
||||
func (pas *PropertyAccessService) DeletePropertyField(callerID string, groupID, id string) error {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return fmt.Errorf("DeletePropertyField: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.DeletePropertyField(groupID, id)
|
||||
}
|
||||
|
||||
// Get existing field to check access
|
||||
existingField, err := pas.propertyService.GetPropertyField(groupID, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeletePropertyField: %w", err)
|
||||
}
|
||||
|
||||
// Check write access
|
||||
if err := pas.checkFieldWriteAccess(existingField, callerID); err != nil {
|
||||
// Check delete access
|
||||
if err := pas.checkFieldDeleteAccess(existingField, callerID); err != nil {
|
||||
return fmt.Errorf("DeletePropertyField: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -291,6 +360,12 @@ func (pas *PropertyAccessService) DeletePropertyField(callerID string, groupID,
|
|||
// CreatePropertyValue creates a new property value.
|
||||
// Checks write access before allowing the creation.
|
||||
func (pas *PropertyAccessService) CreatePropertyValue(callerID string, value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(value.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("CreatePropertyValue: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.CreatePropertyValue(value)
|
||||
}
|
||||
|
||||
// Get the associated field to check access
|
||||
field, err := pas.propertyService.GetPropertyField(value.GroupID, value.FieldID)
|
||||
if err != nil {
|
||||
|
|
@ -312,6 +387,19 @@ func (pas *PropertyAccessService) CreatePropertyValue(callerID string, value *mo
|
|||
// CreatePropertyValues creates multiple property values.
|
||||
// Checks write access for all fields atomically before creating any values.
|
||||
func (pas *PropertyAccessService) CreatePropertyValues(callerID string, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
shouldApplyAccessControl := false
|
||||
for _, value := range values {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(value.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("CreatePropertyValues: cannot determine access restrictions: %w", err)
|
||||
} else if hasRestrictions {
|
||||
shouldApplyAccessControl = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !shouldApplyAccessControl {
|
||||
return pas.propertyService.CreatePropertyValues(values)
|
||||
}
|
||||
|
||||
fieldMap, err := pas.getFieldsForValues(values)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreatePropertyValues: %w", err)
|
||||
|
|
@ -340,6 +428,12 @@ func (pas *PropertyAccessService) CreatePropertyValues(callerID string, values [
|
|||
// GetPropertyValue retrieves a property value by ID.
|
||||
// Returns (nil, nil) if the value exists but the caller doesn't have access.
|
||||
func (pas *PropertyAccessService) GetPropertyValue(callerID string, groupID, id string) (*model.PropertyValue, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyValue: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.GetPropertyValue(groupID, id)
|
||||
}
|
||||
|
||||
value, err := pas.propertyService.GetPropertyValue(groupID, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyValue: %w", err)
|
||||
|
|
@ -362,6 +456,12 @@ func (pas *PropertyAccessService) GetPropertyValue(callerID string, groupID, id
|
|||
// GetPropertyValues retrieves multiple property values by their IDs.
|
||||
// Values the caller doesn't have access to are silently filtered out.
|
||||
func (pas *PropertyAccessService) GetPropertyValues(callerID string, groupID string, ids []string) ([]*model.PropertyValue, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyValues: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.GetPropertyValues(groupID, ids)
|
||||
}
|
||||
|
||||
values, err := pas.propertyService.GetPropertyValues(groupID, ids)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetPropertyValues: %w", err)
|
||||
|
|
@ -378,6 +478,12 @@ func (pas *PropertyAccessService) GetPropertyValues(callerID string, groupID str
|
|||
// SearchPropertyValues searches for property values based on the given options.
|
||||
// Values the caller doesn't have access to are silently filtered out.
|
||||
func (pas *PropertyAccessService) SearchPropertyValues(callerID string, groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("SearchPropertyValues: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.SearchPropertyValues(groupID, opts)
|
||||
}
|
||||
|
||||
values, err := pas.propertyService.SearchPropertyValues(groupID, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SearchPropertyValues: %w", err)
|
||||
|
|
@ -394,6 +500,12 @@ func (pas *PropertyAccessService) SearchPropertyValues(callerID string, groupID
|
|||
// UpdatePropertyValue updates a property value.
|
||||
// Checks write access before allowing the update.
|
||||
func (pas *PropertyAccessService) UpdatePropertyValue(callerID string, groupID string, value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return nil, fmt.Errorf("UpdatePropertyValue: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.UpdatePropertyValue(groupID, value)
|
||||
}
|
||||
|
||||
// Get the associated field to check access
|
||||
field, err := pas.propertyService.GetPropertyField(groupID, value.FieldID)
|
||||
if err != nil {
|
||||
|
|
@ -415,6 +527,19 @@ func (pas *PropertyAccessService) UpdatePropertyValue(callerID string, groupID s
|
|||
// UpdatePropertyValues updates multiple property values.
|
||||
// Checks write access for all fields atomically before updating any values.
|
||||
func (pas *PropertyAccessService) UpdatePropertyValues(callerID string, groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
shouldApplyAccessControl := false
|
||||
for _, value := range values {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(value.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("UpdatePropertyValues: cannot determine access restrictions: %w", err)
|
||||
} else if hasRestrictions {
|
||||
shouldApplyAccessControl = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !shouldApplyAccessControl {
|
||||
return pas.propertyService.UpdatePropertyValues(groupID, values)
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return values, nil
|
||||
}
|
||||
|
|
@ -447,6 +572,12 @@ func (pas *PropertyAccessService) UpdatePropertyValues(callerID string, groupID
|
|||
// UpsertPropertyValue creates or updates a property value.
|
||||
// Checks write access before allowing the upsert.
|
||||
func (pas *PropertyAccessService) UpsertPropertyValue(callerID string, value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(value.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("UpsertPropertyValue cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.UpsertPropertyValue(value)
|
||||
}
|
||||
|
||||
// Get the associated field to check access
|
||||
field, err := pas.propertyService.GetPropertyField(value.GroupID, value.FieldID)
|
||||
if err != nil {
|
||||
|
|
@ -468,6 +599,19 @@ func (pas *PropertyAccessService) UpsertPropertyValue(callerID string, value *mo
|
|||
// UpsertPropertyValues creates or updates multiple property values.
|
||||
// Checks write access for all fields atomically before upserting any values.
|
||||
func (pas *PropertyAccessService) UpsertPropertyValues(callerID string, values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
shouldApplyAccessControl := false
|
||||
for _, value := range values {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(value.GroupID); err != nil {
|
||||
return nil, fmt.Errorf("UpsertPropertyValues: cannot determine access restrictions: %w", err)
|
||||
} else if hasRestrictions {
|
||||
shouldApplyAccessControl = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !shouldApplyAccessControl {
|
||||
return pas.propertyService.UpsertPropertyValues(values)
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return values, nil
|
||||
}
|
||||
|
|
@ -500,6 +644,12 @@ func (pas *PropertyAccessService) UpsertPropertyValues(callerID string, values [
|
|||
// DeletePropertyValue deletes a property value.
|
||||
// Checks write access before allowing deletion.
|
||||
func (pas *PropertyAccessService) DeletePropertyValue(callerID string, groupID, id string) error {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return fmt.Errorf("DeletePropertyValue: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.DeletePropertyValue(groupID, id)
|
||||
}
|
||||
|
||||
// Get the value to find its field ID
|
||||
value, err := pas.propertyService.GetPropertyValue(groupID, id)
|
||||
if err != nil {
|
||||
|
|
@ -527,6 +677,12 @@ func (pas *PropertyAccessService) DeletePropertyValue(callerID string, groupID,
|
|||
// DeletePropertyValuesForTarget deletes all property values for a specific target.
|
||||
// Checks write access for all affected fields atomically before deleting.
|
||||
func (pas *PropertyAccessService) DeletePropertyValuesForTarget(callerID string, groupID string, targetType string, targetID string) error {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return fmt.Errorf("DeletePropertyValuesForTarget: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.DeletePropertyValuesForTarget(groupID, targetType, targetID)
|
||||
}
|
||||
|
||||
// Collect unique field IDs across all values without loading all values into memory
|
||||
fieldIDs := make(map[string]struct{})
|
||||
var cursor model.PropertyValueSearchCursor
|
||||
|
|
@ -605,6 +761,12 @@ func (pas *PropertyAccessService) DeletePropertyValuesForTarget(callerID string,
|
|||
// DeletePropertyValuesForField deletes all property values for a specific field.
|
||||
// Checks write access before allowing deletion.
|
||||
func (pas *PropertyAccessService) DeletePropertyValuesForField(callerID string, groupID, fieldID string) error {
|
||||
if hasRestrictions, err := pas.groupHasAccessRestrictions(groupID); err != nil {
|
||||
return fmt.Errorf("DeletePropertyValuesForField: cannot determine access restrictions: %w", err)
|
||||
} else if !hasRestrictions {
|
||||
return pas.propertyService.DeletePropertyValuesForField(groupID, fieldID)
|
||||
}
|
||||
|
||||
// Get the field to check access
|
||||
field, err := pas.propertyService.GetPropertyField(groupID, fieldID)
|
||||
if err != nil {
|
||||
|
|
@ -705,6 +867,35 @@ func (pas *PropertyAccessService) checkFieldWriteAccess(field *model.PropertyFie
|
|||
return nil
|
||||
}
|
||||
|
||||
// checkFieldDeleteAccess checks if the given caller can delete a PropertyField.
|
||||
// IMPORTANT: Always pass the existing field fetched from the database, not a field provided by the caller.
|
||||
// Returns nil if deletion is allowed, or an error if denied.
|
||||
func (pas *PropertyAccessService) checkFieldDeleteAccess(field *model.PropertyField, callerID string) error {
|
||||
// Check if field is protected
|
||||
if !model.IsPropertyFieldProtected(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Protected fields can only be deleted by the source plugin
|
||||
sourcePluginID := pas.getSourcePluginID(field)
|
||||
if sourcePluginID == "" {
|
||||
// Protected field with no source plugin - allow deletion
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the source plugin is still installed
|
||||
if pas.pluginChecker != nil && !pas.pluginChecker(sourcePluginID) {
|
||||
// Plugin has been uninstalled - allow deletion of orphaned field
|
||||
return nil
|
||||
}
|
||||
|
||||
if sourcePluginID != callerID {
|
||||
return fmt.Errorf("field %s is protected and can only be modified by source plugin '%s'", field.ID, sourcePluginID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCallerValuesForField retrieves all property values for the caller on a specific field.
|
||||
// This is used internally for shared_only filtering.
|
||||
// Returns an empty slice if callerID is empty or if there are no values.
|
||||
|
|
@ -1076,3 +1267,11 @@ func (pas *PropertyAccessService) applyValueReadAccessControl(values []*model.Pr
|
|||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (pas *PropertyAccessService) groupHasAccessRestrictions(groupId string) (bool, error) {
|
||||
cpaID, err := getCpaGroupID(pas)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return groupId == cpaID, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ func TestGetPropertyFieldReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, err := pas.RegisterPropertyGroup("test-group")
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID1 := "plugin-1"
|
||||
pluginID2 := "plugin-2"
|
||||
|
|
@ -326,6 +327,42 @@ func TestGetPropertyFieldReadAccess(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid access mode")
|
||||
})
|
||||
|
||||
t.Run("non-CPA group source_only field - everyone sees all options", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-read")
|
||||
require.NoError(t, err)
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "source-only-non-cpa",
|
||||
Type: model.PropertyFieldTypeSelect,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
model.PropertyFieldAttributeOptions: []any{
|
||||
map[string]any{"id": "opt1", "value": "Secret Option 1"},
|
||||
map[string]any{"id": "opt2", "value": "Secret Option 2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
created, err := pas.CreatePropertyField("", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Non-source plugin sees all options (no filtering)
|
||||
retrieved, err := pas.GetPropertyField(pluginID2, nonCpaGroup.ID, created.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, created.ID, retrieved.ID)
|
||||
assert.Len(t, retrieved.Attrs[model.PropertyFieldAttributeOptions].([]any), 2)
|
||||
|
||||
// User sees all options (no filtering)
|
||||
retrieved, err = pas.GetPropertyField(userID, nonCpaGroup.ID, created.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, created.ID, retrieved.ID)
|
||||
assert.Len(t, retrieved.Attrs[model.PropertyFieldAttributeOptions].([]any), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPropertyFieldsReadAccess(t *testing.T) {
|
||||
|
|
@ -333,9 +370,10 @@ func TestGetPropertyFieldsReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, err := pas.RegisterPropertyGroup("test-group-batch")
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID := "plugin-1"
|
||||
userID := model.NewId()
|
||||
|
|
@ -435,9 +473,10 @@ func TestSearchPropertyFieldsReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, err := pas.RegisterPropertyGroup("test-group-search")
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID := "plugin-1"
|
||||
userID := model.NewId()
|
||||
|
|
@ -541,9 +580,10 @@ func TestGetPropertyFieldByNameReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, err := pas.RegisterPropertyGroup("test-group-byname")
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID := "plugin-1"
|
||||
userID := model.NewId()
|
||||
|
|
@ -583,7 +623,14 @@ func TestGetPropertyFieldByNameReadAccess(t *testing.T) {
|
|||
func TestCreatePropertyField_SourcePluginIDValidation(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows field creation without source_plugin_id", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
|
|
@ -680,13 +727,62 @@ func TestCreatePropertyField_SourcePluginIDValidation(t *testing.T) {
|
|||
assert.Nil(t, created)
|
||||
assert.Contains(t, err.Error(), "protected can only be set by plugins")
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows protected attribute", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group")
|
||||
require.NoError(t, err)
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Should succeed because access control doesn't apply to non-CPA groups
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyField("user1", field)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, created)
|
||||
assert.True(t, created.Attrs[model.PropertyAttrsProtected].(bool))
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows source_plugin_id to be set", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-2")
|
||||
require.NoError(t, err)
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: model.NewId(),
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsSourcePluginID: "some-plugin",
|
||||
},
|
||||
}
|
||||
|
||||
// Should succeed because access control doesn't apply to non-CPA groups
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyField("user1", field)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, created)
|
||||
assert.Equal(t, "some-plugin", created.Attrs[model.PropertyAttrsSourcePluginID])
|
||||
})
|
||||
}
|
||||
|
||||
// TestCreatePropertyFieldForPlugin tests the plugin-specific field creation method
|
||||
func TestCreatePropertyFieldForPlugin(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("automatically sets source_plugin_id", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
|
|
@ -759,7 +855,14 @@ func TestCreatePropertyFieldForPlugin(t *testing.T) {
|
|||
func TestUpdatePropertyField_WriteAccessControl(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows update of unprotected field", func(t *testing.T) {
|
||||
field := &model.PropertyField{
|
||||
|
|
@ -855,13 +958,45 @@ func TestUpdatePropertyField_WriteAccessControl(t *testing.T) {
|
|||
assert.Nil(t, updated)
|
||||
assert.Contains(t, err.Error(), "immutable")
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows anyone to update protected field", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-update")
|
||||
require.NoError(t, err)
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "Protected Field Non-CPA",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: "plugin1",
|
||||
},
|
||||
}
|
||||
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyField("", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
created.Name = "Updated by Different Plugin"
|
||||
// Should succeed - plugin2 can update plugin1's protected field in non-CPA group
|
||||
updated, err := th.App.PropertyAccessService().UpdatePropertyField("plugin2", nonCpaGroup.ID, created)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated by Different Plugin", updated.Name)
|
||||
})
|
||||
}
|
||||
|
||||
// TestUpdatePropertyFields_BulkWriteAccessControl tests bulk field updates with atomic access checking
|
||||
func TestUpdatePropertyFields_BulkWriteAccessControl(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows bulk update of unprotected fields", func(t *testing.T) {
|
||||
field1 := &model.PropertyField{GroupID: groupID, Name: "Field1", Type: model.PropertyFieldTypeText}
|
||||
|
|
@ -922,7 +1057,14 @@ func TestUpdatePropertyFields_BulkWriteAccessControl(t *testing.T) {
|
|||
func TestDeletePropertyField_WriteAccessControl(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows deletion of unprotected field", func(t *testing.T) {
|
||||
field := &model.PropertyField{GroupID: groupID, Name: "Unprotected", Type: model.PropertyFieldTypeText}
|
||||
|
|
@ -950,6 +1092,10 @@ func TestDeletePropertyField_WriteAccessControl(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("denies non-source plugin deleting protected field", func(t *testing.T) {
|
||||
pas.setPluginCheckerForTests(func(pluginID string) bool {
|
||||
return pluginID == "plugin1"
|
||||
})
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Protected",
|
||||
|
|
@ -958,20 +1104,49 @@ func TestDeletePropertyField_WriteAccessControl(t *testing.T) {
|
|||
model.PropertyAttrsProtected: true,
|
||||
},
|
||||
}
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyFieldForPlugin("plugin1", field)
|
||||
created, err := pas.CreatePropertyFieldForPlugin("plugin1", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = th.App.PropertyAccessService().DeletePropertyField("plugin2", groupID, created.ID)
|
||||
err = pas.DeletePropertyField("plugin2", groupID, created.ID)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "protected")
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows anyone to delete protected field", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-delete")
|
||||
require.NoError(t, err)
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "Protected Non-CPA",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: "plugin1",
|
||||
},
|
||||
}
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyField("", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should succeed - plugin2 can delete plugin1's protected field in non-CPA group
|
||||
err = th.App.PropertyAccessService().DeletePropertyField("plugin2", nonCpaGroup.ID, created.ID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// TestCreatePropertyValue_WriteAccessControl tests write access control for value creation
|
||||
func TestCreatePropertyValue_WriteAccessControl(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows creating value for public field", func(t *testing.T) {
|
||||
field := &model.PropertyField{GroupID: groupID, Name: "Public", Type: model.PropertyFieldTypeText}
|
||||
|
|
@ -1042,13 +1217,51 @@ func TestCreatePropertyValue_WriteAccessControl(t *testing.T) {
|
|||
assert.Nil(t, createdValue)
|
||||
assert.Contains(t, err.Error(), "protected")
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows anyone to create value for protected field", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-value-create")
|
||||
require.NoError(t, err)
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "Protected Non-CPA",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: "plugin1",
|
||||
},
|
||||
}
|
||||
created, err := th.App.PropertyAccessService().CreatePropertyField("", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
value := &model.PropertyValue{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
FieldID: created.ID,
|
||||
TargetType: "user",
|
||||
TargetID: model.NewId(),
|
||||
Value: json.RawMessage(`"value from plugin2"`),
|
||||
}
|
||||
|
||||
// Should succeed - plugin2 can create value on plugin1's protected field in non-CPA group
|
||||
createdValue, err := th.App.PropertyAccessService().CreatePropertyValue("plugin2", value)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, createdValue)
|
||||
})
|
||||
}
|
||||
|
||||
// TestDeletePropertyValue_WriteAccessControl tests write access control for value deletion
|
||||
func TestDeletePropertyValue_WriteAccessControl(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows deleting value for public field", func(t *testing.T) {
|
||||
field := &model.PropertyField{GroupID: groupID, Name: "Public", Type: model.PropertyFieldTypeText}
|
||||
|
|
@ -1101,7 +1314,14 @@ func TestDeletePropertyValue_WriteAccessControl(t *testing.T) {
|
|||
func TestDeletePropertyValuesForTarget_WriteAccessControl(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID := model.NewId()
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
groupID := cpaGroupID
|
||||
|
||||
t.Run("allows deleting all values when caller has write access to all fields", func(t *testing.T) {
|
||||
field1 := &model.PropertyField{GroupID: groupID, Name: "Field1", Type: model.PropertyFieldTypeText}
|
||||
|
|
@ -1172,9 +1392,10 @@ func TestGetPropertyValueReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, rerr := pas.RegisterPropertyGroup("test-group-values")
|
||||
// Register the CPA group
|
||||
group, rerr := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, rerr)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID1 := "plugin-1"
|
||||
pluginID2 := "plugin-2"
|
||||
|
|
@ -1503,6 +1724,54 @@ func TestGetPropertyValueReadAccess(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Nil(t, retrieved)
|
||||
})
|
||||
|
||||
t.Run("non-CPA group source_only value - everyone can read", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-value-read")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create source_only field
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "source-only-non-cpa",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
},
|
||||
}
|
||||
field, err = pas.CreatePropertyField("", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create value
|
||||
textValue, err := json.Marshal("secret value")
|
||||
require.NoError(t, err)
|
||||
value := &model.PropertyValue{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
FieldID: field.ID,
|
||||
TargetType: "user",
|
||||
TargetID: userID1,
|
||||
Value: textValue,
|
||||
}
|
||||
value, err = pas.CreatePropertyValue("", value)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Non-source plugin can read (no filtering)
|
||||
retrieved, err := pas.GetPropertyValue(pluginID2, nonCpaGroup.ID, value.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, retrieved)
|
||||
assert.Equal(t, value.ID, retrieved.ID)
|
||||
assert.Equal(t, json.RawMessage(textValue), retrieved.Value)
|
||||
|
||||
// User can read (no filtering)
|
||||
retrieved, err = pas.GetPropertyValue(userID2, nonCpaGroup.ID, value.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, retrieved)
|
||||
assert.Equal(t, value.ID, retrieved.ID)
|
||||
assert.Equal(t, json.RawMessage(textValue), retrieved.Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPropertyValuesReadAccess(t *testing.T) {
|
||||
|
|
@ -1510,9 +1779,10 @@ func TestGetPropertyValuesReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, rerr := pas.RegisterPropertyGroup("test-group-bulk-values")
|
||||
// Register the CPA group
|
||||
group, rerr := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, rerr)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID1 := "plugin-1"
|
||||
pluginID2 := "plugin-2"
|
||||
|
|
@ -1595,9 +1865,10 @@ func TestSearchPropertyValuesReadAccess(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
// Register a test group
|
||||
group, rerr := pas.RegisterPropertyGroup("test-group-search-values")
|
||||
// Register the CPA group
|
||||
group, rerr := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, rerr)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID1 := "plugin-1"
|
||||
pluginID2 := "plugin-2"
|
||||
|
|
@ -1740,8 +2011,10 @@ func TestCreatePropertyValues_WriteAccessControl(t *testing.T) {
|
|||
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
group, err := pas.RegisterPropertyGroup("test-group-create-values")
|
||||
// Register the CPA group
|
||||
group, err := pas.RegisterPropertyGroup("cpa")
|
||||
require.NoError(t, err)
|
||||
cpaGroupID = group.ID
|
||||
|
||||
pluginID1 := "plugin-1"
|
||||
pluginID2 := "plugin-2"
|
||||
|
|
@ -2063,4 +2336,278 @@ func TestCreatePropertyValues_WriteAccessControl(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Empty(t, results3)
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows bulk creation of values for protected fields", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-bulk-create")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create protected fields
|
||||
field1 := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "protected-bulk-1",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
},
|
||||
}
|
||||
field1, err = pas.CreatePropertyField("", field1)
|
||||
require.NoError(t, err)
|
||||
|
||||
field2 := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "protected-bulk-2",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
},
|
||||
}
|
||||
field2, err = pas.CreatePropertyField("", field2)
|
||||
require.NoError(t, err)
|
||||
|
||||
targetID := model.NewId()
|
||||
value1, err := json.Marshal("data1")
|
||||
require.NoError(t, err)
|
||||
value2, err := json.Marshal("data2")
|
||||
require.NoError(t, err)
|
||||
|
||||
values := []*model.PropertyValue{
|
||||
{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
FieldID: field1.ID,
|
||||
TargetType: "user",
|
||||
TargetID: targetID,
|
||||
Value: value1,
|
||||
},
|
||||
{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
FieldID: field2.ID,
|
||||
TargetType: "user",
|
||||
TargetID: targetID,
|
||||
Value: value2,
|
||||
},
|
||||
}
|
||||
|
||||
// Should succeed - plugin2 can create values on plugin1's protected fields in non-CPA group
|
||||
created, err := pas.CreatePropertyValues(pluginID2, values)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, created, 2)
|
||||
})
|
||||
|
||||
t.Run("non-CPA group allows bulk upsert of values for protected fields", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-bulk-upsert")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create protected field
|
||||
field := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "protected-upsert",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
},
|
||||
}
|
||||
field, err = pas.CreatePropertyField("", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
targetID := model.NewId()
|
||||
value1, err := json.Marshal("initial value")
|
||||
require.NoError(t, err)
|
||||
|
||||
values := []*model.PropertyValue{
|
||||
{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
FieldID: field.ID,
|
||||
TargetType: "user",
|
||||
TargetID: targetID,
|
||||
Value: value1,
|
||||
},
|
||||
}
|
||||
|
||||
// Should succeed - plugin2 can upsert values on plugin1's protected field in non-CPA group
|
||||
created, err := pas.UpsertPropertyValues(pluginID2, values)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, created, 1)
|
||||
|
||||
// Update the value
|
||||
value2, err := json.Marshal("updated value")
|
||||
require.NoError(t, err)
|
||||
values[0].Value = value2
|
||||
|
||||
// Should succeed again
|
||||
updated, err := pas.UpsertPropertyValues(pluginID2, values)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, updated, 1)
|
||||
|
||||
var retrievedValue string
|
||||
err = json.Unmarshal(updated[0].Value, &retrievedValue)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "updated value", retrievedValue)
|
||||
})
|
||||
|
||||
t.Run("mixed CPA and non-CPA groups - enforces access control only on CPA group", func(t *testing.T) {
|
||||
// Register a non-CPA group
|
||||
nonCpaGroup, err := pas.RegisterPropertyGroup("other-group-mixed")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create protected field in CPA group
|
||||
cpaField := &model.PropertyField{
|
||||
GroupID: group.ID,
|
||||
Name: "cpa-protected-mixed",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
},
|
||||
}
|
||||
cpaField, err = pas.CreatePropertyFieldForPlugin(pluginID1, cpaField)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create protected field in non-CPA group
|
||||
nonCpaField := &model.PropertyField{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
Name: "non-cpa-protected-mixed",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
TargetType: "user",
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsAccessMode: model.PropertyAccessModeSourceOnly,
|
||||
model.PropertyAttrsProtected: true,
|
||||
model.PropertyAttrsSourcePluginID: pluginID1,
|
||||
},
|
||||
}
|
||||
nonCpaField, err = pas.CreatePropertyField("", nonCpaField)
|
||||
require.NoError(t, err)
|
||||
|
||||
targetID := model.NewId()
|
||||
cpaValue, err := json.Marshal("cpa data")
|
||||
require.NoError(t, err)
|
||||
nonCpaValue, err := json.Marshal("non-cpa data")
|
||||
require.NoError(t, err)
|
||||
|
||||
values := []*model.PropertyValue{
|
||||
{
|
||||
GroupID: group.ID,
|
||||
FieldID: cpaField.ID,
|
||||
TargetType: "user",
|
||||
TargetID: targetID,
|
||||
Value: cpaValue,
|
||||
},
|
||||
{
|
||||
GroupID: nonCpaGroup.ID,
|
||||
FieldID: nonCpaField.ID,
|
||||
TargetType: "user",
|
||||
TargetID: targetID,
|
||||
Value: nonCpaValue,
|
||||
},
|
||||
}
|
||||
|
||||
// Should fail - plugin2 cannot create value on CPA group protected field
|
||||
created, err := pas.CreatePropertyValues(pluginID2, values)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, created)
|
||||
assert.Contains(t, err.Error(), "protected")
|
||||
|
||||
// Verify no values were created (atomic failure)
|
||||
results, err := pas.SearchPropertyValues(pluginID1, group.ID, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{targetID},
|
||||
PerPage: 100,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
|
||||
results, err = pas.SearchPropertyValues(pluginID1, nonCpaGroup.ID, model.PropertyValueSearchOpts{
|
||||
TargetIDs: []string{targetID},
|
||||
PerPage: 100,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeletePropertyField_OrphanedFieldDeletion(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
groupID, err := th.App.CpaGroupID()
|
||||
require.NoError(t, err)
|
||||
pas := th.App.PropertyAccessService()
|
||||
|
||||
t.Run("allows deletion of orphaned protected field when plugin is uninstalled", func(t *testing.T) {
|
||||
pas.setPluginCheckerForTests(func(pluginID string) bool {
|
||||
return false
|
||||
})
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Orphaned Protected Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
},
|
||||
}
|
||||
created, err := pas.CreatePropertyFieldForPlugin("removed-plugin", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = pas.DeletePropertyField("admin-user", groupID, created.ID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("blocks deletion of protected field when plugin is still installed", func(t *testing.T) {
|
||||
pas.setPluginCheckerForTests(func(pluginID string) bool {
|
||||
return pluginID == "installed-plugin"
|
||||
})
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Active Protected Field",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
},
|
||||
}
|
||||
created, err := pas.CreatePropertyFieldForPlugin("installed-plugin", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = pas.DeletePropertyField("admin-user", groupID, created.ID)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "protected")
|
||||
assert.Contains(t, err.Error(), "installed-plugin")
|
||||
|
||||
err = pas.DeletePropertyField("installed-plugin", groupID, created.ID)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("blocks update of orphaned protected field even when plugin is uninstalled", func(t *testing.T) {
|
||||
pas.setPluginCheckerForTests(func(pluginID string) bool {
|
||||
return false
|
||||
})
|
||||
|
||||
field := &model.PropertyField{
|
||||
GroupID: groupID,
|
||||
Name: "Orphaned Field For Update",
|
||||
Type: model.PropertyFieldTypeText,
|
||||
Attrs: model.StringInterface{
|
||||
model.PropertyAttrsProtected: true,
|
||||
},
|
||||
}
|
||||
created, err := pas.CreatePropertyFieldForPlugin("removed-plugin", field)
|
||||
require.NoError(t, err)
|
||||
|
||||
created.Name = "Updated Orphaned Field"
|
||||
updated, err := pas.UpdatePropertyField("admin-user", groupID, created)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, updated)
|
||||
assert.Contains(t, err.Error(), "protected")
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ type Server struct {
|
|||
telemetryService *telemetry.TelemetryService
|
||||
userService *users.UserService
|
||||
teamService *teams.TeamService
|
||||
propertyService *properties.PropertyService
|
||||
propertyAccessService *PropertyAccessService
|
||||
|
||||
serviceMux sync.RWMutex
|
||||
|
|
@ -235,7 +234,7 @@ func NewServer(options ...Option) (*Server, error) {
|
|||
return nil, errors.Wrapf(err, "unable to create teams service")
|
||||
}
|
||||
|
||||
s.propertyService, err = properties.New(properties.ServiceConfig{
|
||||
propertyService, err := properties.New(properties.ServiceConfig{
|
||||
PropertyGroupStore: s.Store().PropertyGroup(),
|
||||
PropertyFieldStore: s.Store().PropertyField(),
|
||||
PropertyValueStore: s.Store().PropertyValue(),
|
||||
|
|
@ -245,7 +244,10 @@ func NewServer(options ...Option) (*Server, error) {
|
|||
}
|
||||
|
||||
// Wrap PropertyService with access control layer to enforce caller-based permissions
|
||||
s.propertyAccessService = NewPropertyAccessService(s.propertyService)
|
||||
s.propertyAccessService = NewPropertyAccessService(propertyService, func(pluginID string) bool {
|
||||
_, err := s.ch.GetPluginStatus(pluginID)
|
||||
return err == nil
|
||||
})
|
||||
|
||||
// It is important to initialize the hub only after the global logger is set
|
||||
// to avoid race conditions while logging from inside the hub.
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
|||
propertyValueStore := mocks.PropertyValueStore{}
|
||||
|
||||
propertyGroupStore.On("Register", model.ContentFlaggingGroupName).Return(&model.PropertyGroup{ID: model.NewId(), Name: model.ContentFlaggingGroupName}, nil)
|
||||
propertyGroupStore.On("Register", model.CustomProfileAttributesPropertyGroupName).Return(&model.PropertyGroup{ID: model.NewId(), Name: model.CustomProfileAttributesPropertyGroupName}, nil)
|
||||
|
||||
propertyFieldStore.On("SearchPropertyFields", mock.Anything).Return([]*model.PropertyField{}, nil)
|
||||
propertyFieldStore.On("CreatePropertyField", mock.Anything).Return(&model.PropertyField{}, nil)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import (
|
|||
|
||||
// cleanCPAFields removes all existing CPA fields to ensure clean test state
|
||||
func (s *MmctlE2ETestSuite) cleanCPAFields() {
|
||||
existingFields, appErr := s.th.App.ListCPAFields()
|
||||
existingFields, appErr := s.th.App.ListCPAFields("")
|
||||
s.Require().Nil(appErr)
|
||||
for _, field := range existingFields {
|
||||
appErr := s.th.App.DeleteCPAField(field.ID)
|
||||
appErr := s.th.App.DeleteCPAField("", field.ID)
|
||||
s.Require().Nil(appErr)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,11 +66,11 @@ func (s *MmctlE2ETestSuite) TestCPAFieldListCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdTextField, appErr := s.th.App.CreateCPAField(textField)
|
||||
createdTextField, appErr := s.th.App.CreateCPAField("", textField)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().NotNil(createdTextField)
|
||||
|
||||
createdSelectField, appErr := s.th.App.CreateCPAField(selectField)
|
||||
createdSelectField, appErr := s.th.App.CreateCPAField("", selectField)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().NotNil(createdSelectField)
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldCreateCmd() {
|
|||
s.Require().Contains(output, "Field Department correctly created")
|
||||
|
||||
// Verify field was actually created in the database
|
||||
fields, appErr := s.th.App.ListCPAFields()
|
||||
fields, appErr := s.th.App.ListCPAFields("")
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(fields, 1)
|
||||
s.Require().Equal("Department", fields[0].Name)
|
||||
|
|
@ -150,7 +150,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldCreateCmd() {
|
|||
s.Require().Contains(output, "Field Skills correctly created")
|
||||
|
||||
// Verify field was actually created in the database with correct options
|
||||
fields, appErr := s.th.App.ListCPAFields()
|
||||
fields, appErr := s.th.App.ListCPAFields("")
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(fields, 1)
|
||||
s.Require().Equal("Skills", fields[0].Name)
|
||||
|
|
@ -210,7 +210,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(field)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", field)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Now edit the field
|
||||
|
|
@ -237,7 +237,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
s.Require().Contains(output, "Field Programming Languages successfully updated")
|
||||
|
||||
// Verify field was actually updated
|
||||
updatedField, appErr := s.th.App.GetCPAField(createdField.ID)
|
||||
updatedField, appErr := s.th.App.GetCPAField("", createdField.ID)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Equal("Programming Languages", updatedField.Name)
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(field)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", field)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Now edit the field with --managed flag
|
||||
|
|
@ -287,7 +287,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
|
||||
// Verify field was actually updated
|
||||
updatedField, appErr := s.th.App.GetCPAField(createdField.ID)
|
||||
updatedField, appErr := s.th.App.GetCPAField("", createdField.ID)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Verify that managed flag was set correctly
|
||||
|
|
@ -310,7 +310,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(field)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", field)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Now edit the field using its name instead of ID
|
||||
|
|
@ -336,7 +336,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
s.Require().Contains(output, "Field Team successfully updated")
|
||||
|
||||
// Verify field was actually updated by retrieving it
|
||||
updatedField, appErr := s.th.App.GetCPAField(createdField.ID)
|
||||
updatedField, appErr := s.th.App.GetCPAField("", createdField.ID)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Equal("Team", updatedField.Name)
|
||||
|
||||
|
|
@ -363,7 +363,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(field)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", field)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Get the original option IDs to verify they are preserved
|
||||
|
|
@ -406,7 +406,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|||
s.Require().Contains(output, "Field Programming Languages successfully updated")
|
||||
|
||||
// Verify field was actually updated and options are preserved correctly
|
||||
updatedField, appErr := s.th.App.GetCPAField(createdField.ID)
|
||||
updatedField, appErr := s.th.App.GetCPAField("", createdField.ID)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Check options
|
||||
|
|
@ -456,7 +456,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldDeleteCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(field)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", field)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
|
|
@ -475,7 +475,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldDeleteCmd() {
|
|||
s.Require().Contains(output, "Successfully deleted CPA field")
|
||||
|
||||
// Verify field was actually deleted by checking if it exists in the list
|
||||
fields, appErr := s.th.App.ListCPAFields()
|
||||
fields, appErr := s.th.App.ListCPAFields("")
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Field should not be in the list anymore
|
||||
|
|
@ -502,7 +502,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldDeleteCmd() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(field)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", field)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
|
|
@ -522,7 +522,7 @@ func (s *MmctlE2ETestSuite) TestCPAFieldDeleteCmd() {
|
|||
s.Require().Contains(output, "Successfully deleted CPA field: Department")
|
||||
|
||||
// Verify field was actually deleted by checking if it exists in the list
|
||||
fields, appErr := s.th.App.ListCPAFields()
|
||||
fields, appErr := s.th.App.ListCPAFields("")
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Field should not be in the list anymore
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
// cleanCPAValuesForUser removes all CPA values for a user
|
||||
func (s *MmctlE2ETestSuite) cleanCPAValuesForUser(userID string) {
|
||||
existingValues, appErr := s.th.App.ListCPAValues(userID)
|
||||
existingValues, appErr := s.th.App.ListCPAValues("", userID)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Clear all existing values by setting them to null
|
||||
|
|
@ -24,7 +24,7 @@ func (s *MmctlE2ETestSuite) cleanCPAValuesForUser(userID string) {
|
|||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
_, appErr = s.th.App.PatchCPAValues(userID, updates, false)
|
||||
_, appErr = s.th.App.PatchCPAValues("", userID, updates, false)
|
||||
s.Require().Nil(appErr)
|
||||
}
|
||||
}
|
||||
|
|
@ -64,14 +64,14 @@ func (s *MmctlE2ETestSuite) TestCPAValueList() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(textField)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", textField)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Set a text value using the app layer
|
||||
updates := map[string]json.RawMessage{
|
||||
createdField.ID: json.RawMessage(`"Engineering"`),
|
||||
}
|
||||
_, appErr = s.th.App.PatchCPAValues(s.th.BasicUser.Id, updates, false)
|
||||
_, appErr = s.th.App.PatchCPAValues("", s.th.BasicUser.Id, updates, false)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Test listing the values with plain format (human-readable)
|
||||
|
|
@ -122,7 +122,7 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(textField)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", textField)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Set a text value
|
||||
|
|
@ -135,7 +135,8 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
s.Require().Nil(err)
|
||||
|
||||
// Verify the value was set
|
||||
values, appErr := s.th.App.ListCPAValues(s.th.BasicUser.Id)
|
||||
|
||||
values, appErr := s.th.App.ListCPAValues("", s.th.BasicUser.Id)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(values, 1)
|
||||
s.Require().Equal(createdField.ID, values[0].FieldID)
|
||||
|
|
@ -165,7 +166,7 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(selectField)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", selectField)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Set a select value using the option name
|
||||
|
|
@ -178,7 +179,8 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
s.Require().Nil(err)
|
||||
|
||||
// Verify the value was set (should be stored as option ID)
|
||||
values, appErr := s.th.App.ListCPAValues(s.th.BasicUser.Id)
|
||||
|
||||
values, appErr := s.th.App.ListCPAValues("", s.th.BasicUser.Id)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(values, 1)
|
||||
s.Require().Equal(createdField.ID, values[0].FieldID)
|
||||
|
|
@ -218,7 +220,7 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(multiselectField)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", multiselectField)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Set multiple values using option names
|
||||
|
|
@ -236,7 +238,8 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
s.Require().Nil(err)
|
||||
|
||||
// Verify the values were set (should be stored as option IDs)
|
||||
values, appErr := s.th.App.ListCPAValues(s.th.BasicUser.Id)
|
||||
|
||||
values, appErr := s.th.App.ListCPAValues("", s.th.BasicUser.Id)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(values, 1)
|
||||
s.Require().Equal(createdField.ID, values[0].FieldID)
|
||||
|
|
@ -285,7 +288,7 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(multiselectField)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", multiselectField)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Set a single value using option name
|
||||
|
|
@ -299,7 +302,8 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
s.Require().Nil(err)
|
||||
|
||||
// Verify the value was set (should be stored as an array with single option ID)
|
||||
values, appErr := s.th.App.ListCPAValues(s.th.BasicUser.Id)
|
||||
|
||||
values, appErr := s.th.App.ListCPAValues("", s.th.BasicUser.Id)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(values, 1)
|
||||
s.Require().Equal(createdField.ID, values[0].FieldID)
|
||||
|
|
@ -345,7 +349,7 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
},
|
||||
}
|
||||
|
||||
createdField, appErr := s.th.App.CreateCPAField(userField)
|
||||
createdField, appErr := s.th.App.CreateCPAField("", userField)
|
||||
s.Require().Nil(appErr)
|
||||
|
||||
// Set a user value using the system admin user ID
|
||||
|
|
@ -358,7 +362,8 @@ func (s *MmctlE2ETestSuite) TestCPAValueSet() {
|
|||
s.Require().Nil(err)
|
||||
|
||||
// Verify the value was set
|
||||
values, appErr := s.th.App.ListCPAValues(s.th.BasicUser.Id)
|
||||
|
||||
values, appErr := s.th.App.ListCPAValues("", s.th.BasicUser.Id)
|
||||
s.Require().Nil(appErr)
|
||||
s.Require().Len(values, 1)
|
||||
s.Require().Equal(createdField.ID, values[0].FieldID)
|
||||
|
|
|
|||
|
|
@ -112,13 +112,16 @@ type CPAField struct {
|
|||
}
|
||||
|
||||
type CPAAttrs struct {
|
||||
Visibility string `json:"visibility"`
|
||||
SortOrder float64 `json:"sort_order"`
|
||||
Options PropertyOptions[*CustomProfileAttributesSelectOption] `json:"options"`
|
||||
ValueType string `json:"value_type"`
|
||||
LDAP string `json:"ldap"`
|
||||
SAML string `json:"saml"`
|
||||
Managed string `json:"managed"`
|
||||
Visibility string `json:"visibility"`
|
||||
SortOrder float64 `json:"sort_order"`
|
||||
Options PropertyOptions[*CustomProfileAttributesSelectOption] `json:"options"`
|
||||
ValueType string `json:"value_type"`
|
||||
LDAP string `json:"ldap"`
|
||||
SAML string `json:"saml"`
|
||||
Managed string `json:"managed"`
|
||||
Protected bool `json:"protected"`
|
||||
SourcePluginID string `json:"source_plugin_id"`
|
||||
AccessMode string `json:"access_mode"`
|
||||
}
|
||||
|
||||
func (c *CPAField) IsSynced() bool {
|
||||
|
|
@ -173,6 +176,9 @@ func (c *CPAField) ToPropertyField() *PropertyField {
|
|||
CustomProfileAttributesPropertyAttrsLDAP: c.Attrs.LDAP,
|
||||
CustomProfileAttributesPropertyAttrsSAML: c.Attrs.SAML,
|
||||
CustomProfileAttributesPropertyAttrsManaged: c.Attrs.Managed,
|
||||
PropertyAttrsProtected: c.Attrs.Protected,
|
||||
PropertyAttrsSourcePluginID: c.Attrs.SourcePluginID,
|
||||
PropertyAttrsAccessMode: c.Attrs.AccessMode,
|
||||
}
|
||||
|
||||
return &pf
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ export type UserPropertyField = PropertyField & {
|
|||
ldap?: string;
|
||||
saml?: string;
|
||||
managed?: string;
|
||||
protected?: boolean;
|
||||
source_plugin_id?: string;
|
||||
access_mode?: '' | 'source_only' | 'shared_only';
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue