github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/model/bot.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package model
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"strings"
    12  	"unicode/utf8"
    13  )
    14  
    15  const (
    16  	BOT_DISPLAY_NAME_MAX_RUNES   = USER_FIRST_NAME_MAX_RUNES
    17  	BOT_DESCRIPTION_MAX_RUNES    = 1024
    18  	BOT_CREATOR_ID_MAX_RUNES     = KEY_VALUE_PLUGIN_ID_MAX_RUNES // UserId or PluginId
    19  	BOT_WARN_METRIC_BOT_USERNAME = "mattermost-advisor"
    20  )
    21  
    22  // Bot is a special type of User meant for programmatic interactions.
    23  // Note that the primary key of a bot is the UserId, and matches the primary key of the
    24  // corresponding user.
    25  type Bot struct {
    26  	UserId         string `json:"user_id"`
    27  	Username       string `json:"username"`
    28  	DisplayName    string `json:"display_name,omitempty"`
    29  	Description    string `json:"description,omitempty"`
    30  	OwnerId        string `json:"owner_id"`
    31  	LastIconUpdate int64  `json:"last_icon_update,omitempty"`
    32  	CreateAt       int64  `json:"create_at"`
    33  	UpdateAt       int64  `json:"update_at"`
    34  	DeleteAt       int64  `json:"delete_at"`
    35  }
    36  
    37  // BotPatch is a description of what fields to update on an existing bot.
    38  type BotPatch struct {
    39  	Username    *string `json:"username"`
    40  	DisplayName *string `json:"display_name"`
    41  	Description *string `json:"description"`
    42  }
    43  
    44  // BotGetOptions acts as a filter on bulk bot fetching queries.
    45  type BotGetOptions struct {
    46  	OwnerId        string
    47  	IncludeDeleted bool
    48  	OnlyOrphaned   bool
    49  	Page           int
    50  	PerPage        int
    51  }
    52  
    53  // BotList is a list of bots.
    54  type BotList []*Bot
    55  
    56  // Trace describes the minimum information required to identify a bot for the purpose of logging.
    57  func (b *Bot) Trace() map[string]interface{} {
    58  	return map[string]interface{}{"user_id": b.UserId}
    59  }
    60  
    61  // Clone returns a shallow copy of the bot.
    62  func (b *Bot) Clone() *Bot {
    63  	copy := *b
    64  	return &copy
    65  }
    66  
    67  // IsValid validates the bot and returns an error if it isn't configured correctly.
    68  func (b *Bot) IsValid() *AppError {
    69  	if !IsValidId(b.UserId) {
    70  		return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
    71  	}
    72  
    73  	if !IsValidUsername(b.Username) {
    74  		return NewAppError("Bot.IsValid", "model.bot.is_valid.username.app_error", b.Trace(), "", http.StatusBadRequest)
    75  	}
    76  
    77  	if utf8.RuneCountInString(b.DisplayName) > BOT_DISPLAY_NAME_MAX_RUNES {
    78  		return NewAppError("Bot.IsValid", "model.bot.is_valid.user_id.app_error", b.Trace(), "", http.StatusBadRequest)
    79  	}
    80  
    81  	if utf8.RuneCountInString(b.Description) > BOT_DESCRIPTION_MAX_RUNES {
    82  		return NewAppError("Bot.IsValid", "model.bot.is_valid.description.app_error", b.Trace(), "", http.StatusBadRequest)
    83  	}
    84  
    85  	if len(b.OwnerId) == 0 || utf8.RuneCountInString(b.OwnerId) > BOT_CREATOR_ID_MAX_RUNES {
    86  		return NewAppError("Bot.IsValid", "model.bot.is_valid.creator_id.app_error", b.Trace(), "", http.StatusBadRequest)
    87  	}
    88  
    89  	if b.CreateAt == 0 {
    90  		return NewAppError("Bot.IsValid", "model.bot.is_valid.create_at.app_error", b.Trace(), "", http.StatusBadRequest)
    91  	}
    92  
    93  	if b.UpdateAt == 0 {
    94  		return NewAppError("Bot.IsValid", "model.bot.is_valid.update_at.app_error", b.Trace(), "", http.StatusBadRequest)
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // PreSave should be run before saving a new bot to the database.
   101  func (b *Bot) PreSave() {
   102  	b.CreateAt = GetMillis()
   103  	b.UpdateAt = b.CreateAt
   104  	b.DeleteAt = 0
   105  }
   106  
   107  // PreUpdate should be run before saving an updated bot to the database.
   108  func (b *Bot) PreUpdate() {
   109  	b.UpdateAt = GetMillis()
   110  }
   111  
   112  // Etag generates an etag for caching.
   113  func (b *Bot) Etag() string {
   114  	return Etag(b.UserId, b.UpdateAt)
   115  }
   116  
   117  // ToJson serializes the bot to json.
   118  func (b *Bot) ToJson() []byte {
   119  	data, _ := json.Marshal(b)
   120  	return data
   121  }
   122  
   123  // BotFromJson deserializes a bot from json.
   124  func BotFromJson(data io.Reader) *Bot {
   125  	var bot *Bot
   126  	json.NewDecoder(data).Decode(&bot)
   127  	return bot
   128  }
   129  
   130  // Patch modifies an existing bot with optional fields from the given patch.
   131  func (b *Bot) Patch(patch *BotPatch) {
   132  	if patch.Username != nil {
   133  		b.Username = *patch.Username
   134  	}
   135  
   136  	if patch.DisplayName != nil {
   137  		b.DisplayName = *patch.DisplayName
   138  	}
   139  
   140  	if patch.Description != nil {
   141  		b.Description = *patch.Description
   142  	}
   143  }
   144  
   145  // ToJson serializes the bot patch to json.
   146  func (b *BotPatch) ToJson() []byte {
   147  	data, err := json.Marshal(b)
   148  	if err != nil {
   149  		return nil
   150  	}
   151  
   152  	return data
   153  }
   154  
   155  // BotPatchFromJson deserializes a bot patch from json.
   156  func BotPatchFromJson(data io.Reader) *BotPatch {
   157  	decoder := json.NewDecoder(data)
   158  	var botPatch BotPatch
   159  	err := decoder.Decode(&botPatch)
   160  	if err != nil {
   161  		return nil
   162  	}
   163  
   164  	return &botPatch
   165  }
   166  
   167  // UserFromBot returns a user model describing the bot fields stored in the User store.
   168  func UserFromBot(b *Bot) *User {
   169  	return &User{
   170  		Id:        b.UserId,
   171  		Username:  b.Username,
   172  		Email:     NormalizeEmail(fmt.Sprintf("%s@localhost", b.Username)),
   173  		FirstName: b.DisplayName,
   174  		Roles:     SYSTEM_USER_ROLE_ID,
   175  	}
   176  }
   177  
   178  // BotFromUser returns a bot model given a user model
   179  func BotFromUser(u *User) *Bot {
   180  	return &Bot{
   181  		OwnerId:     u.Id,
   182  		UserId:      u.Id,
   183  		Username:    u.Username,
   184  		DisplayName: u.GetDisplayName(SHOW_USERNAME),
   185  	}
   186  }
   187  
   188  // BotListFromJson deserializes a list of bots from json.
   189  func BotListFromJson(data io.Reader) BotList {
   190  	var bots BotList
   191  	json.NewDecoder(data).Decode(&bots)
   192  	return bots
   193  }
   194  
   195  // ToJson serializes a list of bots to json.
   196  func (l *BotList) ToJson() []byte {
   197  	b, _ := json.Marshal(l)
   198  	return b
   199  }
   200  
   201  // Etag computes the etag for a list of bots.
   202  func (l *BotList) Etag() string {
   203  	id := "0"
   204  	var t int64 = 0
   205  	var delta int64 = 0
   206  
   207  	for _, v := range *l {
   208  		if v.UpdateAt > t {
   209  			t = v.UpdateAt
   210  			id = v.UserId
   211  		}
   212  
   213  	}
   214  
   215  	return Etag(id, t, delta, len(*l))
   216  }
   217  
   218  // MakeBotNotFoundError creates the error returned when a bot does not exist, or when the user isn't allowed to query the bot.
   219  // The errors must the same in both cases to avoid leaking that a user is a bot.
   220  func MakeBotNotFoundError(userId string) *AppError {
   221  	return NewAppError("SqlBotStore.Get", "store.sql_bot.get.missing.app_error", map[string]interface{}{"user_id": userId}, "", http.StatusNotFound)
   222  }
   223  
   224  func IsBotDMChannel(channel *Channel, botUserID string) bool {
   225  	if channel.Type != CHANNEL_DIRECT {
   226  		return false
   227  	}
   228  
   229  	if !strings.HasPrefix(channel.Name, botUserID+"__") && !strings.HasSuffix(channel.Name, "__"+botUserID) {
   230  		return false
   231  	}
   232  
   233  	return true
   234  }