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 }