Merge branch 'user-whitelisting' into 'main'
Add support for configuring user whitelisting See merge request etke.cc/postmoogle!20
This commit is contained in:
@@ -83,6 +83,7 @@ issues:
|
|||||||
- path: _test\.go
|
- path: _test\.go
|
||||||
linters:
|
linters:
|
||||||
- gocyclo
|
- gocyclo
|
||||||
|
- gocognit
|
||||||
- errcheck
|
- errcheck
|
||||||
- dupl
|
- dupl
|
||||||
- gosec
|
- gosec
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ env vars
|
|||||||
* **POSTMOOGLE_DB_DSN** - database connection string
|
* **POSTMOOGLE_DB_DSN** - database connection string
|
||||||
* **POSTMOOGLE_DB_DIALECT** - database dialect (postgres, sqlite3)
|
* **POSTMOOGLE_DB_DIALECT** - database dialect (postgres, sqlite3)
|
||||||
* **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes
|
* **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes
|
||||||
|
* **POSTMOOGLE_USERS** - a space-separated list of whitelisted users allowed to use the bridge. If not defined, everyone is allowed. Example rule: `@someone:example.com @another:example.com @bot.*:example.com @*:another.com`
|
||||||
|
|
||||||
You can find default values in [config/defaults.go](config/defaults.go)
|
You can find default values in [config/defaults.go](config/defaults.go)
|
||||||
|
|
||||||
|
|||||||
21
bot/bot.go
21
bot/bot.go
@@ -3,6 +3,7 @@ package bot
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
@@ -19,6 +20,7 @@ type Bot struct {
|
|||||||
federation bool
|
federation bool
|
||||||
prefix string
|
prefix string
|
||||||
domain string
|
domain string
|
||||||
|
allowedUsers []*regexp.Regexp
|
||||||
rooms sync.Map
|
rooms sync.Map
|
||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
lp *linkpearl.Linkpearl
|
lp *linkpearl.Linkpearl
|
||||||
@@ -27,16 +29,17 @@ type Bot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new matrix bot
|
// New creates a new matrix bot
|
||||||
func New(lp *linkpearl.Linkpearl, log *logger.Logger, prefix, domain string, noowner, federation bool) *Bot {
|
func New(lp *linkpearl.Linkpearl, log *logger.Logger, prefix, domain string, noowner, federation bool, allowedUsers []*regexp.Regexp) *Bot {
|
||||||
return &Bot{
|
return &Bot{
|
||||||
noowner: noowner,
|
noowner: noowner,
|
||||||
federation: federation,
|
federation: federation,
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
rooms: sync.Map{},
|
allowedUsers: allowedUsers,
|
||||||
log: log,
|
rooms: sync.Map{},
|
||||||
lp: lp,
|
log: log,
|
||||||
mu: map[id.RoomID]*sync.Mutex{},
|
lp: lp,
|
||||||
|
mu: map[id.RoomID]*sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ func (b *Bot) runStop(ctx context.Context, checkAllowed bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkAllowed && !cfg.Allowed(b.noowner, evt.Sender) {
|
if checkAllowed && !b.Allowed(evt.Sender, cfg) {
|
||||||
b.Notice(ctx, evt.RoomID, "you don't have permission to do that")
|
b.Notice(ctx, evt.RoomID, "you don't have permission to do that")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.Allowed(b.noowner, evt.Sender) {
|
if !b.Allowed(evt.Sender, cfg) {
|
||||||
b.Notice(ctx, evt.RoomID, "you don't have permission to do that, kupo")
|
b.Notice(ctx, evt.RoomID, "you don't have permission to do that, kupo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,20 +19,6 @@ type settingsOld struct {
|
|||||||
NoSender bool
|
NoSender bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allowed checks if change is allowed
|
|
||||||
func (s settings) Allowed(noowner bool, userID id.UserID) bool {
|
|
||||||
if noowner {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
owner := s.Owner()
|
|
||||||
if owner == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return owner == userID.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get option
|
// Get option
|
||||||
func (s settings) Get(key string) string {
|
func (s settings) Get(key string) string {
|
||||||
value := s[strings.ToLower(strings.TrimSpace(key))]
|
value := s[strings.ToLower(strings.TrimSpace(key))]
|
||||||
@@ -118,3 +104,21 @@ func (b *Bot) getSettings(roomID id.RoomID) (settings, error) {
|
|||||||
func (b *Bot) setSettings(roomID id.RoomID, cfg settings) error {
|
func (b *Bot) setSettings(roomID id.RoomID, cfg settings) error {
|
||||||
return utils.UnwrapError(b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg))
|
return utils.UnwrapError(b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allowed checks if change is allowed
|
||||||
|
func (b *Bot) Allowed(userID id.UserID, cfg settings) bool {
|
||||||
|
if !utils.Match(userID.String(), b.allowedUsers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.noowner {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
owner := cfg.Owner()
|
||||||
|
if owner == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return owner == userID.String()
|
||||||
|
}
|
||||||
|
|||||||
10
cmd/cmd.go
10
cmd/cmd.go
@@ -26,7 +26,13 @@ var (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
quit := make(chan struct{})
|
quit := make(chan struct{})
|
||||||
cfg := config.New()
|
|
||||||
|
cfg, err := config.New()
|
||||||
|
if err != nil {
|
||||||
|
log = logger.New("postmoogle.", "info")
|
||||||
|
log.Fatal("cannot read config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
log = logger.New("postmoogle.", cfg.LogLevel)
|
log = logger.New("postmoogle.", cfg.LogLevel)
|
||||||
|
|
||||||
log.Info("#############################")
|
log.Info("#############################")
|
||||||
@@ -81,7 +87,7 @@ func initBot(cfg *config.Config) {
|
|||||||
// nolint // Fatal = panic, not os.Exit()
|
// nolint // Fatal = panic, not os.Exit()
|
||||||
log.Fatal("cannot initialize matrix bot: %v", err)
|
log.Fatal("cannot initialize matrix bot: %v", err)
|
||||||
}
|
}
|
||||||
mxb = bot.New(lp, mxlog, cfg.Prefix, cfg.Domain, cfg.NoOwner, cfg.Federation)
|
mxb = bot.New(lp, mxlog, cfg.Prefix, cfg.Domain, cfg.NoOwner, cfg.Federation, cfg.Users)
|
||||||
log.Debug("bot has been created")
|
log.Debug("bot has been created")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,29 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"gitlab.com/etke.cc/go/env"
|
"gitlab.com/etke.cc/go/env"
|
||||||
|
|
||||||
|
"gitlab.com/etke.cc/postmoogle/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const prefix = "postmoogle"
|
const prefix = "postmoogle"
|
||||||
|
|
||||||
// New config
|
// New config
|
||||||
func New() *Config {
|
func New() (*Config, error) {
|
||||||
env.SetPrefix(prefix)
|
env.SetPrefix(prefix)
|
||||||
|
|
||||||
|
mxidPatterns := env.Slice("users")
|
||||||
|
regexPatterns, err := utils.WildcardMXIDsToRegexes(mxidPatterns)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to convert wildcard user patterns (`%s`) to regular expression: %s",
|
||||||
|
mxidPatterns,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Homeserver: env.String("homeserver", defaultConfig.Homeserver),
|
Homeserver: env.String("homeserver", defaultConfig.Homeserver),
|
||||||
Login: env.String("login", defaultConfig.Login),
|
Login: env.String("login", defaultConfig.Login),
|
||||||
@@ -21,6 +36,7 @@ func New() *Config {
|
|||||||
Federation: env.Bool("federation"),
|
Federation: env.Bool("federation"),
|
||||||
MaxSize: env.Int("maxsize", defaultConfig.MaxSize),
|
MaxSize: env.Int("maxsize", defaultConfig.MaxSize),
|
||||||
StatusMsg: env.String("statusmsg", defaultConfig.StatusMsg),
|
StatusMsg: env.String("statusmsg", defaultConfig.StatusMsg),
|
||||||
|
Users: *regexPatterns,
|
||||||
Sentry: Sentry{
|
Sentry: Sentry{
|
||||||
DSN: env.String("sentry.dsn", defaultConfig.Sentry.DSN),
|
DSN: env.String("sentry.dsn", defaultConfig.Sentry.DSN),
|
||||||
},
|
},
|
||||||
@@ -31,5 +47,5 @@ func New() *Config {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
// Config of Postmoogle
|
// Config of Postmoogle
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Homeserver url
|
// Homeserver url
|
||||||
@@ -26,6 +28,8 @@ type Config struct {
|
|||||||
MaxSize int
|
MaxSize int
|
||||||
// StatusMsg of the bot
|
// StatusMsg of the bot
|
||||||
StatusMsg string
|
StatusMsg string
|
||||||
|
// Users holds list of allowed users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = *
|
||||||
|
Users []*regexp.Regexp
|
||||||
|
|
||||||
// DB config
|
// DB config
|
||||||
DB DB
|
DB DB
|
||||||
|
|||||||
109
utils/user.go
Normal file
109
utils/user.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WildcardMXIDsToRegexes converts a list of wildcard patterns to a list of regular expressions
|
||||||
|
func WildcardMXIDsToRegexes(wildCardPatterns []string) (*[]*regexp.Regexp, error) {
|
||||||
|
regexPatterns := make([]*regexp.Regexp, len(wildCardPatterns))
|
||||||
|
|
||||||
|
for idx, wildCardPattern := range wildCardPatterns {
|
||||||
|
regex, err := parseMXIDWildcard(wildCardPattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse allowed user rule `%s`: %s", wildCardPattern, err)
|
||||||
|
}
|
||||||
|
regexPatterns[idx] = regex
|
||||||
|
}
|
||||||
|
|
||||||
|
return ®exPatterns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match tells if the given user id is allowed to use the bot, according to the given whitelist
|
||||||
|
func Match(userID string, allowed []*regexp.Regexp) bool {
|
||||||
|
// No whitelisted users means everyone is whitelisted
|
||||||
|
if len(allowed) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, regex := range allowed {
|
||||||
|
if regex.MatchString(userID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMXIDWildcard parses a user whitelisting wildcard rule and returns a regular expression which corresponds to it
|
||||||
|
//
|
||||||
|
// Example conversion: `@bot.*.something:*.example.com` -> `^bot\.([^:@]*)\.something:([^:@]*)\.example.com$`
|
||||||
|
// Example of recognized wildcard patterns: `@someone:example.com`, `@*:example.com`, `@bot.*:example.com`, `@someone:*`, `@someone:*.example.com`
|
||||||
|
//
|
||||||
|
// The `*` wildcard character is normally interpretted as "a number of literal characters or an empty string".
|
||||||
|
// Our implementation below matches this (yielding `([^:@])*`), which could provide a slightly suboptimal regex in these cases:
|
||||||
|
// - `@*:example.com` -> `^@([^:@])*:example\.com$`, although `^@([^:@])+:example\.com$` would be preferable
|
||||||
|
// - `@someone:*` -> `@someone:([^:@])*$`, although `@someone:([^:@])+$` would be preferable
|
||||||
|
// When it's a bare wildcard (`*`, instead of `*.example.com`) we likely prefer to yield a regex that matches **at least one character**.
|
||||||
|
// This probably doesn't matter because mxids that we'll match against are all valid and fully complete.
|
||||||
|
func parseMXIDWildcard(wildCardRule string) (*regexp.Regexp, error) {
|
||||||
|
if !strings.HasPrefix(wildCardRule, "@") {
|
||||||
|
return nil, fmt.Errorf("rules need to be fully-qualified, starting with a @")
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingRule := wildCardRule[1:]
|
||||||
|
if strings.Contains(remainingRule, "@") {
|
||||||
|
return nil, fmt.Errorf("rules cannot contain more than one @")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(remainingRule, ":")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("expected exactly 2 parts in the rule, separated by `:`")
|
||||||
|
}
|
||||||
|
|
||||||
|
localPart := parts[0]
|
||||||
|
localPartPattern, err := getRegexPatternForPart(localPart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert local part `%s` to regex: %s", localPart, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
domainPart := parts[1]
|
||||||
|
domainPartPattern, err := getRegexPatternForPart(domainPart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert domain part `%s` to regex: %s", domainPart, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPattern := fmt.Sprintf("^@%s:%s$", localPartPattern, domainPartPattern)
|
||||||
|
|
||||||
|
regex, err := regexp.Compile(finalPattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compile regex `%s`: %s", finalPattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return regex, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRegexPatternForPart(part string) (string, error) {
|
||||||
|
if part == "" {
|
||||||
|
return "", fmt.Errorf("rejecting empty part")
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern strings.Builder
|
||||||
|
for _, rune := range part {
|
||||||
|
if rune == '*' {
|
||||||
|
// We match everything except for `:` and `@`, because that would be an invalid MXID anyway.
|
||||||
|
//
|
||||||
|
// If the whole part is `*` (only) instead of merely containing `*` within it,
|
||||||
|
// we may also consider replacing it with `([^:@]+)` (+, instead of *).
|
||||||
|
// See parseMXIDWildcard for notes about this.
|
||||||
|
pattern.WriteString("([^:@]*)")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern.WriteString(regexp.QuoteMeta(string(rune)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern.String(), nil
|
||||||
|
}
|
||||||
221
utils/user_test.go
Normal file
221
utils/user_test.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRuleToRegex(t *testing.T) {
|
||||||
|
type testDataDefinition struct {
|
||||||
|
name string
|
||||||
|
checkedValue string
|
||||||
|
expectedResult string
|
||||||
|
expectedError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testDataDefinition{
|
||||||
|
{
|
||||||
|
name: "simple pattern without wildcards succeeds",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
expectedResult: `^@someone:example\.com$`,
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with wildcard as the whole local part succeeds",
|
||||||
|
checkedValue: "@*:example.com",
|
||||||
|
expectedResult: `^@([^:@]*):example\.com$`,
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with wildcard within the local part succeeds",
|
||||||
|
checkedValue: "@bot.*.something:example.com",
|
||||||
|
expectedResult: `^@bot\.([^:@]*)\.something:example\.com$`,
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with wildcard as the whole domain part succeeds",
|
||||||
|
checkedValue: "@someone:*",
|
||||||
|
expectedResult: `^@someone:([^:@]*)$`,
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with wildcard within the domain part succeeds",
|
||||||
|
checkedValue: "@someone:*.organization.com",
|
||||||
|
expectedResult: `^@someone:([^:@]*)\.organization\.com$`,
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with wildcard in both parts succeeds",
|
||||||
|
checkedValue: "@*:*",
|
||||||
|
expectedResult: `^@([^:@]*):([^:@]*)$`,
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern that does not appear fully-qualified fails",
|
||||||
|
checkedValue: "someone:example.com",
|
||||||
|
expectedResult: ``,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern that does not appear fully-qualified fails",
|
||||||
|
checkedValue: "@someone",
|
||||||
|
expectedResult: ``,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with empty domain part fails",
|
||||||
|
checkedValue: "@someone:",
|
||||||
|
expectedResult: ``,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with empty local part fails",
|
||||||
|
checkedValue: "@:example.com",
|
||||||
|
expectedResult: ``,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with multiple @ fails",
|
||||||
|
checkedValue: "@someone@someone:example.com",
|
||||||
|
expectedResult: ``,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pattern with multiple : fails",
|
||||||
|
checkedValue: "@someone:someone:example.com",
|
||||||
|
expectedResult: ``,
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testData := range tests {
|
||||||
|
func(testData testDataDefinition) {
|
||||||
|
t.Run(testData.name, func(t *testing.T) {
|
||||||
|
actualResult, err := parseMXIDWildcard(testData.checkedValue)
|
||||||
|
|
||||||
|
if testData.expectedError {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("expected an error, but did not get one")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("did not expect an error, but got one: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualResult.String() == testData.expectedResult {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf(
|
||||||
|
"Expected `%s` to yield `%s`, not `%s`",
|
||||||
|
testData.checkedValue,
|
||||||
|
testData.expectedResult,
|
||||||
|
actualResult.String(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}(testData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatch(t *testing.T) {
|
||||||
|
type testDataDefinition struct {
|
||||||
|
name string
|
||||||
|
checkedValue string
|
||||||
|
allowedUsers []string
|
||||||
|
expectedResult bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testDataDefinition{
|
||||||
|
{
|
||||||
|
name: "Empty allowed users allows anyone",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Direct full mxid match is allowed",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{"@someone:example.com"},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Direct full mxid match later on is allowed",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{"@another:example.com", "@someone:example.com"},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No mxid match is not allowed",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{"@another:example.com"},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid localpart only wildcard match is allowed",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{"@*:example.com"},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid localpart with wildcard match is allowed",
|
||||||
|
checkedValue: "@bot.abc:example.com",
|
||||||
|
allowedUsers: []string{"@bot.*:example.com"},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid localpart with wildcard match is not allowed when it does not match",
|
||||||
|
checkedValue: "@bot.abc:example.com",
|
||||||
|
allowedUsers: []string{"@employee.*:example.com"},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid localpart wildcard for another domain is not allowed",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{"@*:another.com"},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid domainpart with only wildcard match is allowed",
|
||||||
|
checkedValue: "@someone:example.com",
|
||||||
|
allowedUsers: []string{"@someone:*"},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid domainpart with wildcard match is allowed",
|
||||||
|
checkedValue: "@someone:example.organization.com",
|
||||||
|
allowedUsers: []string{"@someone:*.organization.com"},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mxid domainpart with wildcard match is not allowed when it does not match",
|
||||||
|
checkedValue: "@someone:example.another.com",
|
||||||
|
allowedUsers: []string{"@someone:*.organization.com"},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testData := range tests {
|
||||||
|
func(testData testDataDefinition) {
|
||||||
|
t.Run(testData.name, func(t *testing.T) {
|
||||||
|
allowedUserRegexes, err := WildcardMXIDsToRegexes(testData.allowedUsers)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualResult := Match(testData.checkedValue, *allowedUserRegexes)
|
||||||
|
|
||||||
|
if actualResult == testData.expectedResult {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf(
|
||||||
|
"Expected `%s` compared against `%v` to yield `%v`, not `%v`",
|
||||||
|
testData.checkedValue,
|
||||||
|
testData.allowedUsers,
|
||||||
|
testData.expectedResult,
|
||||||
|
actualResult,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}(testData)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user