Update dependencies (#2180)
Some checks failed
Development / golangci-lint (push) Has been cancelled
Development / test-build-upload (1.22.x, ubuntu-latest) (push) Has been cancelled

* Update dependencies

* Fix whatsmeow API changes
This commit is contained in:
Wim
2024-08-27 19:04:05 +02:00
committed by GitHub
parent d16645c952
commit c4157a4d5b
589 changed files with 681707 additions and 198856 deletions

View File

@@ -750,7 +750,6 @@ func newRemoteCluster(r *RemoteCluster) auditRemoteCluster {
var rc auditRemoteCluster
if r != nil {
rc.RemoteId = r.RemoteId
rc.RemoteTeamId = r.RemoteTeamId
rc.Name = r.Name
rc.DisplayName = r.DisplayName
rc.SiteURL = r.SiteURL

View File

@@ -7,3 +7,14 @@ func NewBool(b bool) *bool { return &b }
func NewInt(n int) *int { return &n }
func NewInt64(n int64) *int64 { return &n }
func NewString(s string) *string { return &s }
func NewPointer[T any](t T) *T { return &t }
// SafeDereference returns the zero value of T if t is nil.
// Otherwise it return the derference of t.
func SafeDereference[T any](t *T) T {
if t == nil {
var t T
return t
}
return *t
}

View File

@@ -568,6 +568,10 @@ func (c *Client4) exportRoute(name string) string {
return fmt.Sprintf(c.exportsRoute()+"/%v", name)
}
func (c *Client4) remoteClusterRoute() string {
return "/remotecluster"
}
func (c *Client4) sharedChannelsRoute() string {
return "/sharedchannels"
}
@@ -698,7 +702,7 @@ func (c *Client4) DoUploadFile(ctx context.Context, url string, data []byte, con
}
func (c *Client4) doUploadFile(ctx context.Context, url string, body io.Reader, contentType string, contentLength int64) (*FileUploadResponse, *Response, error) {
rq, err := http.NewRequest("POST", c.APIURL+url, body)
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+url, body)
if err != nil {
return nil, nil, err
}
@@ -757,7 +761,7 @@ func (c *Client4) DoEmojiUploadFile(ctx context.Context, url string, data []byte
}
func (c *Client4) DoUploadImportTeam(ctx context.Context, url string, data []byte, contentType string) (map[string]string, *Response, error) {
rq, err := http.NewRequest("POST", c.APIURL+url, bytes.NewReader(data))
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+url, bytes.NewReader(data))
if err != nil {
return nil, nil, err
}
@@ -1817,7 +1821,7 @@ func (c *Client4) SetProfileImage(ctx context.Context, userId string, data []byt
return nil, NewAppError("SetProfileImage", "model.client.set_profile_user.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
rq, err := http.NewRequest("POST", c.APIURL+c.userRoute(userId)+"/image", bytes.NewReader(body.Bytes()))
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.userRoute(userId)+"/image", bytes.NewReader(body.Bytes()))
if err != nil {
return nil, err
}
@@ -2889,7 +2893,7 @@ func (c *Client4) SetTeamIcon(ctx context.Context, teamId string, data []byte) (
return nil, NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
rq, err := http.NewRequest("POST", c.APIURL+c.teamRoute(teamId)+"/image", bytes.NewReader(body.Bytes()))
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.teamRoute(teamId)+"/image", bytes.NewReader(body.Bytes()))
if err != nil {
return nil, err
}
@@ -3730,6 +3734,23 @@ func (c *Client4) AddChannelMember(ctx context.Context, channelId, userId string
return ch, BuildResponse(r), nil
}
// AddChannelMembers adds users to a channel and return an array of channel members.
func (c *Client4) AddChannelMembers(ctx context.Context, channelId, postRootId string, userIds []string) ([]*ChannelMember, *Response, error) {
requestBody := map[string]any{"user_ids": userIds, "post_root_id": postRootId}
r, err := c.DoAPIPost(ctx, c.channelMembersRoute(channelId)+"", StringInterfaceToJSON(requestBody))
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var ch []*ChannelMember
err = json.NewDecoder(r.Body).Decode(&ch)
if err != nil {
return nil, BuildResponse(r), NewAppError("AddChannelMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return ch, BuildResponse(r), nil
}
// AddChannelMemberWithRootId adds user to channel and return a channel member. Post add to channel message has the postRootId.
func (c *Client4) AddChannelMemberWithRootId(ctx context.Context, channelId, userId, postRootId string) (*ChannelMember, *Response, error) {
requestBody := map[string]string{"user_id": userId, "post_root_id": postRootId}
@@ -4850,7 +4871,7 @@ func (c *Client4) UploadLicenseFile(ctx context.Context, data []byte) (*Response
return nil, NewAppError("UploadLicenseFile", "model.client.set_profile_user.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
rq, err := http.NewRequest("POST", c.APIURL+c.licenseRoute(), bytes.NewReader(body.Bytes()))
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.licenseRoute(), bytes.NewReader(body.Bytes()))
if err != nil {
return nil, err
}
@@ -5422,7 +5443,7 @@ func (c *Client4) GetComplianceReport(ctx context.Context, reportId string) (*Co
// DownloadComplianceReport returns a full compliance report as a file.
func (c *Client4) DownloadComplianceReport(ctx context.Context, reportId string) ([]byte, *Response, error) {
rq, err := http.NewRequest("GET", c.APIURL+c.complianceReportDownloadRoute(reportId), nil)
rq, err := http.NewRequestWithContext(ctx, "GET", c.APIURL+c.complianceReportDownloadRoute(reportId), nil)
if err != nil {
return nil, nil, err
}
@@ -5826,7 +5847,7 @@ func (c *Client4) UploadBrandImage(ctx context.Context, data []byte) (*Response,
return nil, NewAppError("UploadBrandImage", "model.client.set_profile_user.writer.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
rq, err := http.NewRequest("POST", c.APIURL+c.brandRoute()+"/image", bytes.NewReader(body.Bytes()))
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.brandRoute()+"/image", bytes.NewReader(body.Bytes()))
if err != nil {
return nil, err
}
@@ -5862,6 +5883,20 @@ func (c *Client4) GetLogs(ctx context.Context, page, perPage int) ([]string, *Re
return c.ArrayFromJSON(r.Body), BuildResponse(r), nil
}
// Download logs as mattermost.log file
func (c *Client4) DownloadLogs(ctx context.Context) ([]byte, *Response, error) {
r, err := c.DoAPIGet(ctx, "/logs/download", "")
if err != nil {
return nil, BuildResponse(r), err
}
data, err := io.ReadAll(r.Body)
if err != nil {
return nil, BuildResponse(r), NewAppError("DownloadLogs", "model.client.read_file.app_error", nil, "", r.StatusCode).Wrap(err)
}
return data, BuildResponse(r), nil
}
// PostLog is a convenience Web Service call so clients can log messages into
// the server-side logs. For example we typically log javascript error messages
// into the server-side. It returns the log message if the logging was successful.
@@ -6023,7 +6058,7 @@ func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Respo
// GetOAuthAccessToken is a test helper function for the OAuth access token endpoint.
func (c *Client4) GetOAuthAccessToken(ctx context.Context, data url.Values) (*AccessResponse, *Response, error) {
url := c.URL + "/oauth/access_token"
rq, err := http.NewRequest(http.MethodPost, url, strings.NewReader(data.Encode()))
rq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(data.Encode()))
if err != nil {
return nil, nil, err
}
@@ -7018,8 +7053,8 @@ func (c *Client4) GetJob(ctx context.Context, id string) (*Job, *Response, error
}
// GetJobs gets all jobs, sorted with the job that was created most recently first.
func (c *Client4) GetJobs(ctx context.Context, page int, perPage int) ([]*Job, *Response, error) {
r, err := c.DoAPIGet(ctx, c.jobsRoute()+fmt.Sprintf("?page=%v&per_page=%v", page, perPage), "")
func (c *Client4) GetJobs(ctx context.Context, jobType string, status string, page int, perPage int) ([]*Job, *Response, error) {
r, err := c.DoAPIGet(ctx, c.jobsRoute()+fmt.Sprintf("?page=%v&per_page=%v&job_type=%v&status=%v", page, perPage, jobType, status), "")
if err != nil {
return nil, BuildResponse(r), err
}
@@ -7088,6 +7123,23 @@ func (c *Client4) DownloadJob(ctx context.Context, jobId string) ([]byte, *Respo
return data, BuildResponse(r), nil
}
// UpdateJobStatus updates the status of a job
func (c *Client4) UpdateJobStatus(ctx context.Context, jobId string, status string, force bool) (*Response, error) {
buf, err := json.Marshal(map[string]any{
"status": status,
"force": force,
})
if err != nil {
return nil, NewAppError("UpdateJobStatus", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
r, err := c.DoAPIPatchBytes(ctx, c.jobsRoute()+fmt.Sprintf("/%v/status", jobId), buf)
if err != nil {
return BuildResponse(r), err
}
defer closeBody(r)
return BuildResponse(r), nil
}
// Roles Section
// GetAllRoles returns a list of all the roles.
@@ -7305,7 +7357,7 @@ func (c *Client4) uploadPlugin(ctx context.Context, file io.Reader, force bool)
return nil, nil, err
}
rq, err := http.NewRequest("POST", c.APIURL+c.pluginsRoute(), body)
rq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL+c.pluginsRoute(), body)
if err != nil {
return nil, nil, err
}
@@ -8650,10 +8702,158 @@ func (c *Client4) GetRemoteClusterInfo(ctx context.Context, remoteID string) (Re
return rci, BuildResponse(r), nil
}
func (c *Client4) GetRemoteClusters(ctx context.Context, page, perPage int, filter RemoteClusterQueryFilter) ([]*RemoteCluster, *Response, error) {
v := url.Values{}
if page != 0 {
v.Set("page", fmt.Sprintf("%d", page))
}
if perPage != 0 {
v.Set("per_page", fmt.Sprintf("%d", perPage))
}
if filter.ExcludeOffline {
v.Set("exclude_offline", "true")
}
if filter.InChannel != "" {
v.Set("in_channel", filter.InChannel)
}
if filter.NotInChannel != "" {
v.Set("not_in_channel", filter.NotInChannel)
}
if filter.Topic != "" {
v.Set("topic", filter.Topic)
}
if filter.CreatorId != "" {
v.Set("creator_id", filter.CreatorId)
}
if filter.OnlyConfirmed {
v.Set("only_confirmed", "true")
}
if filter.PluginID != "" {
v.Set("plugin_id", filter.PluginID)
}
if filter.OnlyPlugins {
v.Set("only_plugins", "true")
}
if filter.ExcludePlugins {
v.Set("exclude_plugins", "true")
}
url := c.remoteClusterRoute()
if len(v) > 0 {
url += "?" + v.Encode()
}
r, err := c.DoAPIGet(ctx, url, "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rcs []*RemoteCluster
json.NewDecoder(r.Body).Decode(&rcs)
return rcs, BuildResponse(r), nil
}
func (c *Client4) CreateRemoteCluster(ctx context.Context, rcWithPassword *RemoteClusterWithPassword) (*RemoteClusterWithInvite, *Response, error) {
rcJSON, err := json.Marshal(rcWithPassword)
if err != nil {
return nil, nil, NewAppError("CreateRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
r, err := c.DoAPIPost(ctx, c.remoteClusterRoute(), string(rcJSON))
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rcWithInvite RemoteClusterWithInvite
if err := json.NewDecoder(r.Body).Decode(&rcWithInvite); err != nil {
return nil, nil, NewAppError("CreateRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &rcWithInvite, BuildResponse(r), nil
}
func (c *Client4) RemoteClusterAcceptInvite(ctx context.Context, rcAcceptInvite *RemoteClusterAcceptInvite) (*RemoteCluster, *Response, error) {
rcAcceptInviteJSON, err := json.Marshal(rcAcceptInvite)
if err != nil {
return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
url := fmt.Sprintf("%s/accept_invite", c.remoteClusterRoute())
r, err := c.DoAPIPost(ctx, url, string(rcAcceptInviteJSON))
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rc RemoteCluster
if err := json.NewDecoder(r.Body).Decode(&rc); err != nil {
return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &rc, BuildResponse(r), nil
}
func (c *Client4) GenerateRemoteClusterInvite(ctx context.Context, remoteClusterId, password string) (string, *Response, error) {
url := fmt.Sprintf("%s/%s/generate_invite", c.remoteClusterRoute(), remoteClusterId)
r, err := c.DoAPIPost(ctx, url, MapToJSON(map[string]string{"password": password}))
if err != nil {
return "", BuildResponse(r), err
}
defer closeBody(r)
b, err := io.ReadAll(r.Body)
if err != nil {
return "", nil, NewAppError("GenerateRemoteClusterInvite", "api.read_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return string(b), BuildResponse(r), nil
}
func (c *Client4) GetRemoteCluster(ctx context.Context, remoteClusterId string) (*RemoteCluster, *Response, error) {
r, err := c.DoAPIGet(ctx, fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId), "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rc *RemoteCluster
json.NewDecoder(r.Body).Decode(&rc)
return rc, BuildResponse(r), nil
}
func (c *Client4) PatchRemoteCluster(ctx context.Context, remoteClusterId string, patch *RemoteClusterPatch) (*RemoteCluster, *Response, error) {
patchJSON, err := json.Marshal(patch)
if err != nil {
return nil, nil, NewAppError("PatchRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
url := fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId)
r, err := c.DoAPIPatchBytes(ctx, url, patchJSON)
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rc RemoteCluster
if err := json.NewDecoder(r.Body).Decode(&rc); err != nil {
return nil, nil, NewAppError("PatchRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &rc, BuildResponse(r), nil
}
func (c *Client4) DeleteRemoteCluster(ctx context.Context, remoteClusterId string) (*Response, error) {
r, err := c.DoAPIDelete(ctx, fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId))
if err != nil {
return BuildResponse(r), err
}
defer closeBody(r)
return BuildResponse(r), nil
}
func (c *Client4) GetAncillaryPermissions(ctx context.Context, subsectionPermissions []string) ([]string, *Response, error) {
var returnedPermissions []string
url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.permissionsRoute(), strings.Join(subsectionPermissions, ","))
r, err := c.DoAPIGet(ctx, url, "")
url := fmt.Sprintf("%s/ancillary", c.permissionsRoute())
r, err := c.DoAPIPost(ctx, url, ArrayToJSON(subsectionPermissions))
if err != nil {
return returnedPermissions, BuildResponse(r), err
}

View File

@@ -112,6 +112,7 @@ const (
ServiceSettingsDefaultGiphySdkKeyTest = "s0glxvzVg9azvPipKxcPLpXV0q1x1fVP"
ServiceSettingsDefaultDeveloperFlags = ""
ServiceSettingsDefaultUniqueReactionsPerPost = 50
ServiceSettingsDefaultMaxURLLength = 2048
ServiceSettingsMaxUniqueReactionsPerPost = 500
TeamSettingsDefaultSiteName = "Mattermost"
@@ -178,7 +179,8 @@ const (
NativeappSettingsDefaultAndroidAppDownloadLink = "https://mattermost.com/pl/android-app/"
NativeappSettingsDefaultIosAppDownloadLink = "https://mattermost.com/pl/ios-app/"
ExperimentalSettingsDefaultLinkMetadataTimeoutMilliseconds = 5000
ExperimentalSettingsDefaultLinkMetadataTimeoutMilliseconds = 5000
ExperimentalSettingsDefaultUsersStatusAndProfileFetchingPollIntervalMilliseconds = 3000
AnalyticsSettingsDefaultMaxUsersForStatistics = 2500
@@ -204,6 +206,8 @@ const (
ElasticsearchSettingsDefaultLiveIndexingBatchSize = 1
ElasticsearchSettingsDefaultRequestTimeoutSeconds = 30
ElasticsearchSettingsDefaultBatchSize = 10000
ElasticsearchSettingsESBackend = "elasticsearch"
ElasticsearchSettingsOSBackend = "opensearch"
BleveSettingsDefaultIndexDir = ""
BleveSettingsDefaultBatchSize = 10000
@@ -340,6 +344,7 @@ type ServiceSettings struct {
CorsDebug *bool `access:"integrations_cors,write_restrictable,cloud_restrictable"`
AllowCookiesForSubdomains *bool `access:"write_restrictable,cloud_restrictable"`
ExtendSessionLengthWithActivity *bool `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
TerminateSessionsOnPasswordChange *bool `access:"environment_session_lengths,write_restrictable,cloud_restrictable"`
// Deprecated
SessionLengthWebInDays *int `access:"environment_session_lengths,write_restrictable,cloud_restrictable"` // telemetry: none
@@ -370,7 +375,6 @@ type ServiceSettings struct {
EnableUserStatuses *bool `access:"write_restrictable,cloud_restrictable"`
ExperimentalEnableAuthenticationTransfer *bool `access:"experimental_features"`
ClusterLogTimeoutMilliseconds *int `access:"write_restrictable,cloud_restrictable"`
EnablePreviewFeatures *bool `access:"experimental_features"`
EnableTutorial *bool `access:"experimental_features"`
EnableOnboardingFlow *bool `access:"experimental_features"`
ExperimentalEnableDefaultChannelLeaveJoinMessages *bool `access:"experimental_features"`
@@ -407,6 +411,7 @@ type ServiceSettings struct {
UniqueEmojiReactionLimitPerPost *int `access:"site_posts"`
RefreshPostStatsRunTime *string `access:"site_users_and_teams"`
MaximumPayloadSizeBytes *int64 `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
MaximumURLLength *int `access:"environment_file_storage,write_restrictable,cloud_restrictable"`
}
var MattermostGiphySdkKey string
@@ -630,6 +635,11 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
s.ExtendSessionLengthWithActivity = NewBool(!isUpdate)
}
// Must be manually enabled for existing installations.
if s.TerminateSessionsOnPasswordChange == nil {
s.TerminateSessionsOnPasswordChange = NewBool(!isUpdate)
}
if s.SessionLengthWebInDays == nil {
if isUpdate {
s.SessionLengthWebInDays = NewInt(180)
@@ -766,10 +776,6 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
s.PostEditTimeLimit = NewInt(-1)
}
if s.EnablePreviewFeatures == nil {
s.EnablePreviewFeatures = NewBool(true)
}
if s.ExperimentalEnableDefaultChannelLeaveJoinMessages == nil {
s.ExperimentalEnableDefaultChannelLeaveJoinMessages = NewBool(true)
}
@@ -917,6 +923,10 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) {
if s.MaximumPayloadSizeBytes == nil {
s.MaximumPayloadSizeBytes = NewInt64(300000)
}
if s.MaximumURLLength == nil {
s.MaximumURLLength = NewInt(ServiceSettingsDefaultMaxURLLength)
}
}
type ClusterSettings struct {
@@ -1010,16 +1020,17 @@ func (s *MetricsSettings) SetDefaults() {
}
type ExperimentalSettings struct {
ClientSideCertEnable *bool `access:"experimental_features,cloud_restrictable"`
ClientSideCertCheck *string `access:"experimental_features,cloud_restrictable"`
LinkMetadataTimeoutMilliseconds *int64 `access:"experimental_features,write_restrictable,cloud_restrictable"`
RestrictSystemAdmin *bool `access:"experimental_features,write_restrictable"`
EnableSharedChannels *bool `access:"experimental_features"`
EnableRemoteClusterService *bool `access:"experimental_features"`
DisableAppBar *bool `access:"experimental_features"`
DisableRefetchingOnBrowserFocus *bool `access:"experimental_features"`
DelayChannelAutocomplete *bool `access:"experimental_features"`
DisableWakeUpReconnectHandler *bool `access:"experimental_features"`
ClientSideCertEnable *bool `access:"experimental_features,cloud_restrictable"`
ClientSideCertCheck *string `access:"experimental_features,cloud_restrictable"`
LinkMetadataTimeoutMilliseconds *int64 `access:"experimental_features,write_restrictable,cloud_restrictable"`
RestrictSystemAdmin *bool `access:"experimental_features,write_restrictable"`
EnableSharedChannels *bool `access:"experimental_features"`
EnableRemoteClusterService *bool `access:"experimental_features"`
DisableAppBar *bool `access:"experimental_features"`
DisableRefetchingOnBrowserFocus *bool `access:"experimental_features"`
DelayChannelAutocomplete *bool `access:"experimental_features"`
DisableWakeUpReconnectHandler *bool `access:"experimental_features"`
UsersStatusAndProfileFetchingPollIntervalMilliseconds *int64 `access:"experimental_features"`
}
func (s *ExperimentalSettings) SetDefaults() {
@@ -1062,6 +1073,10 @@ func (s *ExperimentalSettings) SetDefaults() {
if s.DisableWakeUpReconnectHandler == nil {
s.DisableWakeUpReconnectHandler = NewBool(false)
}
if s.UsersStatusAndProfileFetchingPollIntervalMilliseconds == nil {
s.UsersStatusAndProfileFetchingPollIntervalMilliseconds = NewInt64(ExperimentalSettingsDefaultUsersStatusAndProfileFetchingPollIntervalMilliseconds)
}
}
type AnalyticsSettings struct {
@@ -2342,9 +2357,6 @@ type LdapSettings struct {
LoginButtonColor *string `access:"experimental_features"`
LoginButtonBorderColor *string `access:"experimental_features"`
LoginButtonTextColor *string `access:"experimental_features"`
// Deprecated: Use LogSettings.AdvancedLoggingJSON with the LDAPTrace level instead.
Trace *bool `access:"authentication_ldap"` // telemetry: none
}
func (s *LdapSettings) SetDefaults() {
@@ -2486,10 +2498,6 @@ func (s *LdapSettings) SetDefaults() {
if s.LoginButtonTextColor == nil {
s.LoginButtonTextColor = NewString("#2389D7")
}
if s.Trace == nil {
s.Trace = NewBool(false)
}
}
type ComplianceSettings struct {
@@ -2751,6 +2759,7 @@ func (s *NativeAppSettings) SetDefaults() {
type ElasticsearchSettings struct {
ConnectionURL *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
Backend *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
Username *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
Password *string `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
EnableIndexing *bool `access:"environment_elasticsearch,write_restrictable,cloud_restrictable"`
@@ -2783,6 +2792,10 @@ func (s *ElasticsearchSettings) SetDefaults() {
s.ConnectionURL = NewString(ElasticsearchSettingsDefaultConnectionURL)
}
if s.Backend == nil {
s.Backend = NewString(ElasticsearchSettingsESBackend)
}
if s.Username == nil {
s.Username = NewString(ElasticsearchSettingsDefaultUsername)
}
@@ -3060,12 +3073,6 @@ func (s *CloudSettings) SetDefaults() {
}
}
type ProductSettings struct {
}
func (s *ProductSettings) SetDefaults() {
}
type PluginState struct {
Enable bool
}
@@ -3498,7 +3505,6 @@ type Config struct {
DataRetentionSettings DataRetentionSettings
MessageExportSettings MessageExportSettings
JobSettings JobSettings
ProductSettings ProductSettings // Deprecated: Remove in next major version:: https://mattermost.atlassian.net/browse/MM-56655
PluginSettings PluginSettings
DisplaySettings DisplaySettings
GuestAccountsSettings GuestAccountsSettings
@@ -3599,7 +3605,6 @@ func (o *Config) SetDefaults() {
o.ThemeSettings.SetDefaults()
o.ClusterSettings.SetDefaults()
o.PluginSettings.SetDefaults(o.LogSettings)
o.ProductSettings.SetDefaults()
o.AnalyticsSettings.SetDefaults()
o.ComplianceSettings.SetDefaults()
o.LocalizationSettings.SetDefaults()
@@ -4032,6 +4037,10 @@ func (s *ServiceSettings) isValid() *AppError {
return NewAppError("Config.IsValid", "model.config.is_valid.max_payload_size.app_error", nil, "", http.StatusBadRequest)
}
if *s.MaximumURLLength <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.max_url_length.app_error", nil, "", http.StatusBadRequest)
}
if *s.ReadTimeout <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "", http.StatusBadRequest)
}
@@ -4161,6 +4170,10 @@ func (s *ElasticsearchSettings) isValid() *AppError {
}
}
if *s.Backend != ElasticsearchSettingsOSBackend && *s.Backend != ElasticsearchSettingsESBackend {
return NewAppError("Config.IsValid", "model.config.is_valid.elastic_search.invalid_backend.app_error", nil, "", http.StatusBadRequest)
}
return nil
}

View File

@@ -57,8 +57,6 @@ type FeatureFlags struct {
NotificationMonitoring bool
ExperimentalAuditSettingsSystemConsoleUI bool
ClientMetrics bool
}
func (f *FeatureFlags) SetDefaults() {
@@ -80,10 +78,9 @@ func (f *FeatureFlags) SetDefaults() {
f.CloudAnnualRenewals = false
f.CloudDedicatedExportUI = false
f.ChannelBookmarks = false
f.WebSocketEventScope = false
f.WebSocketEventScope = true
f.NotificationMonitoring = true
f.ExperimentalAuditSettingsSystemConsoleUI = false
f.ClientMetrics = false
}
// ToMap returns the feature flags as a map[string]string

View File

@@ -52,18 +52,30 @@ func (group *Group) Auditable() map[string]interface{} {
return map[string]interface{}{
"id": group.Id,
"source": group.Source,
"remote_id": group.RemoteId,
"remote_id": group.GetRemoteId(),
"create_at": group.CreateAt,
"update_at": group.UpdateAt,
"delete_at": group.DeleteAt,
"has_syncables": group.HasSyncables,
"member_count": group.MemberCount,
"member_count": group.GetMemberCount(),
"allow_reference": group.AllowReference,
}
}
func (group *Group) LogClone() any {
return group.Auditable()
return map[string]interface{}{
"id": group.Id,
"name": group.GetName(),
"display_name": group.DisplayName,
"source": group.Source,
"remote_id": group.GetRemoteId(),
"create_at": group.CreateAt,
"update_at": group.UpdateAt,
"delete_at": group.DeleteAt,
"has_syncables": group.HasSyncables,
"member_count": group.GetMemberCount(),
"allow_reference": group.AllowReference,
}
}
type GroupWithUserIds struct {
@@ -75,12 +87,12 @@ func (group *GroupWithUserIds) Auditable() map[string]interface{} {
return map[string]interface{}{
"id": group.Id,
"source": group.Source,
"remote_id": group.RemoteId,
"remote_id": group.GetRemoteId(),
"create_at": group.CreateAt,
"update_at": group.UpdateAt,
"delete_at": group.DeleteAt,
"has_syncables": group.HasSyncables,
"member_count": group.MemberCount,
"member_count": group.GetMemberCount(),
"allow_reference": group.AllowReference,
"user_ids": group.UserIds,
}
@@ -268,17 +280,15 @@ func (group *Group) IsValidName() *AppError {
}
func (group *Group) GetName() string {
if group.Name == nil {
return ""
}
return *group.Name
return SafeDereference(group.Name)
}
func (group *Group) GetRemoteId() string {
if group.RemoteId == nil {
return ""
}
return *group.RemoteId
return SafeDereference(group.RemoteId)
}
func (group *Group) GetMemberCount() int {
return SafeDereference(group.MemberCount)
}
type GroupsWithCount struct {

View File

@@ -8,37 +8,38 @@ import (
)
const (
JobTypeDataRetention = "data_retention"
JobTypeMessageExport = "message_export"
JobTypeElasticsearchPostIndexing = "elasticsearch_post_indexing"
JobTypeElasticsearchPostAggregation = "elasticsearch_post_aggregation"
JobTypeBlevePostIndexing = "bleve_post_indexing"
JobTypeLdapSync = "ldap_sync"
JobTypeMigrations = "migrations"
JobTypePlugins = "plugins"
JobTypeExpiryNotify = "expiry_notify"
JobTypeProductNotices = "product_notices"
JobTypeActiveUsers = "active_users"
JobTypeImportProcess = "import_process"
JobTypeImportDelete = "import_delete"
JobTypeExportProcess = "export_process"
JobTypeExportDelete = "export_delete"
JobTypeCloud = "cloud"
JobTypeResendInvitationEmail = "resend_invitation_email"
JobTypeExtractContent = "extract_content"
JobTypeLastAccessiblePost = "last_accessible_post"
JobTypeLastAccessibleFile = "last_accessible_file"
JobTypeUpgradeNotifyAdmin = "upgrade_notify_admin"
JobTypeTrialNotifyAdmin = "trial_notify_admin"
JobTypePostPersistentNotifications = "post_persistent_notifications"
JobTypeInstallPluginNotifyAdmin = "install_plugin_notify_admin"
JobTypeHostedPurchaseScreening = "hosted_purchase_screening"
JobTypeS3PathMigration = "s3_path_migration"
JobTypeCleanupDesktopTokens = "cleanup_desktop_tokens"
JobTypeDeleteEmptyDraftsMigration = "delete_empty_drafts_migration"
JobTypeRefreshPostStats = "refresh_post_stats"
JobTypeDeleteOrphanDraftsMigration = "delete_orphan_drafts_migration"
JobTypeExportUsersToCSV = "export_users_to_csv"
JobTypeDataRetention = "data_retention"
JobTypeMessageExport = "message_export"
JobTypeElasticsearchPostIndexing = "elasticsearch_post_indexing"
JobTypeElasticsearchPostAggregation = "elasticsearch_post_aggregation"
JobTypeBlevePostIndexing = "bleve_post_indexing"
JobTypeLdapSync = "ldap_sync"
JobTypeMigrations = "migrations"
JobTypePlugins = "plugins"
JobTypeExpiryNotify = "expiry_notify"
JobTypeProductNotices = "product_notices"
JobTypeActiveUsers = "active_users"
JobTypeImportProcess = "import_process"
JobTypeImportDelete = "import_delete"
JobTypeExportProcess = "export_process"
JobTypeExportDelete = "export_delete"
JobTypeCloud = "cloud"
JobTypeResendInvitationEmail = "resend_invitation_email"
JobTypeExtractContent = "extract_content"
JobTypeLastAccessiblePost = "last_accessible_post"
JobTypeLastAccessibleFile = "last_accessible_file"
JobTypeUpgradeNotifyAdmin = "upgrade_notify_admin"
JobTypeTrialNotifyAdmin = "trial_notify_admin"
JobTypePostPersistentNotifications = "post_persistent_notifications"
JobTypeInstallPluginNotifyAdmin = "install_plugin_notify_admin"
JobTypeHostedPurchaseScreening = "hosted_purchase_screening"
JobTypeS3PathMigration = "s3_path_migration"
JobTypeCleanupDesktopTokens = "cleanup_desktop_tokens"
JobTypeDeleteEmptyDraftsMigration = "delete_empty_drafts_migration"
JobTypeRefreshPostStats = "refresh_post_stats"
JobTypeDeleteOrphanDraftsMigration = "delete_orphan_drafts_migration"
JobTypeExportUsersToCSV = "export_users_to_csv"
JobTypeDeleteDmsPreferencesMigration = "delete_dms_preferences_migration"
JobStatusPending = "pending"
JobStatusInProgress = "in_progress"
@@ -108,7 +109,31 @@ func (j *Job) IsValid() *AppError {
return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Status {
validStatus := IsValidJobStatus(j.Status)
if !validStatus {
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
return nil
}
func (j *Job) IsValidStatusChange(newStatus string) bool {
currentStatus := j.Status
switch currentStatus {
case JobStatusInProgress:
return newStatus == JobStatusPending || newStatus == JobStatusCancelRequested
case JobStatusPending:
return newStatus == JobStatusCancelRequested
case JobStatusCancelRequested:
return newStatus == JobStatusCanceled
}
return false
}
func IsValidJobStatus(status string) bool {
switch status {
case JobStatusPending,
JobStatusInProgress,
JobStatusSuccess,
@@ -117,10 +142,20 @@ func (j *Job) IsValid() *AppError {
JobStatusCancelRequested,
JobStatusCanceled:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
return false
}
return nil
return true
}
func IsValidJobType(jobType string) bool {
for _, t := range AllJobTypes {
if t == jobType {
return true
}
}
return false
}
func (j *Job) LogClone() any {

View File

@@ -91,6 +91,29 @@ type PluginSetting struct {
Hosting string `json:"hosting"`
}
type PluginSettingsSection struct {
// A unique identifier for this section.
Key string `json:"key" yaml:"key"`
// Optional text to display as section title.
Title string `json:"title" yaml:"title"`
// Optional text to display as section subtitle.
Subtitle string `json:"subtitle" yaml:"subtitle"`
// A list of setting definitions to display inside the section.
Settings []*PluginSetting `json:"settings" yaml:"settings"`
// Optional text to display above the settings. Supports Markdown formatting.
Header string `json:"header" yaml:"header"`
// Optional text to display below the settings. Supports Markdown formatting.
Footer string `json:"footer" yaml:"footer"`
// If true, the section will load the custom component registered using `registry.registerAdminConsoleCustomSection`
Custom bool `json:"custom" yaml:"custom"`
}
type PluginSettingsSchema struct {
// Optional text to display above the settings. Supports Markdown formatting.
Header string `json:"header" yaml:"header"`
@@ -100,6 +123,9 @@ type PluginSettingsSchema struct {
// A list of setting definitions.
Settings []*PluginSetting `json:"settings" yaml:"settings"`
// A list of settings section definitions.
Sections []*PluginSettingsSection `json:"sections" yaml:"sections"`
}
// The plugin manifest defines the metadata required to load and present your plugin. The manifest
@@ -330,6 +356,27 @@ func (s *PluginSettingsSchema) isValid() error {
}
}
for _, section := range s.Sections {
if err := section.IsValid(); err != nil {
return err
}
}
return nil
}
func (s *PluginSettingsSection) IsValid() error {
if s.Key == "" {
return errors.New("invalid empty Key")
}
for _, setting := range s.Settings {
err := setting.isValid()
if err != nil {
return err
}
}
return nil
}

View File

@@ -14,15 +14,21 @@ import (
type MetricType string
const (
ClientTimeToFirstByte MetricType = "TTFB"
ClientFirstContentfulPaint MetricType = "FCP"
ClientLargestContentfulPaint MetricType = "LCP"
ClientInteractionToNextPaint MetricType = "INP"
ClientCumulativeLayoutShift MetricType = "CLS"
ClientLongTasks MetricType = "long_tasks"
ClientChannelSwitchDuration MetricType = "channel_switch"
ClientTeamSwitchDuration MetricType = "team_switch"
ClientRHSLoadDuration MetricType = "rhs_load"
ClientTimeToFirstByte MetricType = "TTFB"
ClientFirstContentfulPaint MetricType = "FCP"
ClientLargestContentfulPaint MetricType = "LCP"
ClientInteractionToNextPaint MetricType = "INP"
ClientCumulativeLayoutShift MetricType = "CLS"
ClientLongTasks MetricType = "long_tasks"
ClientPageLoadDuration MetricType = "page_load"
ClientChannelSwitchDuration MetricType = "channel_switch"
ClientTeamSwitchDuration MetricType = "team_switch"
ClientRHSLoadDuration MetricType = "rhs_load"
ClientGlobalThreadsLoadDuration MetricType = "global_threads_load"
MobileClientLoadDuration MetricType = "mobile_load"
MobileClientChannelSwitchDuration MetricType = "mobile_channel_switch"
MobileClientTeamSwitchDuration MetricType = "mobile_team_switch"
performanceReportTTLMilliseconds = 300 * 1000 // 300 seconds/5 minutes
)
@@ -31,6 +37,20 @@ var (
performanceReportVersion = semver.MustParse("0.1.0")
acceptedPlatforms = sliceToMapKey("linux", "macos", "ios", "android", "windows", "other")
acceptedAgents = sliceToMapKey("desktop", "firefox", "chrome", "safari", "edge", "other")
AcceptedInteractions = sliceToMapKey("keyboard", "pointer", "other")
AcceptedLCPRegions = sliceToMapKey(
"post",
"post_textbox",
"channel_sidebar",
"team_sidebar",
"channel_header",
"global_header",
"announcement_bar",
"center_channel",
"modal_content",
"other",
)
)
type MetricSample struct {
@@ -40,6 +60,10 @@ type MetricSample struct {
Labels map[string]string `json:"labels,omitempty"`
}
func (s *MetricSample) GetLabelValue(name string, acceptedValues map[string]any, defaultValue string) string {
return processLabel(s.Labels, name, acceptedValues, defaultValue)
}
// PerformanceReport is a set of samples collected from a client
type PerformanceReport struct {
Version string `json:"version"`
@@ -78,37 +102,25 @@ func (r *PerformanceReport) IsValid() error {
}
func (r *PerformanceReport) ProcessLabels() map[string]string {
var platform, agent string
var ok bool
// check if the platform is specified
platform, ok = r.Labels["platform"]
if !ok {
platform = "other"
}
platform = strings.ToLower(platform)
// check if platform is one of the accepted platforms
_, ok = acceptedPlatforms[platform]
if !ok {
platform = "other"
}
// check if the agent is specified
agent, ok = r.Labels["agent"]
if !ok {
agent = "other"
}
agent = strings.ToLower(agent)
// check if agent is one of the accepted agents
_, ok = acceptedAgents[agent]
if !ok {
agent = "other"
}
return map[string]string{
"platform": platform,
"agent": agent,
"platform": processLabel(r.Labels, "platform", acceptedPlatforms, "other"),
"agent": processLabel(r.Labels, "agent", acceptedAgents, "other"),
}
}
func processLabel(labels map[string]string, name string, acceptedValues map[string]any, defaultValue string) string {
// check if the label is specified
value, ok := labels[name]
if !ok {
return defaultValue
}
value = strings.ToLower(value)
// check if the value is one that we accept
_, ok = acceptedValues[value]
if !ok {
return defaultValue
}
return value
}

View File

@@ -47,4 +47,6 @@ const (
MigrationKeyAddIPFilteringPermissions = "add_ip_filtering_permissions"
MigrationKeyAddOutgoingOAuthConnectionsPermissions = "add_outgoing_oauth_connections_permissions"
MigrationKeyAddChannelBookmarksPermissions = "add_channel_bookmarks_permissions"
MigrationKeyDeleteDmsPreferences = "delete_dms_preferences_migration"
MigrationKeyAddManageJobAncillaryPermissions = "add_manage_jobs_ancillary_permissions"
)

View File

@@ -18,6 +18,8 @@ const (
NotificationTypeWebsocket NotificationType = "websocket"
NotificationTypePush NotificationType = "push"
NotificationNoPlatform = "no_platform"
NotificationReasonFetchError NotificationReason = "fetch_error"
NotificationReasonParseError NotificationReason = "json_parse_error"
NotificationReasonPushProxyError NotificationReason = "push_proxy_error"

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"fmt"
"github.com/blang/semver/v4"
"gopkg.in/yaml.v2"
)
type PacketType string
const (
CurrentMetadataVersion int = 1
SupportPacketType PacketType = "support-packet"
PluginPacketType PacketType = "plugin-packet"
PacketMetadataFileName = "metadata.yaml"
)
// PacketMetadata contains information about the server and the configured license (if there is one),
// It's used in file archives, so called Packets, that customer send to Mattermost Staff for review.
// For example, this metadata is attached to the Support Packet and the Metrics plugin Packet.
type PacketMetadata struct {
// Required Fields
Version int `yaml:"version"`
Type PacketType `yaml:"type"`
GeneratedAt int64 `yaml:"generated_at"`
ServerVersion string `yaml:"server_version"`
ServerID string `yaml:"server_id"`
// Optional Fields
LicenseID string `yaml:"license_id"`
CustomerID string `yaml:"customer_id"`
Extras map[string]any `yaml:"extras,omitempty"`
}
func (md *PacketMetadata) Validate() error {
if md.Version < 1 {
return fmt.Errorf("metadata version should be greater than 1")
}
switch md.Type {
case SupportPacketType, PluginPacketType:
default:
return fmt.Errorf("unrecognized packet type: %s", md.Type)
}
if md.GeneratedAt <= 0 {
return fmt.Errorf("generated_at should be a positive number")
}
if _, err := semver.ParseTolerant(md.ServerVersion); err != nil {
return fmt.Errorf("could not parse server version: %w", err)
}
if !IsValidId(md.ServerID) {
return fmt.Errorf("server id is not a valid id %q", md.ServerID)
}
if md.LicenseID != "" && !IsValidId(md.LicenseID) {
return fmt.Errorf("license id is not a valid id %q", md.LicenseID)
}
if md.CustomerID != "" && !IsValidId(md.CustomerID) {
return fmt.Errorf("customer id is not a valid id %q", md.CustomerID)
}
return nil
}
func ParsePacketMetadata(b []byte) (*PacketMetadata, error) {
v := struct {
Version int `yaml:"version"`
}{}
err := yaml.Unmarshal(b, &v)
if err != nil {
return nil, err
}
switch v.Version {
case 1:
var md PacketMetadata
err = yaml.Unmarshal(b, &md)
if err != nil {
return nil, err
}
err = md.Validate()
if err != nil {
return nil, err
}
return &md, nil
default:
return nil, fmt.Errorf("unsupported metadata version: %d", v.Version)
}
}
// GeneratePacketMetadata is a utility function to generate metadata for customer provided Packets.
// It will construct it from a Packet Type, the telemetryID and optionally a license.
func GeneratePacketMetadata(t PacketType, telemetryID string, license *License, extra map[string]any) (*PacketMetadata, error) {
if extra == nil {
extra = make(map[string]any)
}
md := &PacketMetadata{
Version: CurrentMetadataVersion,
Type: t,
GeneratedAt: GetMillis(),
ServerVersion: CurrentVersion,
ServerID: telemetryID,
Extras: extra,
}
if license != nil {
md.LicenseID = license.Id
md.CustomerID = license.Customer.Id
}
if err := md.Validate(); err != nil {
return nil, fmt.Errorf("invalid metadata: %w", err)
}
return md, nil
}

View File

@@ -123,8 +123,10 @@ var PermissionManageSharedChannels *Permission
var PermissionManageSecureConnections *Permission
var PermissionDownloadComplianceExportResult *Permission
var PermissionCreateDataRetentionJob *Permission
var PermissionManageDataRetentionJob *Permission
var PermissionReadDataRetentionJob *Permission
var PermissionCreateComplianceExportJob *Permission
var PermissionManageComplianceExportJob *Permission
var PermissionReadComplianceExportJob *Permission
var PermissionReadAudits *Permission
var PermissionTestElasticsearch *Permission
@@ -136,12 +138,16 @@ var PermissionRecycleDatabaseConnections *Permission
var PermissionPurgeElasticsearchIndexes *Permission
var PermissionTestEmail *Permission
var PermissionCreateElasticsearchPostIndexingJob *Permission
var PermissionManageElasticsearchPostIndexingJob *Permission
var PermissionCreateElasticsearchPostAggregationJob *Permission
var PermissionManageElasticsearchPostAggregationJob *Permission
var PermissionReadElasticsearchPostIndexingJob *Permission
var PermissionReadElasticsearchPostAggregationJob *Permission
var PermissionPurgeBleveIndexes *Permission
var PermissionCreatePostBleveIndexesJob *Permission
var PermissionManagePostBleveIndexesJob *Permission
var PermissionCreateLdapSyncJob *Permission
var PermissionManageLdapSyncJob *Permission
var PermissionReadLdapSyncJob *Permission
var PermissionTestLdap *Permission
var PermissionInvalidateEmailInvite *Permission
@@ -790,6 +796,12 @@ func initializePermissions() {
"",
PermissionScopeSystem,
}
PermissionManageDataRetentionJob = &Permission{
"manage_data_retention_job",
"",
"",
PermissionScopeSystem,
}
PermissionReadDataRetentionJob = &Permission{
"read_data_retention_job",
"",
@@ -803,6 +815,12 @@ func initializePermissions() {
"",
PermissionScopeSystem,
}
PermissionManageComplianceExportJob = &Permission{
"manage_compliance_export_job",
"",
"",
PermissionScopeSystem,
}
PermissionReadComplianceExportJob = &Permission{
"read_compliance_export_job",
"",
@@ -831,12 +849,25 @@ func initializePermissions() {
PermissionScopeSystem,
}
PermissionManagePostBleveIndexesJob = &Permission{
"manage_post_bleve_indexes_job",
"",
"",
PermissionScopeSystem,
}
PermissionCreateLdapSyncJob = &Permission{
"create_ldap_sync_job",
"",
"",
PermissionScopeSystem,
}
PermissionManageLdapSyncJob = &Permission{
"manage_ldap_sync_job",
"",
"",
PermissionScopeSystem,
}
PermissionReadLdapSyncJob = &Permission{
"read_ldap_sync_job",
"",
@@ -1029,12 +1060,24 @@ func initializePermissions() {
"",
PermissionScopeSystem,
}
PermissionManageElasticsearchPostIndexingJob = &Permission{
"manage_elasticsearch_post_indexing_job",
"",
"",
PermissionScopeSystem,
}
PermissionCreateElasticsearchPostAggregationJob = &Permission{
"create_elasticsearch_post_aggregation_job",
"",
"",
PermissionScopeSystem,
}
PermissionManageElasticsearchPostAggregationJob = &Permission{
"manage_elasticsearch_post_aggregation_job",
"",
"",
PermissionScopeSystem,
}
PermissionReadElasticsearchPostIndexingJob = &Permission{
"read_elasticsearch_post_indexing_job",
"",
@@ -2347,8 +2390,10 @@ func initializePermissions() {
PermissionManageSecureConnections,
PermissionDownloadComplianceExportResult,
PermissionCreateDataRetentionJob,
PermissionManageDataRetentionJob,
PermissionReadDataRetentionJob,
PermissionCreateComplianceExportJob,
PermissionManageComplianceExportJob,
PermissionReadComplianceExportJob,
PermissionReadAudits,
PermissionTestSiteURL,
@@ -2360,12 +2405,16 @@ func initializePermissions() {
PermissionPurgeElasticsearchIndexes,
PermissionTestEmail,
PermissionCreateElasticsearchPostIndexingJob,
PermissionManageElasticsearchPostIndexingJob,
PermissionCreateElasticsearchPostAggregationJob,
PermissionManageElasticsearchPostAggregationJob,
PermissionReadElasticsearchPostIndexingJob,
PermissionReadElasticsearchPostAggregationJob,
PermissionPurgeBleveIndexes,
PermissionCreatePostBleveIndexesJob,
PermissionManagePostBleveIndexesJob,
PermissionCreateLdapSyncJob,
PermissionManageLdapSyncJob,
PermissionReadLdapSyncJob,
PermissionTestLdap,
PermissionInvalidateEmailInvite,

View File

@@ -488,6 +488,12 @@ func (o *Post) SanitizeProps() {
}
}
// Remove any input data from the post object that is not user controlled
func (o *Post) SanitizeInput() {
o.DeleteAt = 0
o.RemoteId = NewString("")
}
func (o *Post) ContainsIntegrationsReservedProps() []string {
return containsIntegrationsReservedProps(o.GetProps())
}

View File

@@ -7,6 +7,7 @@ import (
"encoding/json"
"net/http"
"regexp"
"strconv"
"strings"
"unicode/utf8"
)
@@ -60,6 +61,10 @@ const (
PreferenceEmailIntervalHour = "hour"
PreferenceEmailIntervalHourAsSeconds = "3600"
PreferenceCloudUserEphemeralInfo = "cloud_user_ephemeral_info"
PreferenceLimitVisibleDmsGms = "limit_visible_dms_gms"
PreferenceMaxLimitVisibleDmsGmsValue = 40
MaxPreferenceValueLength = 20000
)
type Preference struct {
@@ -84,7 +89,7 @@ func (o *Preference) IsValid() *AppError {
return NewAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name, http.StatusBadRequest)
}
if utf8.RuneCountInString(o.Value) > 2000 {
if utf8.RuneCountInString(o.Value) > MaxPreferenceValueLength {
return NewAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value, http.StatusBadRequest)
}
@@ -95,6 +100,13 @@ func (o *Preference) IsValid() *AppError {
}
}
if o.Category == PreferenceCategorySidebarSettings && o.Name == PreferenceLimitVisibleDmsGms {
visibleDmsGmsValue, convErr := strconv.Atoi(o.Value)
if convErr != nil || visibleDmsGmsValue < 1 || visibleDmsGmsValue > PreferenceMaxLimitVisibleDmsGmsValue {
return NewAppError("Preference.IsValid", "model.preference.is_valid.limit_visible_dms_gms.app_error", nil, "value="+o.Value, http.StatusBadRequest)
}
}
return nil
}

View File

@@ -64,3 +64,10 @@ func (o *Reaction) PreUpdate() {
o.RemoteId = NewString("")
}
}
func (o *Reaction) GetRemoteID() string {
if o.RemoteId == nil {
return ""
}
return *o.RemoteId
}

View File

@@ -51,7 +51,7 @@ func (bm *Bitmask) UnsetBit(flag Bitmask) {
type RemoteCluster struct {
RemoteId string `json:"remote_id"`
RemoteTeamId string `json:"remote_team_id"`
RemoteTeamId string `json:"remote_team_id"` // Deprecated: this field is no longer used. It's only kept for backwards compatibility.
Name string `json:"name"`
DisplayName string `json:"display_name"`
SiteURL string `json:"site_url"`
@@ -126,6 +126,37 @@ func (rc *RemoteCluster) IsValid() *AppError {
return nil
}
func (rc *RemoteCluster) Sanitize() {
rc.Token = ""
rc.RemoteToken = ""
}
type RemoteClusterPatch struct {
DisplayName *string `json:"display_name"`
}
func (rcp *RemoteClusterPatch) Auditable() map[string]interface{} {
return map[string]interface{}{
"display_name": rcp.DisplayName,
}
}
func (rc *RemoteCluster) Patch(patch *RemoteClusterPatch) {
if patch.DisplayName != nil {
rc.DisplayName = *patch.DisplayName
}
}
type RemoteClusterWithPassword struct {
*RemoteCluster
Password string `json:"password"`
}
type RemoteClusterWithInvite struct {
RemoteCluster *RemoteCluster `json:"remote_cluster"`
Invite string `json:"invite"`
}
func newIDFromBytes(b []byte) string {
hash := md5.New()
_, _ = hash.Write(b)
@@ -251,7 +282,8 @@ type RemoteClusterFrame struct {
func (f *RemoteClusterFrame) Auditable() map[string]interface{} {
return map[string]interface{}{
"remote_id": f.RemoteId,
"msg": f.Msg,
"msg_id": f.Msg.Id,
"topic": f.Msg.Topic,
}
}
@@ -311,7 +343,7 @@ type RemoteClusterPing struct {
// RemoteClusterInvite represents an invitation to establish a simple trust with a remote cluster.
type RemoteClusterInvite struct {
RemoteId string `json:"remote_id"`
RemoteTeamId string `json:"remote_team_id"`
RemoteTeamId string `json:"remote_team_id"` // Deprecated: this field is no longer used. It's only kept for backwards compatibility.
SiteURL string `json:"site_url"`
Token string `json:"token"`
}
@@ -392,6 +424,13 @@ func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error
return json.Unmarshal(plain, &rci)
}
type RemoteClusterAcceptInvite struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Invite string `json:"invite"`
Password string `json:"password"`
}
// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
type RemoteClusterQueryFilter struct {
ExcludeOffline bool
@@ -402,5 +441,6 @@ type RemoteClusterQueryFilter struct {
OnlyConfirmed bool
PluginID string
OnlyPlugins bool
ExcludePlugins bool
RequireOptions Bitmask
}

View File

@@ -144,7 +144,7 @@ func (u *UserReportOptions) IsValid() *AppError {
}
func (u *UserReportQuery) ToReport() *UserReport {
u.ClearNonProfileFields()
u.ClearNonProfileFields(false)
return &UserReport{
User: u.User,
UserPostStats: u.UserPostStats,

View File

@@ -90,7 +90,9 @@ func init() {
PermissionSysconsoleWriteEnvironmentElasticsearch.Id: {
PermissionTestElasticsearch,
PermissionCreateElasticsearchPostIndexingJob,
PermissionManageElasticsearchPostIndexingJob,
PermissionCreateElasticsearchPostAggregationJob,
PermissionManageElasticsearchPostAggregationJob,
PermissionPurgeElasticsearchIndexes,
},
PermissionSysconsoleWriteEnvironmentFileStorage.Id: {
@@ -114,7 +116,6 @@ func init() {
PermissionPromoteGuest,
},
PermissionSysconsoleWriteUserManagementChannels.Id: {
PermissionManageTeam,
PermissionManagePublicChannelProperties,
PermissionManagePrivateChannelProperties,
PermissionManagePrivateChannelMembers,
@@ -134,7 +135,6 @@ func init() {
PermissionAddUserToTeam,
},
PermissionSysconsoleWriteUserManagementGroups.Id: {
PermissionManageTeam,
PermissionManagePrivateChannelMembers,
PermissionManagePublicChannelMembers,
PermissionConvertPublicChannelToPrivate,
@@ -145,12 +145,14 @@ func init() {
},
PermissionSysconsoleWriteComplianceDataRetentionPolicy.Id: {
PermissionCreateDataRetentionJob,
PermissionManageDataRetentionJob,
},
PermissionSysconsoleReadComplianceDataRetentionPolicy.Id: {
PermissionReadDataRetentionJob,
},
PermissionSysconsoleWriteComplianceComplianceExport.Id: {
PermissionCreateComplianceExportJob,
PermissionManageComplianceExportJob,
PermissionDownloadComplianceExportResult,
},
PermissionSysconsoleReadComplianceComplianceExport.Id: {
@@ -163,9 +165,11 @@ func init() {
PermissionSysconsoleWriteExperimentalBleve.Id: {
PermissionCreatePostBleveIndexesJob,
PermissionPurgeBleveIndexes,
PermissionManagePostBleveIndexesJob,
},
PermissionSysconsoleWriteAuthenticationLdap.Id: {
PermissionCreateLdapSyncJob,
PermissionManageLdapSyncJob,
PermissionAddLdapPublicCert,
PermissionRemoveLdapPublicCert,
PermissionAddLdapPrivateCert,

View File

@@ -11,9 +11,15 @@ import (
"github.com/pkg/errors"
)
const (
UserPropsKeyRemoteUsername = "RemoteUsername"
UserPropsKeyRemoteEmail = "RemoteEmail"
)
var (
ErrChannelAlreadyShared = errors.New("channel is already shared")
ErrChannelHomedOnRemote = errors.New("channel is homed on a remote cluster")
ErrChannelAlreadyExists = errors.New("channel already exists")
)
// SharedChannel represents a channel that can be synchronized with a remote cluster.

View File

@@ -14,6 +14,8 @@ import (
"time"
"unicode/utf8"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"golang.org/x/text/language"
@@ -130,7 +132,7 @@ func (u *User) Auditable() map[string]interface{} {
"locale": u.Locale,
"timezone": u.Timezone,
"mfa_active": u.MfaActive,
"remote_id": u.RemoteId,
"remote_id": u.GetRemoteID(),
"last_activity_at": u.LastActivityAt,
"is_bot": u.IsBot,
"bot_description": u.BotDescription,
@@ -142,7 +144,26 @@ func (u *User) Auditable() map[string]interface{} {
}
func (u *User) LogClone() any {
return u.Auditable()
return map[string]interface{}{
"id": u.Id,
"create_at": u.CreateAt,
"update_at": u.UpdateAt,
"delete_at": u.DeleteAt,
"username": u.Username,
"auth_data": u.GetAuthData(),
"auth_service": u.AuthService,
"email": u.Email,
"email_verified": u.EmailVerified,
"position": u.Position,
"roles": u.Roles,
"allow_marketing": u.AllowMarketing,
"props": u.Props,
"notify_props": u.NotifyProps,
"locale": u.Locale,
"timezone": u.Timezone,
"mfa_active": u.MfaActive,
"remote_id": u.GetRemoteID(),
}
}
//msgp UserMap
@@ -346,7 +367,7 @@ func (u *User) IsValid() *AppError {
}
}
if len(u.Email) > UserEmailMaxLength || u.Email == "" || !IsValidEmail(u.Email) {
if len(u.Email) > UserEmailMaxLength || u.Email == "" || (!IsValidEmail(u.Email) && !u.IsRemote()) {
return InvalidUserError("email", u.Id, u.Email)
}
@@ -378,10 +399,6 @@ func (u *User) IsValid() *AppError {
return InvalidUserError("auth_data_pwd", u.Id, *u.AuthData)
}
if len(u.Password) > UserPasswordMaxLength {
return InvalidUserError("password_limit", u.Id, "")
}
if !IsValidLocale(u.Locale) {
return InvalidUserError("locale", u.Id, u.Locale)
}
@@ -430,7 +447,7 @@ func NormalizeEmail(email string) string {
// PreSave will set the Id and Username if missing. It will also fill
// in the CreateAt, UpdateAt times. It will also hash the password. It should
// be run before saving the user to the db.
func (u *User) PreSave() {
func (u *User) PreSave() *AppError {
if u.Id == "" {
u.Id = NewId()
}
@@ -477,7 +494,15 @@ func (u *User) PreSave() {
}
if u.Password != "" {
u.Password = HashPassword(u.Password)
hashed, err := HashPassword(u.Password)
if errors.Is(err, bcrypt.ErrPasswordTooLong) {
return NewAppError("User.PreSave", "model.user.pre_save.password_too_long.app_error",
nil, "user_id="+u.Id, http.StatusBadRequest).Wrap(err)
} else if err != nil {
return NewAppError("User.PreSave", "model.user.pre_save.password_hash.app_error",
nil, "user_id="+u.Id, http.StatusBadRequest).Wrap(err)
}
u.Password = hashed
}
cs := u.GetCustomStatus()
@@ -485,6 +510,8 @@ func (u *User) PreSave() {
cs.PreSave()
u.SetCustomStatus(cs)
}
return nil
}
// PreUpdate should be run before updating the user in the db.
@@ -637,6 +664,7 @@ func (u *User) Sanitize(options map[string]bool) {
if len(options) != 0 && !options["email"] {
u.Email = ""
delete(u.Props, UserPropsKeyRemoteEmail)
}
if len(options) != 0 && !options["fullname"] {
u.FirstName = ""
@@ -657,6 +685,9 @@ func (u *User) SanitizeInput(isAdmin bool) {
u.AuthService = ""
u.EmailVerified = false
}
u.RemoteId = NewString("")
u.CreateAt = 0
u.UpdateAt = 0
u.DeleteAt = 0
u.LastPasswordUpdate = 0
u.LastPictureUpdate = 0
@@ -664,21 +695,25 @@ func (u *User) SanitizeInput(isAdmin bool) {
u.MfaActive = false
u.MfaSecret = ""
u.Email = strings.TrimSpace(u.Email)
u.LastActivityAt = 0
}
func (u *User) ClearNonProfileFields() {
func (u *User) ClearNonProfileFields(asAdmin bool) {
u.Password = ""
u.AuthData = NewString("")
u.MfaSecret = ""
u.EmailVerified = false
u.AllowMarketing = false
u.NotifyProps = StringMap{}
u.LastPasswordUpdate = 0
u.FailedAttempts = 0
if !asAdmin {
u.NotifyProps = StringMap{}
}
}
func (u *User) SanitizeProfile(options map[string]bool) {
u.ClearNonProfileFields()
func (u *User) SanitizeProfile(options map[string]bool, asAdmin bool) {
u.ClearNonProfileFields(asAdmin)
u.Sanitize(options)
}
@@ -872,15 +907,16 @@ func (u *User) GetTimezoneLocation() *time.Location {
// IsRemote returns true if the user belongs to a remote cluster (has RemoteId).
func (u *User) IsRemote() bool {
return u.RemoteId != nil && *u.RemoteId != ""
return SafeDereference(u.RemoteId) != ""
}
// GetRemoteID returns the remote id for this user or "" if not a remote user.
func (u *User) GetRemoteID() string {
if u.RemoteId != nil {
return *u.RemoteId
}
return ""
return SafeDereference(u.RemoteId)
}
func (u *User) GetAuthData() string {
return SafeDereference(u.AuthData)
}
// GetProp fetches a prop value by name.
@@ -926,13 +962,13 @@ func (u *UserPatch) SetField(fieldName string, fieldValue string) {
}
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
func HashPassword(password string) string {
func HashPassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
panic(err)
return "", err
}
return string(hash)
return string(hash), nil
}
var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)

View File

@@ -13,6 +13,8 @@ import (
// It should be maintained in chronological order with most current
// release at the front of the list.
var versions = []string{
"9.11.0",
"9.10.0",
"9.9.0",
"9.8.0",
"9.7.0",

View File

@@ -9,6 +9,11 @@ import (
"github.com/vmihailenco/msgpack/v5"
)
const (
WebSocketRemoteAddr = "remote_addr"
WebSocketXForwardedFor = "x_forwarded_for"
)
// WebSocketRequest represents a request made to the server through a websocket.
type WebSocketRequest struct {
// Client-provided fields