1048 lines
29 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"runtime/debug"
"strings"
"github.com/gorilla/mux"
"github.com/larkox/mattermost-plugin-badges/badgesmodel"
"github.com/mattermost/mattermost-server/v5/model"
)
// HTTPHandlerFuncWithUser is http.HandleFunc but userID is already exported
type HTTPHandlerFuncWithUser func(w http.ResponseWriter, r *http.Request, userID string)
// ResponseType indicates type of response returned by api
type ResponseType string
const (
// ResponseTypeJSON indicates that response type is json
ResponseTypeJSON ResponseType = "JSON_RESPONSE"
// ResponseTypePlain indicates that response type is text plain
ResponseTypePlain ResponseType = "TEXT_RESPONSE"
// ResponseTypeDialog indicates that response type is a dialog response
ResponseTypeDialog ResponseType = "DIALOG"
)
type APIErrorResponse struct {
ID string `json:"id"`
Message string `json:"message"`
StatusCode int `json:"status_code"`
}
func (p *Plugin) initializeAPI() {
p.router = mux.NewRouter()
p.router.Use(p.withRecovery)
apiRouter := p.router.PathPrefix("/api/v1").Subrouter()
pluginAPIRouter := p.router.PathPrefix(badgesmodel.PluginAPIPath).Subrouter()
autocompleteRouter := p.router.PathPrefix(AutocompletePath).Subrouter()
dialogRouter := p.router.PathPrefix(DialogPath).Subrouter()
apiRouter.HandleFunc("/getUserBadges/{userID}", p.extractUserMiddleWare(p.getUserBadges, ResponseTypeJSON)).Methods(http.MethodGet)
apiRouter.HandleFunc("/getBadgeDetails/{badgeID}", p.extractUserMiddleWare(p.getBadgeDetails, ResponseTypeJSON)).Methods(http.MethodGet)
apiRouter.HandleFunc("/getAllBadges", p.extractUserMiddleWare(p.getAllBadges, ResponseTypeJSON)).Methods(http.MethodGet)
pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathEnsure, checkPluginRequest(p.ensureBadges)).Methods(http.MethodPost)
pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathGrant, checkPluginRequest(p.grantBadge)).Methods(http.MethodPost)
autocompleteRouter.HandleFunc(AutocompletePathBadgeSuggestions, p.extractUserMiddleWare(p.getBadgeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
autocompleteRouter.HandleFunc(AutocompletePathEditBadgeSuggestions, p.extractUserMiddleWare(p.getEditBadgeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
autocompleteRouter.HandleFunc(AutocompletePathTypeSuggestions, p.extractUserMiddleWare(p.getBadgeTypeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
autocompleteRouter.HandleFunc(AutocompletePathEditTypeSuggestions, p.extractUserMiddleWare(p.getEditBadgeTypeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
dialogRouter.HandleFunc(DialogPathCreateBadge, p.extractUserMiddleWare(p.dialogCreateBadge, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathCreateType, p.extractUserMiddleWare(p.dialogCreateType, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathGrant, p.extractUserMiddleWare(p.dialogGrant, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathSelectBadge, p.extractUserMiddleWare(p.dialogSelectBadge, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathSelectType, p.extractUserMiddleWare(p.dialogSelectType, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathEditBadge, p.extractUserMiddleWare(p.dialogEditBadge, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathEditType, p.extractUserMiddleWare(p.dialogEditType, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathCreateSubscription, p.extractUserMiddleWare(p.dialogCreateSubscription, ResponseTypeDialog)).Methods(http.MethodPost)
dialogRouter.HandleFunc(DialogPathDeleteSubscription, p.extractUserMiddleWare(p.dialogDeleteSubscription, ResponseTypeDialog)).Methods(http.MethodPost)
p.router.PathPrefix("/").HandlerFunc(p.defaultHandler)
}
func (p *Plugin) defaultHandler(w http.ResponseWriter, r *http.Request) {
p.mm.Log.Debug("Unexpected call", "url", r.URL)
w.WriteHeader(http.StatusNotFound)
}
func dialogError(w http.ResponseWriter, text string, errors map[string]string) {
resp := &model.SubmitDialogResponse{
Error: "Error: " + text,
Errors: errors,
}
_, _ = w.Write(resp.ToJson())
}
func dialogOK(w http.ResponseWriter) {
resp := &model.SubmitDialogResponse{}
_, _ = w.Write(resp.ToJson())
}
func dialogKeepOpen(w http.ResponseWriter) {
resp := &model.SubmitDialogResponse{
Error: "_",
}
_, _ = w.Write(resp.ToJson())
}
func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
user, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, "could not get the user", nil)
return
}
toCreate := &badgesmodel.Badge{}
toCreate.CreatedBy = userID
toCreate.ImageType = badgesmodel.ImageTypeEmoji
name, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeName)
if errors != nil {
dialogError(w, errText, errors)
return
}
toCreate.Name = name
description, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeDescription)
if errors != nil {
dialogError(w, errText, errors)
return
}
toCreate.Description = description
image, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeImage)
if errors != nil {
dialogError(w, errText, errors)
return
}
if length := len(image); length > 1 && image[0] == ':' && image[length-1] == ':' {
image = image[1 : len(image)-1]
}
if image == "" {
dialogError(w, "Invalid field", map[string]string{"image": "Empty emoji"})
return
}
toCreate.Image = image
badgeTypeStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
if errors != nil {
dialogError(w, errText, errors)
return
}
toCreate.Type = badgesmodel.BadgeType(badgeTypeStr)
toCreate.Multiple = getDialogSubmissionBoolField(req, DialogFieldBadgeMultiple)
t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
if err != nil {
dialogError(w, "this type does not exist", nil)
return
}
if !canCreateBadge(user, p.badgeAdminUserID, t) {
dialogError(w, "you have no permissions to create this badge", nil)
return
}
_, err = p.store.AddBadge(toCreate)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: fmt.Sprintf("Badge `%s` created.", toCreate.Name),
})
dialogOK(w)
}
func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, "cannot get user", nil)
return
}
if !canCreateType(u, p.badgeAdminUserID, false) {
dialogError(w, "you have no permissions to create a type", nil)
return
}
toCreate := &badgesmodel.BadgeTypeDefinition{}
toCreate.CreatedBy = userID
toCreate.CanCreate.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanCreate)
toCreate.CanGrant.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanGrant)
name, errText, errors := getDialogSubmissionTextField(req, DialogFieldTypeName)
if errors != nil {
dialogError(w, errText, errors)
return
}
toCreate.Name = name
createAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanCreate].(string)
grantAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanGrant].(string)
if createAllowList != "" {
toCreate.CanCreate.AllowList = map[string]bool{}
usernames := strings.Split(createAllowList, ",")
for _, username := range usernames {
username = strings.TrimSpace(username)
if username == "" {
continue
}
u, err := p.mm.User.GetByUsername(username)
if err != nil {
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanCreate: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
return
}
toCreate.CanCreate.AllowList[u.Id] = true
}
}
if grantAllowList != "" {
toCreate.CanGrant.AllowList = map[string]bool{}
usernames := strings.Split(createAllowList, ",")
for _, username := range usernames {
username = strings.TrimSpace(username)
if username == "" {
continue
}
u, err := p.mm.User.GetByUsername(username)
if err != nil {
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanGrant: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
return
}
toCreate.CanGrant.AllowList[u.Id] = true
}
}
_, err = p.store.AddType(toCreate)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: fmt.Sprintf("Type `%s` created.", toCreate.Name),
})
dialogOK(w)
}
// This is not working on the current webapp architecture. A similar approach should be handled using Apps.
func (p *Plugin) dialogSelectType(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
badgeTypeStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
if errors != nil {
dialogError(w, errText, errors)
return
}
t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
if err != nil {
dialogError(w, "Cannot get type", map[string]string{DialogFieldBadgeType: "cannot get type"})
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, "Cannot find user", nil)
return
}
if !canEditType(u, p.badgeAdminUserID, t) {
dialogError(w, "You cannot edit this type", nil)
return
}
_, _ = p.mm.SlashCommand.Execute(&model.CommandArgs{
UserId: userID,
ChannelId: req.ChannelId,
TeamId: req.TeamId,
Command: "/badges edit type --type " + badgeTypeStr,
})
dialogKeepOpen(w)
}
func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, "Cannot find user", nil)
return
}
originalTypeID := req.State
originalType, err := p.store.GetType(badgesmodel.BadgeType(originalTypeID))
if err != nil {
dialogError(w, "could not get the type", nil)
return
}
if !canEditType(u, p.badgeAdminUserID, originalType) {
dialogError(w, "you have no permissions to edit this type", nil)
return
}
if getDialogSubmissionBoolField(req, DialogFieldTypeDelete) {
err = p.store.DeleteType(badgesmodel.BadgeType(originalTypeID))
if err != nil {
dialogError(w, err.Error(), nil)
}
return
}
originalType.CanCreate.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanCreate)
originalType.CanGrant.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanGrant)
name, errText, errors := getDialogSubmissionTextField(req, DialogFieldTypeName)
if errors != nil {
dialogError(w, errText, errors)
return
}
originalType.Name = name
createAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanCreate].(string)
grantAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanGrant].(string)
originalType.CanCreate.AllowList = map[string]bool{}
usernames := strings.Split(createAllowList, ",")
for _, username := range usernames {
username = strings.TrimSpace(username)
if username == "" {
continue
}
var allowedUser *model.User
allowedUser, err = p.mm.User.GetByUsername(username)
if err != nil {
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanCreate: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
return
}
originalType.CanCreate.AllowList[allowedUser.Id] = true
}
originalType.CanGrant.AllowList = map[string]bool{}
usernames = strings.Split(grantAllowList, ",")
for _, username := range usernames {
username = strings.TrimSpace(username)
if username == "" {
continue
}
var allowedUser *model.User
allowedUser, err = p.mm.User.GetByUsername(username)
if err != nil {
dialogError(w, "Cannot find user", map[string]string{DialogFieldTypeAllowlistCanGrant: fmt.Sprintf("Error getting user %s. Error: %v", username, err)})
return
}
originalType.CanGrant.AllowList[allowedUser.Id] = true
}
err = p.store.UpdateType(originalType)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: fmt.Sprintf("Type `%s` updated.", originalType.Name),
})
dialogOK(w)
}
// This is not working on the current webapp architecture. A similar approach should be handled using Apps.
func (p *Plugin) dialogSelectBadge(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
badgeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadge)
if errors != nil {
dialogError(w, errText, errors)
return
}
b, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
if err != nil {
dialogError(w, "Cannot get type", map[string]string{DialogFieldBadge: "cannot get badge"})
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, "Cannot find user", nil)
return
}
if !canEditBadge(u, p.badgeAdminUserID, b) {
dialogError(w, "You cannot edit this badge", nil)
return
}
_, _ = p.mm.SlashCommand.Execute(&model.CommandArgs{
UserId: userID,
ChannelId: req.ChannelId,
TeamId: req.TeamId,
Command: "/badges edit badge --id " + badgeIDStr,
})
dialogKeepOpen(w)
}
func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, "Cannot find user", nil)
return
}
originalBadgeID := req.State
originalBadge, err := p.store.GetBadge(badgesmodel.BadgeID(originalBadgeID))
if err != nil {
dialogError(w, "could not get the badge", nil)
return
}
if !canEditBadge(u, p.badgeAdminUserID, originalBadge) {
dialogError(w, "you have no permissions to edit this type", nil)
return
}
if getDialogSubmissionBoolField(req, DialogFieldBadgeDelete) {
err = p.store.DeleteBadge(badgesmodel.BadgeID(originalBadgeID))
if err != nil {
dialogError(w, err.Error(), nil)
return
}
return
}
name, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeName)
if errors != nil {
dialogError(w, errText, errors)
return
}
originalBadge.Name = name
description, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeDescription)
if errors != nil {
dialogError(w, errText, errors)
return
}
originalBadge.Description = description
image, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeImage)
if errors != nil {
dialogError(w, errText, errors)
return
}
if length := len(image); length > 1 && image[0] == ':' && image[length-1] == ':' {
image = image[1 : len(image)-1]
}
if image == "" {
dialogError(w, "Invalid field", map[string]string{"image": "Empty emoji"})
return
}
originalBadge.Image = image
badgeTypeStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
if errors != nil {
dialogError(w, errText, errors)
return
}
originalBadge.Type = badgesmodel.BadgeType(badgeTypeStr)
originalBadge.Multiple = getDialogSubmissionBoolField(req, DialogFieldBadgeMultiple)
err = p.store.UpdateBadge(originalBadge)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: fmt.Sprintf("Badge `%s` updated.", originalBadge.Name),
})
dialogOK(w)
}
func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
badgeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadge)
if errors != nil {
dialogError(w, errText, errors)
return
}
notifyHere := getDialogSubmissionBoolField(req, DialogFieldNotifyHere)
badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
if err != nil {
dialogError(w, "badge not found", nil)
return
}
granter, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
badgeType, err := p.store.GetType(badge.Type)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
if !canGrantBadge(granter, p.badgeAdminUserID, badge, badgeType) {
dialogError(w, "you have no permissions to grant this badge", nil)
return
}
grantToID := req.State
if grantToID == "" {
grantToID, errText, errors = getDialogSubmissionTextField(req, DialogFieldUser)
if errors != nil {
dialogError(w, errText, errors)
return
}
}
grantToUser, err := p.mm.User.Get(grantToID)
if err != nil {
dialogError(w, "user not found", nil)
return
}
reason, _ := req.Submission[DialogFieldGrantReason].(string)
shouldNotify, err := p.store.GrantBadge(badgesmodel.BadgeID(badgeIDStr), grantToID, userID, reason)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot grant badge",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
if shouldNotify {
p.notifyGrant(badgesmodel.BadgeID(badgeIDStr), userID, grantToUser, notifyHere, req.ChannelId, reason)
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: fmt.Sprintf("Badge `%s` granted to @%s.", badge.Name, grantToUser.Username),
})
dialogOK(w)
}
func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
if !canCreateSubscription(u, p.badgeAdminUserID, req.ChannelId) {
dialogError(w, "You cannot create a subscription", nil)
return
}
typeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
if errors != nil {
dialogError(w, errText, errors)
return
}
err = p.store.AddSubscription(badgesmodel.BadgeType(typeIDStr), req.ChannelId)
if err != nil {
dialogError(w, err.Error(), nil)
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: "Subscription added",
})
dialogOK(w)
}
func (p *Plugin) dialogDeleteSubscription(w http.ResponseWriter, r *http.Request, userID string) {
req := model.SubmitDialogRequestFromJson(r.Body)
if req == nil {
dialogError(w, "could not get the dialog request", nil)
return
}
u, err := p.mm.User.Get(userID)
if err != nil {
dialogError(w, err.Error(), nil)
return
}
if !canCreateSubscription(u, p.badgeAdminUserID, req.ChannelId) {
dialogError(w, "You cannot delete a subscription", nil)
return
}
typeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
if errors != nil {
dialogError(w, errText, errors)
return
}
err = p.store.RemoveSubscriptions(badgesmodel.BadgeType(typeIDStr), req.ChannelId)
if err != nil {
dialogError(w, err.Error(), nil)
}
p.mm.Post.SendEphemeralPost(userID, &model.Post{
UserId: p.BotUserID,
ChannelId: req.ChannelId,
Message: "Subscription removed",
})
dialogOK(w)
}
func getDialogSubmissionTextField(req *model.SubmitDialogRequest, fieldName string) (value string, errText string, errors map[string]string) {
value, ok := req.Submission[fieldName].(string)
value = strings.TrimSpace(value)
if !ok || value == "" {
return "", "Invalid argument", map[string]string{fieldName: "Field empty or not recognized."}
}
return value, "", nil
}
func getDialogSubmissionBoolField(req *model.SubmitDialogRequest, fieldName string) bool {
value, _ := req.Submission[fieldName].(bool)
return value
}
func (p *Plugin) grantBadge(w http.ResponseWriter, r *http.Request, pluginID string) {
var req *badgesmodel.GrantBadgeRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot unmarshal request",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
p.mm.Log.Debug("Granting badge", "req", req)
if req == nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "missing request",
Message: "Missing grant request on request body",
StatusCode: http.StatusInternalServerError,
})
return
}
granter, err := p.mm.User.Get(req.BotID)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot get user",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
badge, err := p.store.GetBadge(badgesmodel.BadgeID(req.BadgeID))
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot get badge",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
badgeType, err := p.store.GetType(badge.Type)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot get type",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
if !canGrantBadge(granter, p.badgeAdminUserID, badge, badgeType) {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot grant badge",
Message: "you have no permissions to grant this badge",
StatusCode: http.StatusUnauthorized,
})
return
}
shouldNotify, err := p.store.GrantBadge(req.BadgeID, req.UserID, req.BotID, req.Reason)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot grant badge",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
if shouldNotify {
u, err := p.mm.User.Get(req.UserID)
if err == nil {
p.notifyGrant(req.BadgeID, req.BotID, u, false, "", req.Reason)
}
}
_, _ = w.Write([]byte(`{"sucess": true}`))
}
func (p *Plugin) ensureBadges(w http.ResponseWriter, r *http.Request, pluginID string) {
var req *badgesmodel.EnsureBadgesRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot unmarshal request",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
if req == nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "missing request",
Message: "Missing ensure request on request body",
StatusCode: http.StatusInternalServerError,
})
return
}
badges, err := p.store.EnsureBadges(req.Badges, pluginID, req.BotID)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot ensure",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
b, err := json.Marshal(badges)
if err != nil {
p.writeAPIError(w, &APIErrorResponse{
ID: "cannot marshal",
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
})
return
}
_, _ = w.Write(b)
}
func (p *Plugin) getBadgeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
out := []model.AutocompleteListItem{}
u, err := p.mm.User.Get(actingUserID)
if err != nil {
p.mm.Log.Debug("Error getting user", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
bb, err := p.filterGrantBadges(u)
if err != nil {
p.mm.Log.Debug("Error getting suggestions", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
for _, b := range bb {
s := model.AutocompleteListItem{
Item: string(b.ID),
Hint: b.Name,
HelpText: b.Description,
}
out = append(out, s)
}
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}
func (p *Plugin) getEditBadgeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
out := []model.AutocompleteListItem{}
u, err := p.mm.User.Get(actingUserID)
if err != nil {
p.mm.Log.Debug("Error getting user", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
bb, err := p.filterEditBadges(u)
if err != nil {
p.mm.Log.Debug("Error getting suggestions", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
for _, b := range bb {
s := model.AutocompleteListItem{
Item: string(b.ID),
Hint: b.Name,
HelpText: b.Description,
}
out = append(out, s)
}
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}
func (p *Plugin) getBadgeTypeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
out := []model.AutocompleteListItem{}
u, err := p.mm.User.Get(actingUserID)
if err != nil {
p.mm.Log.Debug("Error getting user", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
types, err := p.filterCreateBadgeTypes(u)
if err != nil {
p.mm.Log.Debug("Error getting suggestions", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
for _, t := range types {
s := model.AutocompleteListItem{
Item: string(t.ID),
Hint: t.Name,
}
out = append(out, s)
}
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}
func (p *Plugin) getEditBadgeTypeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
out := []model.AutocompleteListItem{}
u, err := p.mm.User.Get(actingUserID)
if err != nil {
p.mm.Log.Debug("Error getting user", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
types, err := p.filterEditTypes(u)
if err != nil {
p.mm.Log.Debug("Error getting suggestions", "error", err)
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
return
}
for _, t := range types {
s := model.AutocompleteListItem{
Item: string(t.ID),
Hint: t.Name,
}
out = append(out, s)
}
_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}
func (p *Plugin) getUserBadges(w http.ResponseWriter, r *http.Request, actingUserID string) {
userID, ok := mux.Vars(r)["userID"]
if !ok {
userID = actingUserID
}
badges, err := p.store.GetUserBadges(userID)
if err != nil {
p.mm.Log.Debug("Error getting the badges for user", "error", err, "user", userID)
}
b, _ := json.Marshal(badges)
_, _ = w.Write(b)
}
func (p *Plugin) getBadgeDetails(w http.ResponseWriter, r *http.Request, actingUserID string) {
badgeIDString, ok := mux.Vars(r)["badgeID"]
if !ok {
errMessage := "Missing badge id"
p.mm.Log.Debug(errMessage)
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(errMessage))
return
}
badgeID := badgesmodel.BadgeID(badgeIDString)
badge, err := p.store.GetBadgeDetails(badgeID)
if err != nil {
p.mm.Log.Debug("Cannot get badge details", "badgeID", badgeID, "error", err)
}
b, _ := json.Marshal(badge)
_, _ = w.Write(b)
}
func (p *Plugin) getAllBadges(w http.ResponseWriter, r *http.Request, actingUserID string) {
badge, err := p.store.GetAllBadges()
if err != nil {
p.mm.Log.Debug("Cannot get all badges", "error", err)
}
b, _ := json.Marshal(badge)
_, _ = w.Write(b)
}
func (p *Plugin) extractUserMiddleWare(handler HTTPHandlerFuncWithUser, responseType ResponseType) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("Mattermost-User-ID")
if userID == "" {
switch responseType {
case ResponseTypeJSON:
p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized})
case ResponseTypePlain:
http.Error(w, "Not authorized", http.StatusUnauthorized)
case ResponseTypeDialog:
dialogError(w, "Not Authorized", nil)
default:
p.mm.Log.Error("Unknown ResponseType detected")
}
return
}
handler(w, r, userID)
}
}
func (p *Plugin) withRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if x := recover(); x != nil {
p.mm.Log.Error("Recovered from a panic",
"url", r.URL.String(),
"error", x,
"stack", string(debug.Stack()))
}
}()
next.ServeHTTP(w, r)
})
}
func checkPluginRequest(next HTTPHandlerFuncWithUser) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// All other plugins are allowed
pluginID := r.Header.Get("Mattermost-Plugin-ID")
if pluginID == "" {
http.Error(w, "Not authorized", http.StatusUnauthorized)
return
}
next(w, r, pluginID)
}
}
func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse) {
b, err := json.Marshal(apiErr)
if err != nil {
p.mm.Log.Warn("Failed to marshal API error", "error", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(apiErr.StatusCode)
_, err = w.Write(b)
if err != nil {
p.mm.Log.Warn("Failed to write JSON response", "error", err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (p *Plugin) getPluginURL() string {
urlP := p.mm.Configuration.GetConfig().ServiceSettings.SiteURL
url := "/"
if urlP != nil {
url = *urlP
}
if url[len(url)-1] == '/' {
url = url[0 : len(url)-1]
}
return url + "/plugins/" + manifest.Id
}
func (p *Plugin) getDialogURL() string {
return p.getPluginURL() + DialogPath
}