2017-12-19 17:00:44 +00:00
|
|
|
// Copyright 2017 Andrew Morgan <andrew@amorgan.xyz>
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
2018-02-08 11:02:48 +00:00
|
|
|
"fmt"
|
2017-12-19 17:00:44 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
2018-02-08 11:02:48 +00:00
|
|
|
"regexp"
|
|
|
|
"strings"
|
2017-12-19 17:00:44 +00:00
|
|
|
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ApplicationServiceNamespace is the namespace that a specific application
|
|
|
|
// service has management over.
|
|
|
|
type ApplicationServiceNamespace struct {
|
|
|
|
// Whether or not the namespace is managed solely by this application service
|
|
|
|
Exclusive bool `yaml:"exclusive"`
|
|
|
|
// A regex pattern that represents the namespace
|
|
|
|
Regex string `yaml:"regex"`
|
2018-02-08 11:02:48 +00:00
|
|
|
// Regex object representing our pattern. Saves having to recompile every time
|
|
|
|
RegexpObject *regexp.Regexp
|
2017-12-19 17:00:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ApplicationService represents a Matrix application service.
|
|
|
|
// https://matrix.org/docs/spec/application_service/unstable.html
|
|
|
|
type ApplicationService struct {
|
|
|
|
// User-defined, unique, persistent ID of the application service
|
|
|
|
ID string `yaml:"id"`
|
|
|
|
// Base URL of the application service
|
|
|
|
URL string `yaml:"url"`
|
|
|
|
// Application service token provided in requests to a homeserver
|
|
|
|
ASToken string `yaml:"as_token"`
|
|
|
|
// Homeserver token provided in requests to an application service
|
|
|
|
HSToken string `yaml:"hs_token"`
|
|
|
|
// Localpart of application service user
|
|
|
|
SenderLocalpart string `yaml:"sender_localpart"`
|
|
|
|
// Information about an application service's namespaces
|
2018-02-08 11:02:48 +00:00
|
|
|
NamespaceMap map[string][]ApplicationServiceNamespace `yaml:"namespaces"`
|
2017-12-19 17:00:44 +00:00
|
|
|
}
|
|
|
|
|
2018-02-08 11:02:48 +00:00
|
|
|
// loadAppservices iterates through all application service config files
|
|
|
|
// and loads their data into the config object for later access.
|
2017-12-19 17:00:44 +00:00
|
|
|
func loadAppservices(config *Dendrite) error {
|
|
|
|
for _, configPath := range config.ApplicationServices.ConfigFiles {
|
|
|
|
// Create a new application service
|
|
|
|
var appservice ApplicationService
|
|
|
|
|
|
|
|
// Create an absolute path from a potentially relative path
|
|
|
|
absPath, err := filepath.Abs(configPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the application service's config file
|
|
|
|
configData, err := ioutil.ReadFile(absPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the config data into our struct
|
|
|
|
if err = yaml.UnmarshalStrict(configData, &appservice); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append the parsed application service to the global config
|
|
|
|
config.Derived.ApplicationServices = append(
|
|
|
|
config.Derived.ApplicationServices, appservice)
|
|
|
|
}
|
|
|
|
|
2018-02-08 11:02:48 +00:00
|
|
|
// Check for any errors in the loaded application services
|
|
|
|
return checkErrors(config)
|
|
|
|
}
|
|
|
|
|
|
|
|
// setupRegexps will create regex objects for exclusive and non-exclusive
|
|
|
|
// usernames, aliases and rooms of all application services, so that other
|
|
|
|
// methods can quickly check if a particular string matches any of them.
|
|
|
|
func setupRegexps(cfg *Dendrite) {
|
|
|
|
// Combine all exclusive namespaces for later string checking
|
|
|
|
var exclusiveUsernameStrings, exclusiveAliasStrings, exclusiveRoomStrings []string
|
|
|
|
|
|
|
|
// If an application service's regex is marked as exclusive, add
|
|
|
|
// it's contents to the overall exlusive regex string
|
|
|
|
for _, appservice := range cfg.Derived.ApplicationServices {
|
|
|
|
for key, namespaceSlice := range appservice.NamespaceMap {
|
|
|
|
switch key {
|
|
|
|
case "users":
|
|
|
|
appendExclusiveNamespaceRegexs(&exclusiveUsernameStrings, namespaceSlice)
|
|
|
|
case "aliases":
|
|
|
|
appendExclusiveNamespaceRegexs(&exclusiveAliasStrings, namespaceSlice)
|
|
|
|
case "rooms":
|
|
|
|
appendExclusiveNamespaceRegexs(&exclusiveRoomStrings, namespaceSlice)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Join the regexes together into one big regex.
|
|
|
|
// i.e. "app1.*", "app2.*" -> "(app1.*)|(app2.*)"
|
|
|
|
// Later we can check if a username or some other string matches any exclusive
|
|
|
|
// regex and deny access if it isn't from an application service
|
|
|
|
exclusiveUsernames := strings.Join(exclusiveUsernameStrings, "|")
|
|
|
|
|
2018-02-27 11:42:10 +00:00
|
|
|
// If there are no exclusive username regexes, compile string so that it
|
|
|
|
// will not match any valid usernames
|
|
|
|
if exclusiveUsernames == "" {
|
|
|
|
exclusiveUsernames = "^$"
|
|
|
|
}
|
|
|
|
|
2018-02-08 11:02:48 +00:00
|
|
|
// TODO: Aliases and rooms. Needed?
|
|
|
|
//exclusiveAliases := strings.Join(exclusiveAliasStrings, "|")
|
|
|
|
//exclusiveRooms := strings.Join(exclusiveRoomStrings, "|")
|
|
|
|
|
|
|
|
cfg.Derived.ExclusiveApplicationServicesUsernameRegexp, _ = regexp.Compile(exclusiveUsernames)
|
|
|
|
}
|
|
|
|
|
|
|
|
// concatenateExclusiveNamespaces takes a slice of strings and a slice of
|
|
|
|
// namespaces and will append the regexes of only the exclusive namespaces
|
|
|
|
// into the string slice
|
|
|
|
func appendExclusiveNamespaceRegexs(
|
|
|
|
exclusiveStrings *[]string, namespaces []ApplicationServiceNamespace,
|
|
|
|
) {
|
|
|
|
for _, namespace := range namespaces {
|
|
|
|
if namespace.Exclusive {
|
|
|
|
// We append parenthesis to later separate each regex when we compile
|
|
|
|
// i.e. "app1.*", "app2.*" -> "(app1.*)|(app2.*)"
|
|
|
|
*exclusiveStrings = append(*exclusiveStrings, "("+namespace.Regex+")")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compile this regex into a Regexp object for later use
|
|
|
|
namespace.RegexpObject, _ = regexp.Compile(namespace.Regex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkErrors checks for any configuration errors amongst the loaded
|
|
|
|
// application services according to the application service spec.
|
|
|
|
func checkErrors(config *Dendrite) error {
|
|
|
|
var idMap = make(map[string]bool)
|
|
|
|
var tokenMap = make(map[string]bool)
|
|
|
|
|
|
|
|
// Check that no two application services have the same as_token or id
|
|
|
|
for _, appservice := range config.Derived.ApplicationServices {
|
|
|
|
// Check if we've already seen this ID
|
|
|
|
if idMap[appservice.ID] {
|
|
|
|
return Error{[]string{fmt.Sprintf(
|
|
|
|
"Application Service ID %s must be unique", appservice.ID,
|
|
|
|
)}}
|
|
|
|
}
|
|
|
|
if tokenMap[appservice.ASToken] {
|
|
|
|
return Error{[]string{fmt.Sprintf(
|
|
|
|
"Application Service Token %s must be unique", appservice.ASToken,
|
|
|
|
)}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the id/token to their respective maps if we haven't already
|
|
|
|
// seen them.
|
|
|
|
idMap[appservice.ID] = true
|
|
|
|
tokenMap[appservice.ID] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that namespace(s) are valid regex
|
|
|
|
for _, appservice := range config.Derived.ApplicationServices {
|
|
|
|
for _, namespaceSlice := range appservice.NamespaceMap {
|
|
|
|
for _, namespace := range namespaceSlice {
|
|
|
|
if !IsValidRegex(namespace.Regex) {
|
|
|
|
return Error{[]string{fmt.Sprintf(
|
|
|
|
"Invalid regex string for Application Service %s", appservice.ID,
|
|
|
|
)}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setupRegexps(config)
|
|
|
|
|
2017-12-19 17:00:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
2018-02-08 11:02:48 +00:00
|
|
|
|
|
|
|
// IsValidRegex returns true or false based on whether the
|
|
|
|
// given string is valid regex or not
|
|
|
|
func IsValidRegex(regexString string) bool {
|
|
|
|
_, err := regexp.Compile(regexString)
|
|
|
|
|
|
|
|
return err == nil
|
|
|
|
}
|