From 91c36d5b0ff1f64e6d3813a435f7cc3b24d4d945 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 31 Aug 2017 15:41:08 +0100 Subject: [PATCH] Add database table and functions for tracking 3PIDs --- .../auth/storage/accounts/storage.go | 56 ++++++++- .../auth/storage/accounts/threepid_table.go | 118 ++++++++++++++++++ 2 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/threepid_table.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go index 76b9a4dd9..3617647e9 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go @@ -16,6 +16,7 @@ package accounts import ( "database/sql" + "errors" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/common" @@ -33,6 +34,7 @@ type Database struct { profiles profilesStatements memberships membershipStatements accountDatas accountDataStatements + threepids threepidStatements serverName gomatrixserverlib.ServerName } @@ -63,7 +65,11 @@ func NewDatabase(dataSourceName string, serverName gomatrixserverlib.ServerName) if err = ac.prepare(db); err != nil { return nil, err } - return &Database{db, partitions, a, p, m, ac, serverName}, nil + t := threepidStatements{} + if err = t.prepare(db); err != nil { + return nil, err + } + return &Database{db, partitions, a, p, m, ac, t, serverName}, nil } // GetAccountByPassword returns the account associated with the given localpart and password. @@ -233,3 +239,51 @@ func hashPassword(plaintext string) (hash string, err error) { hashBytes, err := bcrypt.GenerateFromPassword([]byte(plaintext), bcrypt.DefaultCost) return string(hashBytes), err } + +// Err3PIDInUse is the error returned when trying to save an association involving +// a third-party identifier which is already associated to a local user. +var Err3PIDInUse = errors.New("This third-party identifier is already in use") + +// SaveThreePIDAssociation saves the association between a third party identifier +// and a local Matrix user (identified by the user's ID's local part). +// If the third-party identifier is already part of an association, returns Err3PIDInUse. +// Returns an error if there was a problem talking to the database. +func (d *Database) SaveThreePIDAssociation(threepid string, localpart string) (err error) { + return common.WithTransaction(d.db, func(txn *sql.Tx) error { + user, err := d.threepids.selectLocalpartForThreePID(txn, threepid) + if err != nil { + return err + } + + if len(user) > 0 { + return Err3PIDInUse + } + + return d.threepids.insertThreePID(txn, threepid, localpart) + }) +} + +// RemoveThreePIDAssociation removes the association involving a given third-party +// identifier. +// If no association exists involving this third-party identifier, returns nothing. +// If there was a problem talking to the database, returns an error. +func (d *Database) RemoveThreePIDAssociation(threepid string) (err error) { + return d.threepids.deleteThreePID(threepid) +} + +// GetLocalpartForThreePID looks up the localpart associated with a given third-party +// identifier. +// If no association involves the given third-party idenfitier, returns an empty +// string. +// Returns an error if there was a problem talking to the database. +func (d *Database) GetLocalpartForThreePID(threepid string) (localpart string, err error) { + return d.threepids.selectLocalpartForThreePID(nil, threepid) +} + +// GetThreePIDsForLocalpart looks up the third-party identifiers associated with +// a given local user. +// If no association is known for this user, returns an empty slice. +// Returns an error if there was an issue talking to the database. +func (d *Database) GetThreePIDsForLocalpart(localpart string) (threepids map[string]string, err error) { + return d.threepids.selectThreePIDsForLocalpart(localpart) +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/threepid_table.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/threepid_table.go new file mode 100644 index 000000000..ba40aaebd --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/threepid_table.go @@ -0,0 +1,118 @@ +// Copyright 2017 Vector Creations Ltd +// +// 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 accounts + +import ( + "database/sql" +) + +const threepidSchema = ` +-- Stores data about third party identifiers +CREATE TABLE IF NOT EXISTS account_threepid ( + -- The third party identifier + threepid TEXT NOT NULL, + -- The 3PID medium + medium TEXT NOT NULL DEFAULT 'email', + -- The localpart of the Matrix user ID associated to this 3PID + localpart TEXT NOT NULL, + + PRIMARY KEY(threepid, medium) +); + +CREATE INDEX IF NOT EXISTS account_threepid_localpart ON account_threepid(localpart); +` + +const selectLocalpartForThreePIDSQL = "" + + "SELECT localpart FROM account_threepid WHERE threepid = $1" + +const selectThreePIDsForLocalpartSQL = "" + + "SELECT threepid, medium FROM account_threepid WHERE localpart = $1" + +const insertThreePIDSQL = "" + + "INSERT INTO account_threepid (threepid, localpart) VALUES ($1, $2)" + +const deleteThreePIDSQL = "" + + "DELETE FROM account_threepid WHERE threepid = $1" + +type threepidStatements struct { + selectLocalpartForThreePIDStmt *sql.Stmt + selectThreePIDsForLocalpartStmt *sql.Stmt + insertThreePIDStmt *sql.Stmt + deleteThreePIDStmt *sql.Stmt +} + +func (s *threepidStatements) prepare(db *sql.DB) (err error) { + _, err = db.Exec(threepidSchema) + if err != nil { + return + } + if s.selectLocalpartForThreePIDStmt, err = db.Prepare(selectLocalpartForThreePIDSQL); err != nil { + return + } + if s.selectThreePIDsForLocalpartStmt, err = db.Prepare(selectThreePIDsForLocalpartSQL); err != nil { + return + } + if s.insertThreePIDStmt, err = db.Prepare(insertThreePIDSQL); err != nil { + return + } + if s.deleteThreePIDStmt, err = db.Prepare(deleteThreePIDSQL); err != nil { + return + } + + return +} + +func (s *threepidStatements) selectLocalpartForThreePID(txn *sql.Tx, threepid string) (localpart string, err error) { + var stmt *sql.Stmt + if txn != nil { + stmt = txn.Stmt(s.selectLocalpartForThreePIDStmt) + } else { + stmt = s.selectLocalpartForThreePIDStmt + } + err = stmt.QueryRow(threepid).Scan(&localpart) + if err == sql.ErrNoRows { + return "", nil + } + return +} + +func (s *threepidStatements) selectThreePIDsForLocalpart(localpart string) (threepids map[string]string, err error) { + rows, err := s.selectThreePIDsForLocalpartStmt.Query(localpart) + if err != nil { + return + } + + threepids = make(map[string]string) + for rows.Next() { + var threepid string + var medium string + if err = rows.Scan(&threepid, &medium); err != nil { + return + } + threepids[threepid] = medium + } + + return +} + +func (s *threepidStatements) insertThreePID(txn *sql.Tx, threepid string, localpart string) (err error) { + _, err = txn.Stmt(s.insertThreePIDStmt).Exec(threepid, localpart) + return +} + +func (s *threepidStatements) deleteThreePID(threepid string) (err error) { + _, err = s.deleteThreePIDStmt.Exec(threepid) + return +}