diff --git a/config.sample.yaml b/config.sample.yaml new file mode 100644 index 0000000..5428c6f --- /dev/null +++ b/config.sample.yaml @@ -0,0 +1,106 @@ +# Go-NEB Configuration File +# +# This file provides an alternative way to configure Go-NEB which does not involve HTTP APIs. +# +# This file can be supplied to go-neb by the environment variable `CONFIG_FILE=config.yaml`. +# It will force Go-NEB to operate in "config" mode. This means: +# - Go-NEB will ONLY use the data contained inside this file. +# - All of Go-NEB's /admin HTTP listeners will be disabled. You will be unable to add new services at runtime. +# - The environment variable `DATABASE_URL` will be ignored and an in-memory database will be used instead. +# +# This file is broken down into 4 sections which matches the following HTTP APIs: +# - /configureClient +# - /configureAuthRealm +# - /configureService +# - /requestAuthSession (redirects not supported) + +# The list of clients which Go-NEB is aware of. +# Delete or modify this list as appropriate. +# See the docs for /configureClient for the full list of options. +clients: + - UserID: "@goneb:localhost" + AccessToken: "MDASDASJDIASDJASDAFGFRGER" + HomeserverURL: "http://localhost:8008" + Sync: true + AutoJoinRooms: true + DisplayName: "Go-NEB!" + + - UserID: "@another_goneb:localhost" + AccessToken: "MDASDASJDIASDJASDAFGFRGER" + HomeserverURL: "http://localhost:8008" + Sync: false + AutoJoinRooms: false + DisplayName: "Go-NEB!" + +# The list of realms which Go-NEB is aware of. +# Delete or modify this list as appropriate. +# See the docs for /configureAuthRealm for the full list of options. +realms: + - ID: "github_realm" + Type: "github" + Config: {} # No need for client ID or Secret as Go-NEB isn't generating OAuth URLs + +# The list of *authenticated* sessions which Go-NEB is aware of. +# Delete or modify this list as appropriate. +# The full list of options are shown below: there is no single HTTP endpoint +# which maps to this section. +sessions: + - SessionID: "your_github_session" + RealmID: "github_realm" + UserID: "@YOUR_USER_ID:localhost" + Config: + # Populate these fields by generating a "Personal Access Token" on github.com + AccessToken: "YOUR_GITHUB_ACCESS_TOKEN" + Scopes: "admin:org_hook,admin:repo_hook,repo,user" + + +# The list of services which Go-NEB is aware of. +# Delete or modify this list as appropriate. +# See the docs for /configureService for the full list of options. +services: + - ID: "giphy_service" + Type: "giphy" + UserID: "@goneb:localhost" # requires a Syncing client + Config: + api_key: "qwg4672vsuyfsfe" + + - ID: "guggy_service" + Type: "guggy" + UserID: "@goneb:localhost" # requires a Syncing client + Config: + api_key: "2356saaqfhgfe" + + - ID: "rss_service" + Type: "rssbot" + UserID: "@another_goneb:localhost" + Config: + feeds: + "http://lorem-rss.herokuapp.com/feed?unit=second&interval=60": + rooms: ["!qmElAGdFYCHoCJuaNt:localhost"] + + - ID: "github_cmd_service" + Type: "github" + UserID: "@goneb:localhost" # requires a Syncing client + Config: + RealmID: "github_realm" + + # Make sure your BASE_URL can be accessed by Github! + - ID: "github_webhook_service" + Type: "github-webhook" + UserID: "@another_goneb:localhost" + Config: + RealmID: "github_realm" + ClientUserID: "@YOUR_USER_ID:localhost" # needs to be an authenticated user so Go-NEB can create webhooks. + Rooms: + "!someroom:id": + Repos: + "matrix-org/synapse": + Events: ["push", "issues"] + "matrix-org/dendron": + Events: ["pull_request"] + "!anotherroom:id": + Repos: + "matrix-org/synapse": + Events: ["push", "issues"] + "matrix-org/dendron": + Events: ["pull_request"] diff --git a/src/github.com/matrix-org/go-neb/api/api.go b/src/github.com/matrix-org/go-neb/api/api.go new file mode 100644 index 0000000..e003a6a --- /dev/null +++ b/src/github.com/matrix-org/go-neb/api/api.go @@ -0,0 +1,84 @@ +package api + +import ( + "encoding/json" + "errors" + "net/url" +) + +// ConfigureAuthRealmRequest is a request to /configureAuthRealm +type ConfigureAuthRealmRequest struct { + ID string + Type string + Config json.RawMessage +} + +// ConfigureServiceRequest is a request to /configureService +type ConfigureServiceRequest struct { + ID string + Type string + UserID string + Config json.RawMessage +} + +// A ClientConfig is the configuration for a matrix client for a bot to use. It is +// a request to /configureClient +type ClientConfig struct { + UserID string // The matrix UserId to connect with. + HomeserverURL string // A URL with the host and port of the matrix server. E.g. https://matrix.org:8448 + AccessToken string // The matrix access token to authenticate the requests with. + Sync bool // True to start a sync stream for this user + AutoJoinRooms bool // True to automatically join all rooms for this user + DisplayName string // The display name to set for the matrix client +} + +// SessionRequest are usually multi-stage things so this type only exists for the config form +type SessionRequest struct { + SessionID string + RealmID string + UserID string + Config json.RawMessage +} + +// ConfigFile represents config.sample.yaml +type ConfigFile struct { + Clients []ClientConfig + Realms []ConfigureAuthRealmRequest + Services []ConfigureServiceRequest + Sessions []SessionRequest +} + +// Check validates the /configureService request +func (c *ConfigureServiceRequest) Check() error { + if c.ID == "" || c.Type == "" || c.UserID == "" || c.Config == nil { + return errors.New(`Must supply an "ID", a "Type", a "UserID" and a "Config"`) + } + return nil +} + +// Check validates the /configureAuthRealm request +func (c *ConfigureAuthRealmRequest) Check() error { + if c.ID == "" || c.Type == "" || c.Config == nil { + return errors.New(`Must supply a "ID", a "Type" and a "Config"`) + } + return nil +} + +// Check validates the session config request +func (c *SessionRequest) Check() error { + if c.SessionID == "" || c.UserID == "" || c.RealmID == "" || c.Config == nil { + return errors.New(`Must supply a "SessionID", a "RealmID", a "UserID" and a "Config"`) + } + return nil +} + +// Check that the client has the correct fields. +func (c *ClientConfig) Check() error { + if c.UserID == "" || c.HomeserverURL == "" || c.AccessToken == "" { + return errors.New(`Must supply a "UserID", a "HomeserverURL", and an "AccessToken"`) + } + if _, err := url.Parse(c.HomeserverURL); err != nil { + return err + } + return nil +} diff --git a/src/github.com/matrix-org/go-neb/clients/clients.go b/src/github.com/matrix-org/go-neb/clients/clients.go index caab1f4..c6fa205 100644 --- a/src/github.com/matrix-org/go-neb/clients/clients.go +++ b/src/github.com/matrix-org/go-neb/clients/clients.go @@ -2,6 +2,7 @@ package clients import ( log "github.com/Sirupsen/logrus" + "github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/plugin" @@ -64,7 +65,7 @@ func (c *Clients) Client(userID string) (*matrix.Client, error) { } // Update updates the config for a matrix client -func (c *Clients) Update(config types.ClientConfig) (types.ClientConfig, error) { +func (c *Clients) Update(config api.ClientConfig) (api.ClientConfig, error) { _, old, err := c.updateClientInDB(config) return old.config, err } @@ -86,7 +87,7 @@ func (c *Clients) Start() error { } type clientEntry struct { - config types.ClientConfig + config api.ClientConfig client *matrix.Client } @@ -123,7 +124,7 @@ func (c *Clients) loadClientFromDB(userID string) (entry clientEntry, err error) return } -func (c *Clients) updateClientInDB(newConfig types.ClientConfig) (new clientEntry, old clientEntry, err error) { +func (c *Clients) updateClientInDB(newConfig api.ClientConfig) (new clientEntry, old clientEntry, err error) { c.dbMutex.Lock() defer c.dbMutex.Unlock() @@ -231,7 +232,7 @@ func (c *Clients) onRoomMemberEvent(client *matrix.Client, event *matrix.Event) } } -func (c *Clients) newClient(config types.ClientConfig) (*matrix.Client, error) { +func (c *Clients) newClient(config api.ClientConfig) (*matrix.Client, error) { homeserverURL, err := url.Parse(config.HomeserverURL) if err != nil { return nil, err diff --git a/src/github.com/matrix-org/go-neb/database/db.go b/src/github.com/matrix-org/go-neb/database/db.go index 1aaf932..db23b6d 100644 --- a/src/github.com/matrix-org/go-neb/database/db.go +++ b/src/github.com/matrix-org/go-neb/database/db.go @@ -2,6 +2,9 @@ package database import ( "database/sql" + "encoding/json" + "fmt" + "github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/types" "time" ) @@ -48,7 +51,7 @@ func Open(databaseType, databaseURL string) (serviceDB *ServiceDB, err error) { // StoreMatrixClientConfig stores the Matrix client config for a bot service. // If a config already exists then it will be updated, otherwise a new config // will be inserted. The previous config is returned. -func (d *ServiceDB) StoreMatrixClientConfig(config types.ClientConfig) (oldConfig types.ClientConfig, err error) { +func (d *ServiceDB) StoreMatrixClientConfig(config api.ClientConfig) (oldConfig api.ClientConfig, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { oldConfig, err = selectMatrixClientConfigTxn(txn, config.UserID) now := time.Now() @@ -64,7 +67,7 @@ func (d *ServiceDB) StoreMatrixClientConfig(config types.ClientConfig) (oldConfi } // LoadMatrixClientConfigs loads all Matrix client configs from the database. -func (d *ServiceDB) LoadMatrixClientConfigs() (configs []types.ClientConfig, err error) { +func (d *ServiceDB) LoadMatrixClientConfigs() (configs []api.ClientConfig, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { configs, err = selectMatrixClientConfigsTxn(txn) return err @@ -74,7 +77,7 @@ func (d *ServiceDB) LoadMatrixClientConfigs() (configs []types.ClientConfig, err // LoadMatrixClientConfig loads a Matrix client config from the database. // Returns sql.ErrNoRows if the client isn't in the database. -func (d *ServiceDB) LoadMatrixClientConfig(userID string) (config types.ClientConfig, err error) { +func (d *ServiceDB) LoadMatrixClientConfig(userID string) (config api.ClientConfig, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { config, err = selectMatrixClientConfigTxn(txn, userID) return err @@ -272,6 +275,58 @@ func (d *ServiceDB) StoreBotOptions(opts types.BotOptions) (oldOpts types.BotOpt return } +// InsertFromConfig inserts entries from the config file into the database. This only really +// makes sense for in-memory databases. +func (d *ServiceDB) InsertFromConfig(cfg *api.ConfigFile) error { + // Insert clients + for _, cli := range cfg.Clients { + if _, err := d.StoreMatrixClientConfig(cli); err != nil { + return err + } + } + + // Keep a map of realms for inserting sessions + realms := map[string]types.AuthRealm{} // by realm ID + + // Insert realms + for _, r := range cfg.Realms { + if err := r.Check(); err != nil { + return err + } + realm, err := types.CreateAuthRealm(r.ID, r.Type, r.Config) + if err != nil { + return err + } + if _, err := d.StoreAuthRealm(realm); err != nil { + return err + } + realms[realm.ID()] = realm + } + + // Insert sessions + for _, s := range cfg.Sessions { + if err := s.Check(); err != nil { + return err + } + r := realms[s.RealmID] + if r == nil { + return fmt.Errorf("Session %s specifies an unknown realm ID %s", s.SessionID, s.RealmID) + } + session := r.AuthSession(s.SessionID, s.UserID, s.RealmID) + // dump the raw JSON config directly into the session. This is what + // selectAuthSessionByUserTxn does. + if err := json.Unmarshal(s.Config, session); err != nil { + return err + } + if _, err := d.StoreAuthSession(session); err != nil { + return err + } + } + + // Do not insert services yet, they require more work to set up. + return nil +} + func runTransaction(db *sql.DB, fn func(txn *sql.Tx) error) (err error) { txn, err := db.Begin() if err != nil { diff --git a/src/github.com/matrix-org/go-neb/database/schema.go b/src/github.com/matrix-org/go-neb/database/schema.go index d8ae5cd..7e3a30f 100644 --- a/src/github.com/matrix-org/go-neb/database/schema.go +++ b/src/github.com/matrix-org/go-neb/database/schema.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/types" "time" ) @@ -64,7 +65,7 @@ const selectMatrixClientConfigSQL = ` SELECT client_json FROM matrix_clients WHERE user_id = $1 ` -func selectMatrixClientConfigTxn(txn *sql.Tx, userID string) (config types.ClientConfig, err error) { +func selectMatrixClientConfigTxn(txn *sql.Tx, userID string) (config api.ClientConfig, err error) { var configJSON []byte err = txn.QueryRow(selectMatrixClientConfigSQL, userID).Scan(&configJSON) if err != nil { @@ -78,14 +79,14 @@ const selectMatrixClientConfigsSQL = ` SELECT client_json FROM matrix_clients ` -func selectMatrixClientConfigsTxn(txn *sql.Tx) (configs []types.ClientConfig, err error) { +func selectMatrixClientConfigsTxn(txn *sql.Tx) (configs []api.ClientConfig, err error) { rows, err := txn.Query(selectMatrixClientConfigsSQL) if err != nil { return } defer rows.Close() for rows.Next() { - var config types.ClientConfig + var config api.ClientConfig var configJSON []byte if err = rows.Scan(&configJSON); err != nil { return @@ -104,7 +105,7 @@ INSERT INTO matrix_clients( ) VALUES ($1, $2, '', $3, $4) ` -func insertMatrixClientConfigTxn(txn *sql.Tx, now time.Time, config types.ClientConfig) error { +func insertMatrixClientConfigTxn(txn *sql.Tx, now time.Time, config api.ClientConfig) error { t := now.UnixNano() / 1000000 configJSON, err := json.Marshal(&config) if err != nil { @@ -119,7 +120,7 @@ UPDATE matrix_clients SET client_json = $1, time_updated_ms = $2 WHERE user_id = $3 ` -func updateMatrixClientConfigTxn(txn *sql.Tx, now time.Time, config types.ClientConfig) error { +func updateMatrixClientConfigTxn(txn *sql.Tx, now time.Time, config api.ClientConfig) error { t := now.UnixNano() / 1000000 configJSON, err := json.Marshal(&config) if err != nil { diff --git a/src/github.com/matrix-org/go-neb/goneb.go b/src/github.com/matrix-org/go-neb/goneb.go index d2c6f11..9afaf38 100644 --- a/src/github.com/matrix-org/go-neb/goneb.go +++ b/src/github.com/matrix-org/go-neb/goneb.go @@ -1,8 +1,11 @@ package main import ( + "encoding/json" + "fmt" log "github.com/Sirupsen/logrus" "github.com/matrix-org/dugong" + "github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/clients" "github.com/matrix-org/go-neb/database" _ "github.com/matrix-org/go-neb/metrics" @@ -19,18 +22,130 @@ import ( "github.com/matrix-org/go-neb/types" _ "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus" + "gopkg.in/yaml.v2" + "io/ioutil" "net/http" _ "net/http/pprof" "os" "path/filepath" ) +// loadFromConfig loads a config file and returns a ConfigFile +func loadFromConfig(db *database.ServiceDB, configFilePath string) (*api.ConfigFile, error) { + // ::Horrible hacks ahead:: + // The config is represented as YAML, and we want to convert that into NEB types. + // However, NEB types make liberal use of json.RawMessage which the YAML parser + // doesn't like. We can't implement MarshalYAML/UnmarshalYAML as a custom type easily + // because YAML is insane and supports numbers as keys. The YAML parser therefore has the + // generic form of map[interface{}]interface{} - but the JSON parser doesn't know + // how to parse that. + // + // The hack that follows gets around this by type asserting all parsed YAML keys as + // strings then re-encoding/decoding as JSON. That is: + // YAML bytes -> map[interface]interface -> map[string]interface -> JSON bytes -> NEB types + + // Convert to YAML bytes + contents, err := ioutil.ReadFile(configFilePath) + if err != nil { + return nil, err + } + + // Convert to map[interface]interface + var cfg map[interface{}]interface{} + if err = yaml.Unmarshal(contents, &cfg); err != nil { + return nil, fmt.Errorf("Failed to unmarshal YAML: %s", err) + } + + // Convert to map[string]interface + dict := convertKeysToStrings(cfg) + + // Convert to JSON bytes + b, err := json.Marshal(dict) + if err != nil { + return nil, fmt.Errorf("Failed to marshal config as JSON: %s", err) + } + + // Finally, Convert to NEB types + var c api.ConfigFile + if err := json.Unmarshal(b, &c); err != nil { + return nil, fmt.Errorf("Failed to convert to config file: %s", err) + } + + // sanity check (at least 1 client and 1 service) + if len(c.Clients) == 0 || len(c.Services) == 0 { + return nil, fmt.Errorf("At least 1 client and 1 service must be specified") + } + + return &c, nil +} + +func convertKeysToStrings(iface interface{}) interface{} { + obj, isObj := iface.(map[interface{}]interface{}) + if isObj { + strObj := make(map[string]interface{}) + for k, v := range obj { + strObj[k.(string)] = convertKeysToStrings(v) // handle nested objects + } + return strObj + } + + arr, isArr := iface.([]interface{}) + if isArr { + for i := range arr { + arr[i] = convertKeysToStrings(arr[i]) // handle nested objects + } + return arr + } + return iface // base type like string or number +} + +func insertServicesFromConfig(clis *clients.Clients, serviceReqs []api.ConfigureServiceRequest) error { + for i, s := range serviceReqs { + if err := s.Check(); err != nil { + return fmt.Errorf("config: Service[%d] : %s", i, err) + } + service, err := types.CreateService(s.ID, s.Type, s.UserID, s.Config) + if err != nil { + return fmt.Errorf("config: Service[%d] : %s", i, err) + } + + // Fetch the client for this service and register/poll + c, err := clis.Client(s.UserID) + if err != nil { + return fmt.Errorf("config: Service[%d] : %s", i, err) + } + + if err = service.Register(nil, c); err != nil { + return fmt.Errorf("config: Service[%d] : %s", i, err) + } + if _, err := database.GetServiceDB().StoreService(service); err != nil { + return fmt.Errorf("config: Service[%d] : %s", i, err) + } + service.PostRegister(nil) + } + return nil +} + +func loadDatabase(databaseType, databaseURL, configYAML string) (*database.ServiceDB, error) { + if configYAML != "" { + databaseType = "sqlite3" + databaseURL = ":memory:?_busy_timeout=5000" + } + + db, err := database.Open(databaseType, databaseURL) + if err == nil { + database.SetServiceDB(db) // set singleton + } + return db, err +} + func main() { bindAddress := os.Getenv("BIND_ADDRESS") databaseType := os.Getenv("DATABASE_TYPE") databaseURL := os.Getenv("DATABASE_URL") baseURL := os.Getenv("BASE_URL") logDir := os.Getenv("LOG_DIR") + configYAML := os.Getenv("CONFIG_FILE") if logDir != "" { log.AddHook(dugong.NewFSHook( @@ -41,8 +156,8 @@ func main() { } log.Infof( - "Go-NEB (BIND_ADDRESS=%s DATABASE_TYPE=%s DATABASE_URL=%s BASE_URL=%s LOG_DIR=%s)", - bindAddress, databaseType, databaseURL, baseURL, logDir, + "Go-NEB (BIND_ADDRESS=%s DATABASE_TYPE=%s DATABASE_URL=%s BASE_URL=%s LOG_DIR=%s CONFIG_FILE=%s)", + bindAddress, databaseType, databaseURL, baseURL, logDir, configYAML, ) err := types.BaseURL(baseURL) @@ -50,31 +165,55 @@ func main() { log.WithError(err).Panic("Failed to get base url") } - db, err := database.Open(databaseType, databaseURL) + db, err := loadDatabase(databaseType, databaseURL, configYAML) if err != nil { log.WithError(err).Panic("Failed to open database") } - database.SetServiceDB(db) + + // Populate the database from the config file if one was supplied. + var cfg *api.ConfigFile + if configYAML != "" { + if cfg, err = loadFromConfig(db, configYAML); err != nil { + log.WithError(err).WithField("config_file", configYAML).Panic("Failed to load config file") + } + if err := db.InsertFromConfig(cfg); err != nil { + log.WithError(err).Panic("Failed to persist config data into in-memory DB") + } + log.Info("Inserted ", len(cfg.Clients), " clients") + log.Info("Inserted ", len(cfg.Realms), " realms") + log.Info("Inserted ", len(cfg.Sessions), " sessions") + } clients := clients.New(db) if err := clients.Start(); err != nil { log.WithError(err).Panic("Failed to start up clients") } + // Handle non-admin paths for normal NEB functioning http.Handle("/metrics", prometheus.Handler()) http.Handle("/test", prometheus.InstrumentHandler("test", server.MakeJSONAPI(&heartbeatHandler{}))) - http.Handle("/admin/getService", prometheus.InstrumentHandler("getService", server.MakeJSONAPI(&getServiceHandler{db: db}))) - http.Handle("/admin/getSession", prometheus.InstrumentHandler("getSession", server.MakeJSONAPI(&getSessionHandler{db: db}))) - http.Handle("/admin/configureClient", prometheus.InstrumentHandler("configureClient", server.MakeJSONAPI(&configureClientHandler{db: db, clients: clients}))) - http.Handle("/admin/configureService", prometheus.InstrumentHandler("configureService", server.MakeJSONAPI(newConfigureServiceHandler(db, clients)))) - http.Handle("/admin/configureAuthRealm", prometheus.InstrumentHandler("configureAuthRealm", server.MakeJSONAPI(&configureAuthRealmHandler{db: db}))) - http.Handle("/admin/requestAuthSession", prometheus.InstrumentHandler("requestAuthSession", server.MakeJSONAPI(&requestAuthSessionHandler{db: db}))) - http.Handle("/admin/removeAuthSession", prometheus.InstrumentHandler("removeAuthSession", server.MakeJSONAPI(&removeAuthSessionHandler{db: db}))) wh := &webhookHandler{db: db, clients: clients} http.HandleFunc("/services/hooks/", prometheus.InstrumentHandlerFunc("webhookHandler", wh.handle)) rh := &realmRedirectHandler{db: db} http.HandleFunc("/realms/redirects/", prometheus.InstrumentHandlerFunc("realmRedirectHandler", rh.handle)) + // Read exclusively from the config file if one was supplied. + // Otherwise, add HTTP listeners for new Services/Sessions/Clients/etc. + if configYAML != "" { + if err := insertServicesFromConfig(clients, cfg.Services); err != nil { + log.WithError(err).Panic("Failed to insert services") + } + + log.Info("Inserted ", len(cfg.Services), " services") + } else { + http.Handle("/admin/getService", prometheus.InstrumentHandler("getService", server.MakeJSONAPI(&getServiceHandler{db: db}))) + http.Handle("/admin/getSession", prometheus.InstrumentHandler("getSession", server.MakeJSONAPI(&getSessionHandler{db: db}))) + http.Handle("/admin/configureClient", prometheus.InstrumentHandler("configureClient", server.MakeJSONAPI(&configureClientHandler{db: db, clients: clients}))) + http.Handle("/admin/configureService", prometheus.InstrumentHandler("configureService", server.MakeJSONAPI(newConfigureServiceHandler(db, clients)))) + http.Handle("/admin/configureAuthRealm", prometheus.InstrumentHandler("configureAuthRealm", server.MakeJSONAPI(&configureAuthRealmHandler{db: db}))) + http.Handle("/admin/requestAuthSession", prometheus.InstrumentHandler("requestAuthSession", server.MakeJSONAPI(&requestAuthSessionHandler{db: db}))) + http.Handle("/admin/removeAuthSession", prometheus.InstrumentHandler("removeAuthSession", server.MakeJSONAPI(&removeAuthSessionHandler{db: db}))) + } polling.SetClients(clients) if err := polling.Start(); err != nil { log.WithError(err).Panic("Failed to start polling") diff --git a/src/github.com/matrix-org/go-neb/api.go b/src/github.com/matrix-org/go-neb/handlers.go similarity index 95% rename from src/github.com/matrix-org/go-neb/api.go rename to src/github.com/matrix-org/go-neb/handlers.go index 2284370..2cec684 100644 --- a/src/github.com/matrix-org/go-neb/api.go +++ b/src/github.com/matrix-org/go-neb/handlers.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" log "github.com/Sirupsen/logrus" + "github.com/matrix-org/go-neb/api" "github.com/matrix-org/go-neb/clients" "github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/errors" @@ -136,17 +137,13 @@ func (h *configureAuthRealmHandler) OnIncomingRequest(req *http.Request) (interf if req.Method != "POST" { return nil, &errors.HTTPError{nil, "Unsupported Method", 405} } - var body struct { - ID string - Type string - Config json.RawMessage - } + var body api.ConfigureAuthRealmRequest if err := json.NewDecoder(req.Body).Decode(&body); err != nil { return nil, &errors.HTTPError{err, "Error parsing request JSON", 400} } - if body.ID == "" || body.Type == "" || body.Config == nil { - return nil, &errors.HTTPError{nil, `Must supply a "ID", a "Type" and a "Config"`, 400} + if err := body.Check(); err != nil { + return nil, &errors.HTTPError{err, err.Error(), 400} } realm, err := types.CreateAuthRealm(body.ID, body.Type, body.Config) @@ -223,7 +220,7 @@ func (s *configureClientHandler) OnIncomingRequest(req *http.Request) (interface return nil, &errors.HTTPError{nil, "Unsupported Method", 405} } - var body types.ClientConfig + var body api.ClientConfig if err := json.NewDecoder(req.Body).Decode(&body); err != nil { return nil, &errors.HTTPError{err, "Error parsing request JSON", 400} } @@ -238,8 +235,8 @@ func (s *configureClientHandler) OnIncomingRequest(req *http.Request) (interface } return &struct { - OldClient types.ClientConfig - NewClient types.ClientConfig + OldClient api.ClientConfig + NewClient api.ClientConfig }{oldClient, body}, nil } @@ -334,20 +331,13 @@ func (s *configureServiceHandler) OnIncomingRequest(req *http.Request) (interfac } func (s *configureServiceHandler) createService(req *http.Request) (types.Service, *errors.HTTPError) { - var body struct { - ID string - Type string - UserID string - Config json.RawMessage - } + var body api.ConfigureServiceRequest if err := json.NewDecoder(req.Body).Decode(&body); err != nil { return nil, &errors.HTTPError{err, "Error parsing request JSON", 400} } - if body.ID == "" || body.Type == "" || body.UserID == "" || body.Config == nil { - return nil, &errors.HTTPError{ - nil, `Must supply an "ID", a "Type", a "UserID" and a "Config"`, 400, - } + if err := body.Check(); err != nil { + return nil, &errors.HTTPError{err, err.Error(), 400} } service, err := types.CreateService(body.ID, body.Type, body.UserID, body.Config) diff --git a/src/github.com/matrix-org/go-neb/types/types.go b/src/github.com/matrix-org/go-neb/types/types.go index 5b060a0..ed1ae38 100644 --- a/src/github.com/matrix-org/go-neb/types/types.go +++ b/src/github.com/matrix-org/go-neb/types/types.go @@ -7,32 +7,10 @@ import ( "github.com/matrix-org/go-neb/matrix" "github.com/matrix-org/go-neb/plugin" "net/http" - "net/url" "strings" "time" ) -// A ClientConfig is the configuration for a matrix client for a bot to use. -type ClientConfig struct { - UserID string // The matrix UserId to connect with. - HomeserverURL string // A URL with the host and port of the matrix server. E.g. https://matrix.org:8448 - AccessToken string // The matrix access token to authenticate the requests with. - Sync bool // True to start a sync stream for this user - AutoJoinRooms bool // True to automatically join all rooms for this user - DisplayName string // The display name to set for the matrix client -} - -// Check that the client has the correct fields. -func (c *ClientConfig) Check() error { - if c.UserID == "" || c.HomeserverURL == "" || c.AccessToken == "" { - return errors.New(`Must supply a "UserID", a "HomeserverURL", and an "AccessToken"`) - } - if _, err := url.Parse(c.HomeserverURL); err != nil { - return err - } - return nil -} - // BotOptions for a given bot user in a given room type BotOptions struct { RoomID string