From 8fea4bb0ab2320534e6527ab1560c8f6a8d9d3da Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 3 Aug 2016 13:07:49 +0100 Subject: [PATCH] Add AuthModule concept - Register them like we are with Services. - Add `/configureAuth` endpoint to create/update auth. - Move ThirdPartyAuth out of the database layer since they are passed as params to `/admin/configureAuth` --- src/github.com/matrix-org/go-neb/api.go | 26 ++++++++++++ src/github.com/matrix-org/go-neb/auth/auth.go | 13 ++++++ .../matrix-org/go-neb/auth/github/github.go | 22 ++++++++++ .../matrix-org/go-neb/database/db.go | 9 +--- .../matrix-org/go-neb/database/schema.go | 42 ++++++------------- src/github.com/matrix-org/go-neb/goneb.go | 4 ++ .../matrix-org/go-neb/types/types.go | 36 ++++++++++++++++ 7 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 src/github.com/matrix-org/go-neb/auth/auth.go create mode 100644 src/github.com/matrix-org/go-neb/auth/github/github.go diff --git a/src/github.com/matrix-org/go-neb/api.go b/src/github.com/matrix-org/go-neb/api.go index 8a07874..d6e055d 100644 --- a/src/github.com/matrix-org/go-neb/api.go +++ b/src/github.com/matrix-org/go-neb/api.go @@ -16,6 +16,32 @@ func (*heartbeatHandler) OnIncomingRequest(req *http.Request) (interface{}, *err return &struct{}{}, nil } +type configureAuthHandler struct { + db *database.ServiceDB +} + +func (*configureAuthHandler) OnIncomingRequest(req *http.Request) (interface{}, *errors.HTTPError) { + if req.Method != "POST" { + return nil, &errors.HTTPError{nil, "Unsupported Method", 405} + } + var tpa types.ThirdPartyAuth + if err := json.NewDecoder(req.Body).Decode(&tpa); err != nil { + return nil, &errors.HTTPError{err, "Error parsing request JSON", 400} + } + + am := types.GetAuthModule(tpa.Type) + if am == nil { + return nil, &errors.HTTPError{nil, "Bad auth type: " + tpa.Type, 400} + } + + err := am.Process(tpa) + if err != nil { + return nil, &errors.HTTPError{err, "Failed to persist auth", 500} + } + + return nil, nil +} + func handleWebhook(w http.ResponseWriter, req *http.Request) { segments := strings.Split(req.URL.Path, "/") // last path segment is the service type which we will pass the incoming request to diff --git a/src/github.com/matrix-org/go-neb/auth/auth.go b/src/github.com/matrix-org/go-neb/auth/auth.go new file mode 100644 index 0000000..eb9338d --- /dev/null +++ b/src/github.com/matrix-org/go-neb/auth/auth.go @@ -0,0 +1,13 @@ +package auth + +import ( + "github.com/matrix-org/go-neb/auth/github" + "github.com/matrix-org/go-neb/database" + "github.com/matrix-org/go-neb/types" +) + +// RegisterModules registers all known modules so they can be retrieved via +// type.GetAuthModule +func RegisterModules(db *database.ServiceDB) { + types.RegisterAuthModule(&github.AuthModule{Database: db}) +} diff --git a/src/github.com/matrix-org/go-neb/auth/github/github.go b/src/github.com/matrix-org/go-neb/auth/github/github.go new file mode 100644 index 0000000..3661417 --- /dev/null +++ b/src/github.com/matrix-org/go-neb/auth/github/github.go @@ -0,0 +1,22 @@ +package github + +import ( + "github.com/matrix-org/go-neb/database" + "github.com/matrix-org/go-neb/types" +) + +// AuthModule for github +type AuthModule struct { + Database *database.ServiceDB +} + +// Type of the auth module +func (*AuthModule) Type() string { + return "github" +} + +// Process a third-party auth request +func (am *AuthModule) Process(tpa types.ThirdPartyAuth) (err error) { + _, err = am.Database.StoreThirdPartyAuth(tpa) + return +} 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 c7b84e8..2259534 100644 --- a/src/github.com/matrix-org/go-neb/database/db.go +++ b/src/github.com/matrix-org/go-neb/database/db.go @@ -98,7 +98,7 @@ func (d *ServiceDB) LoadServicesInRoom(serviceUserID, roomID string) (services [ // LoadThirdPartyAuth loads third-party credentials that the given userID // has linked to the given resource. Returns sql.ErrNoRows if there are no // credentials for the given resource/user combination. -func (d *ServiceDB) LoadThirdPartyAuth(resource, userID string) (tpa ThirdPartyAuth, err error) { +func (d *ServiceDB) LoadThirdPartyAuth(resource, userID string) (tpa types.ThirdPartyAuth, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { tpa, err = selectThirdPartyAuthTxn(txn, resource, userID) if err != nil { @@ -113,19 +113,14 @@ func (d *ServiceDB) LoadThirdPartyAuth(resource, userID string) (tpa ThirdPartyA // time added/updated values. // If the auth already exists then it will be updated, otherwise a new auth // will be inserted. The previous auth is returned. -func (d *ServiceDB) StoreThirdPartyAuth(tpa ThirdPartyAuth) (old ThirdPartyAuth, err error) { +func (d *ServiceDB) StoreThirdPartyAuth(tpa types.ThirdPartyAuth) (old types.ThirdPartyAuth, err error) { err = runTransaction(d.db, func(txn *sql.Tx) error { old, err = selectThirdPartyAuthTxn(txn, tpa.Resource, tpa.UserID) - now := time.Now().UnixNano() / 1000000 - if err == sql.ErrNoRows { - tpa.TimeAddedMs = now - tpa.TimeUpdatedMs = now return insertThirdPartyAuthTxn(txn, tpa) } else if err != nil { return err } else { - tpa.TimeUpdatedMs = now return updateThirdPartyAuthTxn(txn, tpa) } }) 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 187d411..70275d0 100644 --- a/src/github.com/matrix-org/go-neb/database/schema.go +++ b/src/github.com/matrix-org/go-neb/database/schema.go @@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS matrix_clients ( CREATE TABLE IF NOT EXISTS third_party_auth ( user_id TEXT NOT NULL, + type TEXT NOT NULL, resource TEXT NOT NULL, auth_json TEXT NOT NULL, time_added_ms BIGINT NOT NULL, @@ -211,45 +212,27 @@ func selectRoomServicesTxn(txn *sql.Tx, serviceUserID, roomID string) (serviceID return } -// ThirdPartyAuth represents a third_party_auth data row. -type ThirdPartyAuth struct { - // The ID of the matrix user who has authed with the third party - UserID string - // The location of the third party resource e.g. "github.com". - // This is mainly relevant for decentralised services like JIRA which - // may have many different locations (e.g. "matrix.org/jira") for the - // same ServiceType ("jira"). - Resource string - // An opaque JSON blob of stored auth data. - AuthJSON []byte - // When the row was initially inserted. - TimeAddedMs int64 - // When the row was last updated. - TimeUpdatedMs int64 -} - const selectThirdPartyAuthSQL = ` -SELECT auth_json, time_added_ms, time_updated_ms FROM third_party_auth -WHERE user_id=$1 AND resource=$2 +SELECT type, auth_json FROM third_party_auth WHERE user_id=$1 AND resource=$2 ` -func selectThirdPartyAuthTxn(txn *sql.Tx, resource, userID string) (tpa ThirdPartyAuth, err error) { +func selectThirdPartyAuthTxn(txn *sql.Tx, resource, userID string) (tpa types.ThirdPartyAuth, err error) { tpa.Resource = resource tpa.UserID = userID - err = txn.QueryRow(selectThirdPartyAuthSQL, userID, resource).Scan( - &tpa.AuthJSON, &tpa.TimeAddedMs, &tpa.TimeUpdatedMs) + err = txn.QueryRow(selectThirdPartyAuthSQL, userID, resource).Scan(&tpa.Type, &tpa.AuthJSON) return } const insertThirdPartyAuthSQL = ` INSERT INTO third_party_auth( - user_id, resource, auth_json, time_added_ms, time_updated_ms -) VALUES($1, $2, $3, $4, $5) + user_id, type, resource, auth_json, time_added_ms, time_updated_ms +) VALUES($1, $2, $3, $4, $5, $6) ` -func insertThirdPartyAuthTxn(txn *sql.Tx, tpa ThirdPartyAuth) (err error) { - _, err = txn.Exec(insertThirdPartyAuthSQL, tpa.UserID, tpa.Resource, - tpa.AuthJSON, tpa.TimeAddedMs, tpa.TimeUpdatedMs) +func insertThirdPartyAuthTxn(txn *sql.Tx, tpa types.ThirdPartyAuth) (err error) { + timeAddedMs := time.Now().UnixNano() / 1000000 + _, err = txn.Exec(insertThirdPartyAuthSQL, tpa.UserID, tpa.Type, tpa.Resource, + []byte(tpa.AuthJSON), timeAddedMs, timeAddedMs) return } @@ -258,8 +241,9 @@ UPDATE third_party_auth SET auth_json=$1, time_updated_ms=$2 WHERE user_id=$3 AND resource=$4 ` -func updateThirdPartyAuthTxn(txn *sql.Tx, tpa ThirdPartyAuth) (err error) { - _, err = txn.Exec(updateThirdPartyAuthSQL, tpa.AuthJSON, tpa.TimeUpdatedMs, +func updateThirdPartyAuthTxn(txn *sql.Tx, tpa types.ThirdPartyAuth) (err error) { + timeUpdatedMs := time.Now().UnixNano() / 1000000 + _, err = txn.Exec(updateThirdPartyAuthSQL, []byte(tpa.AuthJSON), timeUpdatedMs, tpa.UserID, tpa.Resource) return err } diff --git a/src/github.com/matrix-org/go-neb/goneb.go b/src/github.com/matrix-org/go-neb/goneb.go index 7ba75c2..72e6f24 100644 --- a/src/github.com/matrix-org/go-neb/goneb.go +++ b/src/github.com/matrix-org/go-neb/goneb.go @@ -2,6 +2,7 @@ package main import ( log "github.com/Sirupsen/logrus" + "github.com/matrix-org/go-neb/auth" "github.com/matrix-org/go-neb/clients" "github.com/matrix-org/go-neb/database" "github.com/matrix-org/go-neb/server" @@ -28,9 +29,12 @@ func main() { log.Panic(err) } + auth.RegisterModules(db) + http.Handle("/test", server.MakeJSONAPI(&heartbeatHandler{})) http.Handle("/admin/configureClient", server.MakeJSONAPI(&configureClientHandler{db: db, clients: clients})) http.Handle("/admin/configureService", server.MakeJSONAPI(&configureServiceHandler{db: db, clients: clients})) + http.Handle("/admin/configureAuth", server.MakeJSONAPI(&configureAuthHandler{db: db})) http.HandleFunc("/services/hooks/", handleWebhook) http.ListenAndServe(bindAddress, nil) 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 dcf42e6..1f0f6c9 100644 --- a/src/github.com/matrix-org/go-neb/types/types.go +++ b/src/github.com/matrix-org/go-neb/types/types.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "errors" "github.com/matrix-org/go-neb/plugin" "net/http" @@ -51,3 +52,38 @@ func CreateService(serviceID, serviceType string) Service { } return f(serviceID) } + +// AuthModule represents a thing which can handle auth requests of a given type. +type AuthModule interface { + Type() string + Process(tpa ThirdPartyAuth) error +} + +var authModulesByType = map[string]AuthModule{} + +// ThirdPartyAuth represents an individual authorisation entry between +// a third party and the Matrix user. +type ThirdPartyAuth struct { + // The ID of the matrix user who has authed with the third party + UserID string + // The type of auth (e.g. "jira", "github"). This determines which + // auth module is loaded to process the auth. + Type string + // The location of the third party resource e.g. "github.com". + // This is mainly relevant for decentralised services like JIRA which + // may have many different locations (e.g. "matrix.org/jira") for the + // same ServiceType ("jira"). + Resource string + // An opaque JSON blob of stored auth data. + AuthJSON json.RawMessage +} + +// RegisterAuthModule so it can be used by other parts of NEB. +func RegisterAuthModule(am AuthModule) { + authModulesByType[am.Type()] = am +} + +// GetAuthModule for the given auth type. Returns nil if no match. +func GetAuthModule(authType string) AuthModule { + return authModulesByType[authType] +}