Merge branch 'master' into mark-as-unread
Этот коммит содержится в:
13
model/bot.go
13
model/bot.go
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
@@ -217,3 +218,15 @@ func (l *BotList) Etag() string {
|
||||
func MakeBotNotFoundError(userId string) *AppError {
|
||||
return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func IsBotDMChannel(channel *Channel, botUserID string) bool {
|
||||
if channel.Type != CHANNEL_DIRECT {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(channel.Name, botUserID+"__") && !strings.HasSuffix(channel.Name, "__"+botUserID) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -683,3 +683,45 @@ func TestBotListEtag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBotChannel(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Channel *Channel
|
||||
Expected bool
|
||||
}{
|
||||
{
|
||||
Name: "not a direct channel",
|
||||
Channel: &Channel{Type: CHANNEL_OPEN},
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
Name: "a direct channel with another user",
|
||||
Channel: &Channel{
|
||||
Name: "user1__user2",
|
||||
Type: CHANNEL_DIRECT,
|
||||
},
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
Name: "a direct channel with the name containing the bot's ID first",
|
||||
Channel: &Channel{
|
||||
Name: "botUserID__user2",
|
||||
Type: CHANNEL_DIRECT,
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Name: "a direct channel with the name containing the bot's ID second",
|
||||
Channel: &Channel{
|
||||
Name: "user1__botUserID",
|
||||
Type: CHANNEL_DIRECT,
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
assert.Equal(t, test.Expected, IsBotDMChannel(test.Channel, "botUserID"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4472,6 +4472,21 @@ func (c *Client4) InstallPluginFromUrl(downloadUrl string, force bool) (*Manifes
|
||||
return ManifestFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// InstallMarketplacePlugin will install marketplace plugin.
|
||||
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
|
||||
func (c *Client4) InstallMarketplacePlugin(request *InstallMarketplacePluginRequest) (*Manifest, *Response) {
|
||||
json, err := request.ToJson()
|
||||
if err != nil {
|
||||
return nil, &Response{Error: NewAppError("InstallMarketplacePlugin", "model.client.plugin_request_to_json.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
r, appErr := c.DoApiPost(c.GetPluginsRoute()+"/marketplace", json)
|
||||
if appErr != nil {
|
||||
return nil, BuildErrorResponse(r, appErr)
|
||||
}
|
||||
defer closeBody(r)
|
||||
return ManifestFromJson(r.Body), BuildResponse(r)
|
||||
}
|
||||
|
||||
// GetPlugins will return a list of plugin manifests for currently active plugins.
|
||||
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
|
||||
func (c *Client4) GetPlugins() (*PluginsResponse, *Response) {
|
||||
|
||||
@@ -24,6 +24,9 @@ const (
|
||||
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
|
||||
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES = "inv_roles"
|
||||
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_SCHEMES = "inv_schemes"
|
||||
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_BY_ID = "inv_emojis_by_id"
|
||||
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_EMOJIS_ID_BY_NAME = "inv_emojis_id_by_name"
|
||||
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBER_COUNTS = "inv_channel_member_counts"
|
||||
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_ALL_USERS = "inv_all_user_sessions"
|
||||
CLUSTER_EVENT_INSTALL_PLUGIN = "install_plugin"
|
||||
CLUSTER_EVENT_REMOVE_PLUGIN = "remove_plugin"
|
||||
|
||||
@@ -48,6 +48,7 @@ const (
|
||||
GENERIC_NOTIFICATION = "generic"
|
||||
GENERIC_NOTIFICATION_SERVER = "https://push-test.mattermost.com"
|
||||
FULL_NOTIFICATION = "full"
|
||||
ID_LOADED_NOTIFICATION = "id_loaded"
|
||||
|
||||
DIRECT_MESSAGE_ANY = "any"
|
||||
DIRECT_MESSAGE_TEAM = "team"
|
||||
@@ -1521,6 +1522,7 @@ type TeamSettings struct {
|
||||
ExperimentalEnableAutomaticReplies *bool
|
||||
ExperimentalHideTownSquareinLHS *bool
|
||||
ExperimentalTownSquareIsReadOnly *bool
|
||||
LockTeammateNameDisplay *bool
|
||||
ExperimentalPrimaryTeam *string
|
||||
ExperimentalDefaultChannels []string
|
||||
}
|
||||
@@ -1667,6 +1669,10 @@ func (s *TeamSettings) SetDefaults() {
|
||||
if s.ExperimentalViewArchivedChannels == nil {
|
||||
s.ExperimentalViewArchivedChannels = NewBool(false)
|
||||
}
|
||||
|
||||
if s.LockTeammateNameDisplay == nil {
|
||||
s.LockTeammateNameDisplay = NewBool(false)
|
||||
}
|
||||
}
|
||||
|
||||
type ClientRequirements struct {
|
||||
@@ -2239,7 +2245,9 @@ type PluginSettings struct {
|
||||
Plugins map[string]map[string]interface{}
|
||||
PluginStates map[string]*PluginState
|
||||
EnableMarketplace *bool
|
||||
RequirePluginSignature *bool
|
||||
MarketplaceUrl *string
|
||||
SignaturePublicKeyFiles []string
|
||||
}
|
||||
|
||||
func (s *PluginSettings) SetDefaults(ls LogSettings) {
|
||||
@@ -2287,6 +2295,14 @@ func (s *PluginSettings) SetDefaults(ls LogSettings) {
|
||||
if s.MarketplaceUrl == nil || *s.MarketplaceUrl == "" || *s.MarketplaceUrl == PLUGIN_SETTINGS_OLD_MARKETPLACE_URL {
|
||||
s.MarketplaceUrl = NewString(PLUGIN_SETTINGS_DEFAULT_MARKETPLACE_URL)
|
||||
}
|
||||
|
||||
if s.RequirePluginSignature == nil {
|
||||
s.RequirePluginSignature = NewBool(false)
|
||||
}
|
||||
|
||||
if s.SignaturePublicKeyFiles == nil {
|
||||
s.SignaturePublicKeyFiles = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
type GlobalRelayMessageExportSettings struct {
|
||||
|
||||
@@ -170,7 +170,6 @@ func TestConfigIsValidFakeAlgorithm(t *testing.T) {
|
||||
require.Equal(t, "model.config.is_valid.saml_canonical_algorithm.app_error", err.Message)
|
||||
*c1.SamlSettings.CanonicalAlgorithm = temp
|
||||
|
||||
temp = *c1.SamlSettings.SignatureAlgorithm
|
||||
*c1.SamlSettings.SignatureAlgorithm = "Fake Algorithm"
|
||||
err = c1.SamlSettings.isValid()
|
||||
if err == nil {
|
||||
|
||||
@@ -44,6 +44,9 @@ type PostAction struct {
|
||||
// The text on the button, or in the select placeholder.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// If the action is disabled.
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
|
||||
// DataSource indicates the data source for the select action. If left
|
||||
// empty, the select is populated from Options. Other supported values
|
||||
// are "users" and "channels".
|
||||
|
||||
@@ -4,18 +4,25 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BaseMarketplacePlugin is a Mattermost plugin received from the marketplace server.
|
||||
type BaseMarketplacePlugin struct {
|
||||
HomepageURL string `json:"homepage_url"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
IconData string `json:"icon_data"`
|
||||
Manifest *Manifest `json:"manifest"`
|
||||
HomepageURL string `json:"homepage_url"`
|
||||
IconData string `json:"icon_data"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
ReleaseNotesURL string `json:"release_notes_url"`
|
||||
// Signature represents a signature of a plugin saved in base64 encoding.
|
||||
Signature string `json:"signature"`
|
||||
Manifest *Manifest `json:"manifest"`
|
||||
}
|
||||
|
||||
// MarketplacePlugin is a state aware marketplace plugin.
|
||||
@@ -48,6 +55,15 @@ func MarketplacePluginsFromReader(reader io.Reader) ([]*MarketplacePlugin, error
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
// DecodeSignature Decodes signature and returns ReadSeeker.
|
||||
func (plugin *BaseMarketplacePlugin) DecodeSignature() (io.ReadSeeker, error) {
|
||||
signatureBytes, err := base64.StdEncoding.DecodeString(plugin.Signature)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unable to decode base64 signature.")
|
||||
}
|
||||
return bytes.NewReader(signatureBytes), nil
|
||||
}
|
||||
|
||||
// MarketplacePluginFilter describes the parameters to request a list of plugins.
|
||||
type MarketplacePluginFilter struct {
|
||||
Page int
|
||||
@@ -67,3 +83,28 @@ func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
|
||||
q.Add("server_version", filter.ServerVersion)
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
// InstallMarketplacePluginRequest struct describes parameters of the requested plugin.
|
||||
type InstallMarketplacePluginRequest struct {
|
||||
Id string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// PluginRequestFromReader decodes a json-encoded plugin request from the given io.Reader.
|
||||
func PluginRequestFromReader(reader io.Reader) (*InstallMarketplacePluginRequest, error) {
|
||||
var r *InstallMarketplacePluginRequest
|
||||
err := json.NewDecoder(reader).Decode(&r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ToJson method will return json from plugin request.
|
||||
func (r *InstallMarketplacePluginRequest) ToJson() (string, error) {
|
||||
b, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn"
|
||||
PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn"
|
||||
|
||||
PUSH_TYPE_ID_LOADED = "id_loaded"
|
||||
PUSH_TYPE_MESSAGE = "message"
|
||||
PUSH_TYPE_CLEAR = "clear"
|
||||
PUSH_TYPE_UPDATE_BADGE = "update_badge"
|
||||
@@ -39,6 +40,7 @@ type PushNotificationAck struct {
|
||||
ClientReceivedAt int64 `json:"received_at"`
|
||||
ClientPlatform string `json:"platform"`
|
||||
NotificationType string `json:"type"`
|
||||
PostId string `json:"post_id,omitempty"`
|
||||
}
|
||||
|
||||
type PushNotification struct {
|
||||
@@ -46,23 +48,23 @@ type PushNotification struct {
|
||||
Platform string `json:"platform"`
|
||||
ServerId string `json:"server_id"`
|
||||
DeviceId string `json:"device_id"`
|
||||
Category string `json:"category"`
|
||||
Sound string `json:"sound"`
|
||||
Message string `json:"message"`
|
||||
Badge int `json:"badge"`
|
||||
ContentAvailable int `json:"cont_ava"`
|
||||
TeamId string `json:"team_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
PostId string `json:"post_id"`
|
||||
RootId string `json:"root_id"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
Type string `json:"type"`
|
||||
SenderId string `json:"sender_id"`
|
||||
SenderName string `json:"sender_name"`
|
||||
OverrideUsername string `json:"override_username"`
|
||||
OverrideIconUrl string `json:"override_icon_url"`
|
||||
FromWebhook string `json:"from_webhook"`
|
||||
Version string `json:"version"`
|
||||
Category string `json:"category,omitempty"`
|
||||
Sound string `json:"sound,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Badge int `json:"badge,omitempty"`
|
||||
ContentAvailable int `json:"cont_ava,omitempty"`
|
||||
TeamId string `json:"team_id,omitempty"`
|
||||
ChannelId string `json:"channel_id,omitempty"`
|
||||
RootId string `json:"root_id,omitempty"`
|
||||
ChannelName string `json:"channel_name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
SenderId string `json:"sender_id,omitempty"`
|
||||
SenderName string `json:"sender_name,omitempty"`
|
||||
OverrideUsername string `json:"override_username,omitempty"`
|
||||
OverrideIconUrl string `json:"override_icon_url,omitempty"`
|
||||
FromWebhook string `json:"from_webhook,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func (me *PushNotification) ToJson() string {
|
||||
|
||||
@@ -268,6 +268,7 @@ func CleanTeamName(s string) string {
|
||||
|
||||
func (o *Team) Sanitize() {
|
||||
o.Email = ""
|
||||
o.InviteId = ""
|
||||
}
|
||||
|
||||
func (t *Team) Patch(patch *TeamPatch) {
|
||||
|
||||
@@ -624,3 +624,8 @@ func GetPreferredTimezone(timezone StringMap) string {
|
||||
|
||||
return timezone["manualTimezone"]
|
||||
}
|
||||
|
||||
// IsSamlFile checks if filename is a SAML file.
|
||||
func IsSamlFile(saml *SamlSettings, filename string) bool {
|
||||
return filename == *saml.PublicCertificateFile || filename == *saml.PrivateKeyFile || filename == *saml.IdpCertificateFile
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ const (
|
||||
WEBSOCKET_EVENT_LICENSE_CHANGED = "license_changed"
|
||||
WEBSOCKET_EVENT_CONFIG_CHANGED = "config_changed"
|
||||
WEBSOCKET_EVENT_OPEN_DIALOG = "open_dialog"
|
||||
WEBSOCKET_EVENT_GUESTS_DEACTIVATED = "guests_deactivated"
|
||||
)
|
||||
|
||||
type WebSocketMessage interface {
|
||||
|
||||
Ссылка в новой задаче
Block a user