Skip to content

Commit

Permalink
Adding API for adding/replacing links.
Browse files Browse the repository at this point in the history
  • Loading branch information
crspeller committed Jan 23, 2020
1 parent 542fab8 commit 383545a
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 95 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/mattermost/mattermost-plugin-autolink
go 1.12

require (
github.com/gorilla/mux v1.7.3
github.com/mattermost/mattermost-server/v5 v5.18.0
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down Expand Up @@ -120,6 +121,7 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORR
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
Expand Down
129 changes: 129 additions & 0 deletions server/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package api

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/gorilla/mux"
"github.com/mattermost/mattermost-plugin-autolink/server/link"
)

const PluginIDContextValue = "pluginid"

type LinkStore interface {
GetLinks() []link.Link
SaveLinks([]link.Link) error
}

type Authorization interface {
IsAuthorizedAdmin(userID string) (bool, error)
}

type Handler struct {
root *mux.Router
linkStore LinkStore
authorization Authorization
}

func NewHandler(linkStore LinkStore, authorization Authorization) *Handler {
h := &Handler{
linkStore: linkStore,
authorization: authorization,
}

root := mux.NewRouter()
api := root.PathPrefix("/api/v1").Subrouter()
api.Use(h.adminOrPluginRequired)
api.HandleFunc("/link", h.setLink).Methods("POST")

api.Handle("{anything:.*}", http.NotFoundHandler())

h.root = root

return h
}

func (h *Handler) handleError(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusInternalServerError)
b, _ := json.Marshal(struct {
Error string `json:"error"`
Details string `json:"details"`
}{
Error: "An internal error has occurred. Check app server logs for details.",
Details: err.Error(),
})
_, _ = w.Write(b)
}

func (h *Handler) handleErrorWithCode(w http.ResponseWriter, code int, errTitle string, err error) {
w.WriteHeader(code)
b, _ := json.Marshal(struct {
Error string `json:"error"`
Details string `json:"details"`
}{
Error: errTitle,
Details: err.Error(),
})
_, _ = w.Write(b)
}

func (h *Handler) adminOrPluginRequired(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("Mattermost-User-ID")
if userID != "" {
authorized, err := h.authorization.IsAuthorizedAdmin(userID)
if err != nil {
http.Error(w, "Not authorized", http.StatusUnauthorized)
return
}
if authorized {
next.ServeHTTP(w, r)
return
}
}

ifPluginId := r.Context().Value(PluginIDContextValue)
pluginId, ok := ifPluginId.(string)
if ok && pluginId != "" {
next.ServeHTTP(w, r)
return
}

http.Error(w, "Not authorized", http.StatusUnauthorized)
})
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fromPluginID string) {
h.root.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), PluginIDContextValue, fromPluginID)))
}

