Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Autolink admin config parsing #184

Merged
merged 1 commit into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@
"header": "Configure this plugin directly in the `config.json` file, or using the `/autolink` command. Learn more [in our documentation](https://github.com/mattermost/mattermost-plugin-autolink/blob/master/README.md).\n\n To report an issue, make a suggestion, or contribute, [check the plugin repository](https://github.com/mattermost/mattermost-plugin-autolink).",
"settings": [
{
"key": "EnableAdminCommand",
"key": "enableadmincommand",
"display_name": "Enable administration with /autolink command:",
"type": "bool",
"default": true
},
{
"key": "EnableOnUpdate",
"key": "enableonupdate",
"display_name": "Apply plugin to updated posts as well as new posts:",
"type": "bool",
"default": false
},
{
"key": "PluginAdmins",
"key": "pluginadmins",
"display_name": "Admin User IDs:",
"type": "text",
"help_text": "Comma-separated list of user IDs authorized to administer the plugin in addition to the System Admins.\n \n User IDs can be found by navigating to **System Console > User Management > Users**."
Expand Down
6 changes: 3 additions & 3 deletions server/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package api

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

"github.com/gorilla/mux"
"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
)
Expand Down Expand Up @@ -90,7 +90,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) {
var newLink autolink.Autolink
if err := json.NewDecoder(r.Body).Decode(&newLink); err != nil {
h.handleError(w, fmt.Errorf("unable to decode body: %w", err))
h.handleError(w, errors.Wrap(err, "unable to decode body"))
return
}

Expand All @@ -114,7 +114,7 @@ func (h *Handler) setLink(w http.ResponseWriter, r *http.Request) {
status := http.StatusNotModified
if changed {
if err := h.store.SaveLinks(links); err != nil {
h.handleError(w, fmt.Errorf("unable to save link: %w", err))
h.handleError(w, errors.Wrap(err, "unable to save link"))
return
}
status = http.StatusOK
Expand Down
32 changes: 8 additions & 24 deletions server/autolink/autolink.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (

// Autolink represents a pattern to autolink.
type Autolink struct {
Name string
Disabled bool
Pattern string
Template string
Scope []string
WordMatch bool
DisableNonWordPrefix bool
DisableNonWordSuffix bool
Name string `json:"Name"`
Disabled bool `json:"Disabled"`
Pattern string `json:"Pattern"`
Template string `json:"Template"`
Scope []string `json:"Scope"`
WordMatch bool `json:"WordMatch"`
DisableNonWordPrefix bool `json:"DisableNonWordPrefix"`
DisableNonWordSuffix bool `json:"DisableNonWordSuffix"`

template string
re *regexp.Regexp
Expand Down Expand Up @@ -156,19 +156,3 @@ func (l Autolink) ToMarkdown(i int) string {
}
return text
}

// ToConfig returns a JSON-encodable Link represented solely with map[string]
// interface and []string types, compatible with gob/RPC, to be used in
// SavePluginConfig
func (l Autolink) ToConfig() map[string]interface{} {
return map[string]interface{}{
"Name": l.Name,
"Pattern": l.Pattern,
"Template": l.Template,
"Scope": l.Scope,
"DisableNonWordPrefix": l.DisableNonWordPrefix,
"DisableNonWordSuffix": l.DisableNonWordSuffix,
"WordMatch": l.WordMatch,
"Disabled": l.Disabled,
}
}
8 changes: 7 additions & 1 deletion server/autolinkplugin/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,13 @@ func parseBoolArg(arg string) (bool, error) {
func saveConfigLinks(p *Plugin, links []autolink.Autolink) error {
conf := p.getConfig()
conf.Links = links
appErr := p.API.SavePluginConfig(conf.ToConfig())

configMap, err := p.getConfig().ToMap()
if err != nil {
return err
}

appErr := p.API.SavePluginConfig(configMap)
if appErr != nil {
return appErr
}
Expand Down
60 changes: 34 additions & 26 deletions server/autolinkplugin/config.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
package autolinkplugin

import (
"fmt"
"encoding/json"
"sort"
"strings"

"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin"
"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
)

// Config from config.json
type Config struct {
EnableAdminCommand bool
EnableOnUpdate bool
PluginAdmins string
Links []autolink.Autolink
EnableAdminCommand bool `json:"enableadmincommand"`
EnableOnUpdate bool `json:"enableonupdate"`
PluginAdmins string `json:"pluginadmins"`
Links []autolink.Autolink `json:"links"`

// AdminUserIds is a set of UserIds that are permitted to perform
// administrative operations on the plugin configuration (i.e. plugin
// admins). On each configuration change the contents of PluginAdmins
// config field is parsed into this field.
AdminUserIds map[string]struct{}
AdminUserIds map[string]struct{} `json:"-"`
}

// OnConfigurationChange is invoked when configuration changes may have been made.
func (p *Plugin) OnConfigurationChange() error {
var c Config
if err := p.API.LoadPluginConfiguration(&c); err != nil {
return fmt.Errorf("failed to load configuration: %w", err)
return errors.Wrap(err, "failed to load plugin configuration")
}

for i := range c.Links {
Expand All @@ -40,7 +42,7 @@ func (p *Plugin) OnConfigurationChange() error {
// Plugin admin UserId parsing and validation errors are
// not fatal, if everything fails only sysadmin will be able to manage the
// config which is still OK
c.parsePluginAdminList(p)
c.parsePluginAdminList(p.API)

p.UpdateConfig(func(conf *Config) {
*conf = c
Expand Down Expand Up @@ -169,9 +171,15 @@ func (p *Plugin) SaveLinks(links []autolink.Autolink) error {
p.UpdateConfig(func(conf *Config) {
conf.Links = links
})
appErr := p.API.SavePluginConfig(p.getConfig().ToConfig())

configMap, err := p.getConfig().ToMap()
if err != nil {
return errors.Wrap(err, "unable convert config to map")
}

appErr := p.API.SavePluginConfig(configMap)
if appErr != nil {
return fmt.Errorf("unable to save links: %w", appErr)
return errors.Wrap(appErr, "unable to save links")
}

return nil
Expand All @@ -186,17 +194,18 @@ func (p *Plugin) UpdateConfig(f func(conf *Config)) {

// ToConfig marshals Config into a tree of map[string]interface{} to pass down
// to p.API.SavePluginConfig, otherwise RPC/gob barfs at the unknown type.
func (conf *Config) ToConfig() map[string]interface{} {
links := []interface{}{}
for _, l := range conf.Links {
links = append(links, l.ToConfig())
func (conf *Config) ToMap() (map[string]interface{}, error) {
var out map[string]interface{}
data, err := json.Marshal(conf)
if err != nil {
return nil, err
}
return map[string]interface{}{
"EnableAdminCommand": conf.EnableAdminCommand,
"EnableOnUpdate": conf.EnableOnUpdate,
"PluginAdmins": conf.PluginAdmins,
"Links": links,
err = json.Unmarshal(data, &out)
if err != nil {
return nil, err
}

return out, nil
}

// Sorted returns a clone of the Config, with links sorted alphabetically
Expand All @@ -210,22 +219,21 @@ func (conf *Config) Sorted() *Config {
}

// parsePluginAdminList parses the contents of PluginAdmins config field
func (conf *Config) parsePluginAdminList(p *Plugin) {
conf.AdminUserIds = make(map[string]struct{})
func (conf *Config) parsePluginAdminList(api plugin.API) {
conf.AdminUserIds = make(map[string]struct{}, len(conf.PluginAdmins))

if len(conf.PluginAdmins) == 0 {
// There were no plugin admin users defined
return
}

userIDs := strings.Split(conf.PluginAdmins, ",")

for _, v := range userIDs {
userID := strings.TrimSpace(v)
for _, userID := range userIDs {
userID = strings.TrimSpace(userID)
// Let's verify that the given user really exists
_, appErr := p.API.GetUser(userID)
_, appErr := api.GetUser(userID)
if appErr != nil {
p.API.LogError(fmt.Sprintf("error occurred while verifying userID %s: %v", v, appErr))
api.LogWarn("Error occurred while verifying userID", "userID", userID, "error", appErr)
} else {
conf.AdminUserIds[userID] = struct{}{}
}
Expand Down
4 changes: 2 additions & 2 deletions server/autolinkplugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin"
"github.com/mattermost/mattermost-server/v6/shared/markdown"
"github.com/pkg/errors"

"github.com/mattermost/mattermost-plugin-autolink/server/api"
)
Expand Down Expand Up @@ -39,8 +40,7 @@ func (p *Plugin) OnActivate() error {
func (p *Plugin) IsAuthorizedAdmin(userID string) (bool, error) {
user, err := p.API.GetUser(userID)
if err != nil {
return false, fmt.Errorf(
"failed to obtain information about user `%s`: %w", userID, err)
return false, errors.Wrapf(err, "failed to obtain information about user `%s`", userID)
}
if strings.Contains(user.Roles, "system_admin") {
p.API.LogInfo(
Expand Down
7 changes: 6 additions & 1 deletion server/autolinkplugin/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,12 @@ func (suite *SuiteAuthorization) TestNonExistantUsersAreIgnored() {
}
suite.adminUsernames = "marynaId,karynaId"

suite.api.On("LogError", mock.AnythingOfType("string")).Return(nil)
suite.api.On("LogWarn", mock.AnythingOfType("string"),
"userID",
"karynaId",
"error",
mock.AnythingOfType("*model.AppError"),
).Return(nil)
suite.api.On("LogInfo", mock.AnythingOfType("string")).Return(nil)

p := New()
Expand Down