mattermost-community-enterp.../channels/api4/custom_profile_attributes.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

376 lines
12 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"encoding/json"
"net/http"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
)
func (api *API) InitCustomProfileAttributes() {
if api.srv.Config().FeatureFlags.CustomProfileAttributes {
api.BaseRoutes.CustomProfileAttributesFields.Handle("", api.APISessionRequired(listCPAFields)).Methods(http.MethodGet)
api.BaseRoutes.CustomProfileAttributesFields.Handle("", api.APISessionRequired(createCPAField)).Methods(http.MethodPost)
api.BaseRoutes.CustomProfileAttributesField.Handle("", api.APISessionRequired(patchCPAField)).Methods(http.MethodPatch)
api.BaseRoutes.CustomProfileAttributesField.Handle("", api.APISessionRequired(deleteCPAField)).Methods(http.MethodDelete)
api.BaseRoutes.User.Handle("/custom_profile_attributes", api.APISessionRequired(listCPAValues)).Methods(http.MethodGet)
api.BaseRoutes.CustomProfileAttributesValues.Handle("", api.APISessionRequired(patchCPAValues)).Methods(http.MethodPatch)
api.BaseRoutes.CustomProfileAttributes.Handle("/group", api.APISessionRequired(getCPAGroup)).Methods(http.MethodGet)
api.BaseRoutes.User.Handle("/custom_profile_attributes", api.APISessionRequired(patchCPAValuesForUser)).Methods(http.MethodPatch)
}
}
func listCPAFields(c *Context, w http.ResponseWriter, r *http.Request) {
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
c.Err = model.NewAppError("Api4.listCPAFields", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
return
}
fields, appErr := c.App.ListCPAFields()
if appErr != nil {
c.Err = appErr
return
}
if err := json.NewEncoder(w).Encode(fields); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
func createCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
c.Err = model.NewAppError("Api4.createCPAField", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
return
}
var pf *model.CPAField
err := json.NewDecoder(r.Body).Decode(&pf)
if err != nil || pf == nil {
c.SetInvalidParamWithErr("property_field", err)
return
}
pf.Name = strings.TrimSpace(pf.Name)
auditRec := c.MakeAuditRecord(model.AuditEventCreateCPAField, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
model.AddEventParameterAuditableToAuditRec(auditRec, "property_field", pf)
createdField, appErr := c.App.CreateCPAField(pf)
if appErr != nil {
c.Err = appErr
return
}
auditRec.Success()
auditRec.AddEventResultState(createdField)
auditRec.AddEventObjectType("property_field")
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(createdField); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
func patchCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
c.Err = model.NewAppError("Api4.patchCPAField", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
return
}
c.RequireFieldId()
if c.Err != nil {
return
}
var patch *model.PropertyFieldPatch
err := json.NewDecoder(r.Body).Decode(&patch)
if err != nil || patch == nil {
c.SetInvalidParamWithErr("property_field_patch", err)
return
}
if patch.Name != nil {
*patch.Name = strings.TrimSpace(*patch.Name)
}
if err := patch.IsValid(); err != nil {
if appErr, ok := err.(*model.AppError); ok {
c.Err = appErr
} else {
c.Err = model.NewAppError("createCPAField", "api.custom_profile_attributes.invalid_field_patch", nil, "", http.StatusBadRequest)
}
return
}
auditRec := c.MakeAuditRecord(model.AuditEventPatchCPAField, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
model.AddEventParameterAuditableToAuditRec(auditRec, "property_field_patch", patch)
originalField, appErr := c.App.GetCPAField(c.Params.FieldId)
if appErr != nil {
c.Err = appErr
return
}
auditRec.AddEventPriorState(originalField)
patchedField, appErr := c.App.PatchCPAField(c.Params.FieldId, patch)
if appErr != nil {
c.Err = appErr
return
}
auditRec.Success()
auditRec.AddEventResultState(patchedField)
auditRec.AddEventObjectType("property_field")
if err := json.NewEncoder(w).Encode(patchedField); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
func deleteCPAField(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
c.Err = model.NewAppError("Api4.deleteCPAField", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
return
}
c.RequireFieldId()
if c.Err != nil {
return
}
auditRec := c.MakeAuditRecord(model.AuditEventDeleteCPAField, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
model.AddEventParameterToAuditRec(auditRec, "field_id", c.Params.FieldId)
field, appErr := c.App.GetCPAField(c.Params.FieldId)
if appErr != nil {
c.Err = appErr
return
}
auditRec.AddEventPriorState(field)
if appErr := c.App.DeleteCPAField(c.Params.FieldId); appErr != nil {
c.Err = appErr
return
}
auditRec.Success()
auditRec.AddEventResultState(field)
auditRec.AddEventObjectType("property_field")
ReturnStatusOK(w)
}
func getCPAGroup(c *Context, w http.ResponseWriter, r *http.Request) {
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
c.Err = model.NewAppError("Api4.getCPAGroup", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
return
}
groupID, err := c.App.CpaGroupID()
if err != nil {
c.Err = model.NewAppError("Api4.getCPAGroup", "app.custom_profile_attributes.cpa_group_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
return
}
if err := json.NewEncoder(w).Encode(map[string]string{"id": groupID}); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
func patchCPAValues(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)
return
}
userID := c.AppContext.Session().UserId
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), userID) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
var updates map[string]json.RawMessage
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
c.SetInvalidParamWithErr("value", err)
return
}
auditRec := c.MakeAuditRecord(model.AuditEventPatchCPAValues, model.AuditStatusFail)
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()
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 {
// Convert to CPAField to check if managed
cpaField, fErr := model.NewCPAFieldFromPropertyField(field)
if fErr != nil {
c.Err = model.NewAppError("Api4.patchCPAValues", "app.custom_profile_attributes.property_field_conversion.app_error", nil, "", http.StatusInternalServerError).Wrap(fErr)
return
}
if cpaField.IsAdminManaged() {
c.Err = model.NewAppError("Api4.patchCPAValues", "app.custom_profile_attributes.property_field_is_managed.app_error", nil, "", http.StatusForbidden)
return
}
}
}
}
results := make(map[string]json.RawMessage, len(updates))
for fieldID, rawValue := range updates {
patchedValue, appErr := c.App.PatchCPAValue(userID, fieldID, rawValue, false)
if appErr != nil {
c.Err = appErr
return
}
results[fieldID] = patchedValue.Value
}
auditRec.Success()
auditRec.AddEventObjectType("patchCPAValues")
if err := json.NewEncoder(w).Encode(results); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
func listCPAValues(c *Context, w http.ResponseWriter, r *http.Request) {
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) {
c.Err = model.NewAppError("Api4.listCPAValues", "api.custom_profile_attributes.license_error", nil, "", http.StatusForbidden)
return
}
c.RequireUserId()
if c.Err != nil {
return
}
userID := c.Params.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)
if err != nil || !canSee {
c.SetPermissionError(model.PermissionViewMembers)
return
}
}
values, appErr := c.App.ListCPAValues(userID)
if appErr != nil {
c.Err = appErr
return
}
returnValue := make(map[string]json.RawMessage)
for _, value := range values {
returnValue[value.FieldID] = value.Value
}
if err := json.NewEncoder(w).Encode(returnValue); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}
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)
return
}
// Get userID from URL
c.RequireUserId()
if c.Err != nil {
return
}
userID := c.Params.UserId
if !c.App.SessionHasPermissionToUser(*c.AppContext.Session(), userID) {
c.SetPermissionError(model.PermissionEditOtherUsers)
return
}
var updates map[string]json.RawMessage
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
c.SetInvalidParamWithErr("value", err)
return
}
auditRec := c.MakeAuditRecord(model.AuditEventPatchCPAValues, model.AuditStatusFail)
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()
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 {
// Convert to CPAField to check if managed
cpaField, fErr := model.NewCPAFieldFromPropertyField(field)
if fErr != nil {
c.Err = model.NewAppError("Api4.patchCPAValues", "app.custom_profile_attributes.property_field_conversion.app_error", nil, "", http.StatusInternalServerError).Wrap(fErr)
return
}
if cpaField.IsAdminManaged() {
c.Err = model.NewAppError("Api4.patchCPAValues", "app.custom_profile_attributes.property_field_is_managed.app_error", nil, "", http.StatusForbidden)
return
}
}
}
}
results := make(map[string]json.RawMessage, len(updates))
for fieldID, rawValue := range updates {
patchedValue, appErr := c.App.PatchCPAValue(userID, fieldID, rawValue, false)
if appErr != nil {
c.Err = appErr
return
}
results[fieldID] = patchedValue.Value
}
auditRec.Success()
auditRec.AddEventObjectType("patchCPAValues")
if err := json.NewEncoder(w).Encode(results); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
}