func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) {
var newLink link.Link
if err := json.NewDecoder(r.Body).Decode(&newLink); err != nil {
h.handleError(w, fmt.Errorf("Unable to decode body: %w", err))
return
}

links := h.linkStore.GetLinks()
found := false
for i := range links {
if links[i].Name == newLink.Name || links[i].Pattern == newLink.Pattern {
links[i] = newLink
found = true
break
}
}
if !found {
links = append(h.linkStore.GetLinks(), newLink)
}

if err := h.linkStore.SaveLinks(links); err != nil {
h.handleError(w, fmt.Errorf("Unable to save link: %w", err))
return
}

w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"status": "OK"}`))
}
9 changes: 5 additions & 4 deletions server/command.go → server/autolinkplugin/command.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package main
package autolinkplugin

import (
"fmt"
"strconv"
"strings"

"github.com/mattermost/mattermost-plugin-autolink/server/link"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/pkg/errors"
Expand Down Expand Up @@ -261,7 +262,7 @@ func executeAdd(p *Plugin, c *plugin.Context, header *model.CommandArgs, args ..
name = args[0]
}

err := saveConfigLinks(p, append(p.getConfig().Links, Link{
err := saveConfigLinks(p, append(p.getConfig().Links, link.Link{
Name: name,
}))
if err != nil {
Expand All @@ -286,7 +287,7 @@ func responsef(format string, args ...interface{}) *model.CommandResponse {
}
}

func parseLinkRef(p *Plugin, requireUnique bool, args ...string) ([]Link, []int, error) {
func parseLinkRef(p *Plugin, requireUnique bool, args ...string) ([]link.Link, []int, error) {
links := p.getConfig().Sorted().Links
if len(args) == 0 {
if requireUnique {
Expand Down Expand Up @@ -334,7 +335,7 @@ func parseBoolArg(arg string) (bool, error) {
return false, errors.Errorf("Not a bool, %q", arg)
}

func saveConfigLinks(p *Plugin, links []Link) error {
func saveConfigLinks(p *Plugin, links []link.Link) error {
conf := p.getConfig()
conf.Links = links
appErr := p.API.SavePluginConfig(conf.ToConfig())
Expand Down
29 changes: 24 additions & 5 deletions server/config.go → server/autolinkplugin/config.go
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package main
package autolinkplugin

import (
"fmt"
"sort"
"strings"

"github.com/mattermost/mattermost-plugin-autolink/server/link"
"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
)

// Config from config.json
type Config struct {
EnableAdminCommand bool
Links []Link
Links []link.Link
}

// OnConfigurationChange is invoked when configuration changes may have been made.
Expand All @@ -30,7 +31,7 @@ func (p *Plugin) OnConfigurationChange() error {
}
}

p.updateConfig(func(conf *Config) {
p.UpdateConfig(func(conf *Config) {
*conf = c
})

Expand All @@ -57,7 +58,25 @@ func (p *Plugin) getConfig() Config {
return p.conf
}

func (p *Plugin) updateConfig(f func(conf *Config)) Config {
func (p *Plugin) GetLinks() []link.Link {
p.confLock.RLock()
defer p.confLock.RUnlock()
return p.conf.Links
}

func (p *Plugin) SaveLinks(links []link.Link) error {
p.UpdateConfig(func(conf *Config) {
conf.Links = links
})
appErr := p.API.SavePluginConfig(p.getConfig().ToConfig())
if appErr != nil {
return fmt.Errorf("Unable to save links: %w", appErr)
}

return nil
}

func (p *Plugin) UpdateConfig(f func(conf *Config)) Config {
p.confLock.Lock()
defer p.confLock.Unlock()

Expand All @@ -81,7 +100,7 @@ func (conf Config) ToConfig() map[string]interface{} {
// Sorted returns a clone of the Config, with links sorted alphabetically
func (conf Config) Sorted() Config {
sorted := conf
sorted.Links = append([]Link{}, conf.Links...)
sorted.Links = append([]link.Link{}, conf.Links...)
sort.Slice(conf.Links, func(i, j int) bool {
return strings.Compare(conf.Links[i].DisplayName(), conf.Links[j].DisplayName()) < 0
})
Expand Down
49 changes: 47 additions & 2 deletions server/plugin.go → server/autolinkplugin/plugin.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package main
package autolinkplugin

import (
"fmt"
"net/http"
"strings"
"sync"

"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-plugin-autolink/server/api"

"github.com/mattermost/mattermost-server/v5/mlog"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/mattermost/mattermost-server/v5/utils/markdown"
Expand All @@ -15,11 +18,49 @@ import (
type Plugin struct {
plugin.MattermostPlugin

handler *api.Handler

// configuration and a muttex to control concurrent access
conf Config
confLock sync.RWMutex
}

func (p *Plugin) OnActivate() error {
p.handler = api.NewHandler(p, p)

return nil
}

func (p *Plugin) IsAuthorizedAdmin(mattermostID string) (bool, error) {
user, err := p.API.GetUser(mattermostID)
if err != nil {
return false, err
}
if strings.Contains(user.Roles, "system_admin") {
return true, nil
}
return false, nil
}

func contains(team string, channel string, list []string) bool {
for _, channelTeam := range list {
channelTeamSplit := strings.Split(channelTeam, "/")
if len(channelTeamSplit) == 2 {
if strings.EqualFold(channelTeamSplit[0], team) && strings.EqualFold(channelTeamSplit[1], channel) {
return true
}
} else if len(channelTeamSplit) == 1 {
if strings.EqualFold(channelTeamSplit[0], team) {
return true
}
} else {
mlog.Error("error splitting channel & team combination.")
}

}
return false
}

// MessageWillBePosted is invoked when a message is posted by a user before it is committed
// to the database.
func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
Expand Down Expand Up @@ -98,3 +139,7 @@ func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*mode

return post, ""
}

func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
p.handler.ServeHTTP(w, r, c.SourcePluginId)
}
Loading

0 comments on commit 383545a

Please sign in to comment.