Browse Source

Add concept of AuthRealms

- These represent a place where a user can authenticate themselves.
- They function in the same way as Services (insert/update based on an HTTP API)
- They currently don't *do* a lot other than exist for storing realm-specific
  information (e.g. the `GithubRealm` stores the `ClientSecret` and `ClientID`)
pull/6/head
Kegan Dougal 8 years ago
parent
commit
36eb21be7c
  1. 37
      src/github.com/matrix-org/go-neb/api.go
  2. 27
      src/github.com/matrix-org/go-neb/database/db.go
  3. 67
      src/github.com/matrix-org/go-neb/database/schema.go
  4. 3
      src/github.com/matrix-org/go-neb/goneb.go
  5. 26
      src/github.com/matrix-org/go-neb/realms/github/github.go
  6. 24
      src/github.com/matrix-org/go-neb/types/types.go

37
src/github.com/matrix-org/go-neb/api.go

@ -17,30 +17,47 @@ func (*heartbeatHandler) OnIncomingRequest(req *http.Request) (interface{}, *err
return &struct{}{}, nil
}
type configureAuthHandler struct {
type configureAuthRealmHandler struct {
db *database.ServiceDB
}
func (*configureAuthHandler) OnIncomingRequest(req *http.Request) (interface{}, *errors.HTTPError) {
func (h *configureAuthRealmHandler) 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 {
var body struct {
ID string
Type string
Config json.RawMessage
}
if err := json.NewDecoder(req.Body).Decode(&body); 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}
if body.ID == "" || body.Type == "" || body.Config == nil {
return nil, &errors.HTTPError{nil, `Must supply a "ID", a "Type" and a "Config"`, 400}
}
realm := types.CreateAuthRealm(body.ID, body.Type)
if realm == nil {
return nil, &errors.HTTPError{nil, "Unknown realm type", 400}
}
if err := json.Unmarshal(body.Config, realm); err != nil {
return nil, &errors.HTTPError{err, "Error parsing config JSON", 400}
}
err := am.Process(tpa)
oldRealm, err := h.db.StoreAuthRealm(realm)
if err != nil {
return nil, &errors.HTTPError{err, "Failed to persist auth", 500}
return nil, &errors.HTTPError{err, "Error storing realm", 500}
}
return nil, nil
return &struct {
ID string
Type string
OldConfig types.AuthRealm
NewConfig types.AuthRealm
}{body.ID, body.Type, oldRealm, realm}, nil
}
type webhookHandler struct {

27
src/github.com/matrix-org/go-neb/database/db.go

@ -191,6 +191,33 @@ func (d *ServiceDB) StoreService(service types.Service, client *matrix.Client) (
return
}
// LoadAuthRealm loads an AuthRealm from the database.
// Returns sql.ErrNoRows if the realm isn't in the database.
func (d *ServiceDB) LoadAuthRealm(realmID string) (realm types.AuthRealm, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error {
realm, err = selectRealmTxn(txn, realmID)
return err
})
return
}
// StoreAuthRealm stores the given AuthRealm, clobbering based on the realm ID.
// This function updates the time added/updated values. The previous realm, if any, is
// returned.
func (d *ServiceDB) StoreAuthRealm(realm types.AuthRealm) (old types.AuthRealm, err error) {
err = runTransaction(d.db, func(txn *sql.Tx) error {
old, err = selectRealmTxn(txn, realm.ID())
if err == sql.ErrNoRows {
return insertRealmTxn(txn, time.Now(), realm)
} else if err != nil {
return err
} else {
return updateRealmTxn(txn, time.Now(), realm)
}
})
return
}
func runTransaction(db *sql.DB, fn func(txn *sql.Tx) error) (err error) {
txn, err := db.Begin()
if err != nil {

67
src/github.com/matrix-org/go-neb/database/schema.go

@ -44,6 +44,15 @@ CREATE TABLE IF NOT EXISTS third_party_auth (
time_updated_ms BIGINT NOT NULL,
UNIQUE(user_id, resource)
);
CREATE TABLE IF NOT EXISTS auth_realms (
realm_id TEXT NOT NULL,
realm_type TEXT NOT NULL,
realm_json TEXT NOT NULL,
time_added_ms BIGINT NOT NULL,
time_updated_ms BIGINT NOT NULL,
UNIQUE(realm_id)
);
`
const selectServiceUserIDsSQL = `
@ -247,3 +256,61 @@ func updateThirdPartyAuthTxn(txn *sql.Tx, tpa types.ThirdPartyAuth) (err error)
tpa.UserID, tpa.Resource)
return err
}
const insertRealmSQL = `
INSERT INTO auth_realms(
realm_id, realm_type, realm_json, time_added_ms, time_updated_ms
) VALUES ($1, $2, $3, $4, $5)
`
func insertRealmTxn(txn *sql.Tx, now time.Time, realm types.AuthRealm) error {
realmJSON, err := json.Marshal(realm)
if err != nil {
return err
}
t := now.UnixNano() / 1000000
_, err = txn.Exec(
insertRealmSQL,
realm.ID(), realm.Type(), realmJSON, t, t,
)
return err
}
const selectRealmSQL = `
SELECT realm_type, realm_json FROM auth_realms WHERE realm_id = $1
`
func selectRealmTxn(txn *sql.Tx, realmID string) (types.AuthRealm, error) {
var realmType string
var realmJSON []byte
row := txn.QueryRow(selectRealmSQL, realmID)
if err := row.Scan(&realmType, &realmJSON); err != nil {
return nil, err
}
realm := types.CreateAuthRealm(realmID, realmType)
if realm == nil {
return nil, fmt.Errorf("Cannot create realm of type %s", realmType)
}
if err := json.Unmarshal(realmJSON, realm); err != nil {
return nil, err
}
return realm, nil
}
const updateRealmSQL = `
UPDATE auth_realms SET realm_type=$1, realm_json=$2, time_updated_ms=$3
WHERE realm_id=$4
`
func updateRealmTxn(txn *sql.Tx, now time.Time, realm types.AuthRealm) error {
realmJSON, err := json.Marshal(realm)
if err != nil {
return err
}
t := now.UnixNano() / 1000000
_, err = txn.Exec(
updateRealmSQL, realm.Type(), realmJSON, t,
realm.ID(),
)
return err
}

3
src/github.com/matrix-org/go-neb/goneb.go

@ -5,6 +5,7 @@ import (
"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/realms/github"
"github.com/matrix-org/go-neb/server"
_ "github.com/matrix-org/go-neb/services/echo"
_ "github.com/matrix-org/go-neb/services/github"
@ -34,7 +35,7 @@ func main() {
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.Handle("/admin/configureAuthRealm", server.MakeJSONAPI(&configureAuthRealmHandler{db: db}))
wh := &webhookHandler{db: db, clients: clients}
http.HandleFunc("/services/hooks/", wh.handle)

26
src/github.com/matrix-org/go-neb/realms/github/github.go

@ -0,0 +1,26 @@
package realms
import (
"github.com/matrix-org/go-neb/types"
)
type githubRealm struct {
id string
ClientSecret string
ClientID string
WebhookEndpoint string
}
func (r *githubRealm) ID() string {
return r.id
}
func (r *githubRealm) Type() string {
return "github"
}
func init() {
types.RegisterAuthRealm(func(realmID string) types.AuthRealm {
return &githubRealm{id: realmID}
})
}

24
src/github.com/matrix-org/go-neb/types/types.go

@ -88,3 +88,27 @@ func RegisterAuthModule(am AuthModule) {
func GetAuthModule(authType string) AuthModule {
return authModulesByType[authType]
}
// AuthRealm represents a place where a user can authenticate themselves.
// This may static (like github.com) or a specific domain (like matrix.org/jira)
type AuthRealm interface {
ID() string
Type() string
}
var realmsByType = map[string]func(string) AuthRealm{}
// RegisterAuthRealm registers a factory for creating AuthRealm instances.
func RegisterAuthRealm(factory func(string) AuthRealm) {
realmsByType[factory("").Type()] = factory
}
// CreateAuthRealm creates an AuthRealm of the given type and realm ID.
// Returns nil if the realm couldn't be created.
func CreateAuthRealm(realmID, realmType string) AuthRealm {
f := realmsByType[realmType]
if f == nil {
return nil
}
return f(realmID)
}
Loading…
Cancel
Save