зеркало из https://github.com/mozilla/mig.git
280 строки
9.7 KiB
Go
280 строки
9.7 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
|
|
|
|
package database /* import "mig.ninja/mig/database" */
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/lib/pq"
|
|
"mig.ninja/mig"
|
|
)
|
|
|
|
// ActiveInvestigators returns a slice of investigators keys marked as active
|
|
func (db *DB) ActiveInvestigatorsPubKeys() (keys [][]byte, err error) {
|
|
rows, err := db.c.Query(`SELECT publickey FROM investigators WHERE status='active'
|
|
AND publickey IS NOT NULL`)
|
|
if rows != nil {
|
|
defer rows.Close()
|
|
}
|
|
if err != nil && err != sql.ErrNoRows {
|
|
err = fmt.Errorf("Error while listing active investigators keys: '%v'", err)
|
|
return
|
|
}
|
|
if err == sql.ErrNoRows { // having an empty DB is not an issue
|
|
return
|
|
}
|
|
for rows.Next() {
|
|
var key []byte
|
|
err = rows.Scan(&key)
|
|
if err != nil {
|
|
err = fmt.Errorf("Error while retrieving investigator key: '%v'", err)
|
|
return
|
|
}
|
|
keys = append(keys, key)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
err = fmt.Errorf("Failed to complete active investigators query: '%v'", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// InvestigatorByID searches the database for an investigator with a given ID
|
|
func (db *DB) InvestigatorByID(iid float64) (inv mig.Investigator, err error) {
|
|
var perm int64
|
|
err = db.c.QueryRow(`SELECT id, name, COALESCE(pgpfingerprint, ''),
|
|
COALESCE(publickey, ''), status, createdat, lastmodified, permissions,
|
|
CASE WHEN apikey IS NOT NULL THEN 'set' ELSE '' END
|
|
FROM investigators WHERE id=$1`,
|
|
iid).Scan(&inv.ID, &inv.Name, &inv.PGPFingerprint, &inv.PublicKey,
|
|
&inv.Status, &inv.CreatedAt, &inv.LastModified, &perm, &inv.APIKey)
|
|
if err != nil {
|
|
err = fmt.Errorf("Error while retrieving investigator: '%v'", err)
|
|
return
|
|
}
|
|
if err == sql.ErrNoRows {
|
|
return
|
|
}
|
|
inv.Permissions.FromMask(perm)
|
|
return
|
|
}
|
|
|
|
// InvestigatorByFingerprint searches the database for an investigator that
|
|
// has a given fingerprint
|
|
func (db *DB) InvestigatorByFingerprint(fp string) (inv mig.Investigator, err error) {
|
|
var perm int64
|
|
err = db.c.QueryRow(`SELECT investigators.id, investigators.name, investigators.pgpfingerprint,
|
|
investigators.publickey, investigators.status, investigators.createdat,
|
|
investigators.lastmodified, investigators.permissions
|
|
FROM investigators WHERE pgpfingerprint IS NOT NULL AND
|
|
LOWER(pgpfingerprint)=LOWER($1)`,
|
|
fp).Scan(&inv.ID, &inv.Name, &inv.PGPFingerprint, &inv.PublicKey, &inv.Status,
|
|
&inv.CreatedAt, &inv.LastModified, &perm)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
err = fmt.Errorf("Error while finding investigator: '%v'", err)
|
|
return
|
|
}
|
|
if err == sql.ErrNoRows {
|
|
err = fmt.Errorf("InvestigatorByFingerprint: no investigator found for fingerprint '%s'", fp)
|
|
return
|
|
}
|
|
inv.Permissions.FromMask(perm)
|
|
return
|
|
}
|
|
|
|
// Returns a set of InvestigatorAPIAuthHelper structs that the API can utilize to
|
|
// authorize requests containing the X-MIGAPIKEY header
|
|
func (db *DB) InvestigatorAPIKeyAuthHelpers() (ret []mig.InvestigatorAPIAuthHelper, err error) {
|
|
rows, err := db.c.Query(`SELECT id, apikey, apisalt FROM investigators
|
|
WHERE apikey IS NOT NULL AND apisalt IS NOT NULL AND status='active'`)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for rows.Next() {
|
|
var (
|
|
nh mig.InvestigatorAPIAuthHelper
|
|
invid int
|
|
)
|
|
err = rows.Scan(&invid, &nh.APIKey, &nh.Salt)
|
|
if err != nil {
|
|
rows.Close()
|
|
return
|
|
}
|
|
nh.ID = float64(invid)
|
|
ret = append(ret, nh)
|
|
}
|
|
return
|
|
}
|
|
|
|
//InvestigatorByActionID returns the list of investigators that signed a given action
|
|
func (db *DB) InvestigatorByActionID(aid float64) (invs []mig.Investigator, err error) {
|
|
var perm int64
|
|
rows, err := db.c.Query(`SELECT investigators.id, investigators.name, investigators.pgpfingerprint,
|
|
investigators.status, investigators.createdat, investigators.lastmodified,
|
|
investigators.permissions
|
|
FROM investigators, signatures
|
|
WHERE signatures.actionid=$1
|
|
AND signatures.investigatorid=investigators.id`, aid)
|
|
if rows != nil {
|
|
defer rows.Close()
|
|
}
|
|
if err != nil && err != sql.ErrNoRows {
|
|
err = fmt.Errorf("Error while finding investigator: '%v'", err)
|
|
return
|
|
}
|
|
for rows.Next() {
|
|
var inv mig.Investigator
|
|
err = rows.Scan(&inv.ID, &inv.Name, &inv.PGPFingerprint, &inv.Status, &inv.CreatedAt, &inv.LastModified, &perm)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to retrieve investigator data: '%v'", err)
|
|
return
|
|
}
|
|
inv.Permissions.FromMask(perm)
|
|
invs = append(invs, inv)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
err = fmt.Errorf("Failed to complete database query: '%v'", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// InsertInvestigator creates a new investigator in the database and returns its ID,
|
|
// or an error if the insertion failed, or if the investigator already exists
|
|
func (db *DB) InsertInvestigator(inv mig.Investigator) (iid float64, err error) {
|
|
var newid int
|
|
err = db.c.QueryRow(`INSERT INTO investigators
|
|
(name, pgpfingerprint, publickey, status, createdat, lastmodified, permissions)
|
|
VALUES ($1, NULLIF($2, ''), NULLIF($3, E'\\x'::bytea), 'active', $4, $5, $6)
|
|
RETURNING id`,
|
|
inv.Name, inv.PGPFingerprint, inv.PublicKey, time.Now().UTC(), time.Now().UTC(),
|
|
inv.Permissions.ToMask()).Scan(&newid)
|
|
if err != nil {
|
|
if err.Error() == `pq: duplicate key value violates unique constraint "investigators_pgpfingerprint_idx"` {
|
|
return iid, fmt.Errorf("Investigator's PGP Fingerprint already exists in database")
|
|
}
|
|
return iid, fmt.Errorf("Failed to create investigator: '%v'", err)
|
|
}
|
|
iid = float64(newid)
|
|
inv, err = db.InvestigatorByID(iid)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("Failed to retrieve investigator ID: '%v'", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// InsertSchedulerInvestigator creates a new migscheduler investigator in the database
|
|
// and returns its ID, or an error if the insertion failed, or if the investigator already exists
|
|
func (db *DB) InsertSchedulerInvestigator(inv mig.Investigator) (iid float64, err error) {
|
|
_, err = db.c.Exec(`INSERT INTO investigators
|
|
(name, pgpfingerprint, publickey, privatekey, status, createdat, lastmodified)
|
|
VALUES ($1, $2, $3, $4, 'active', $5, $6)`,
|
|
inv.Name, inv.PGPFingerprint, inv.PublicKey, inv.PrivateKey, time.Now().UTC(), time.Now().UTC())
|
|
if err != nil {
|
|
if err.Error() == `pq: duplicate key value violates unique constraint "investigators_pgpfingerprint_idx"` {
|
|
return iid, fmt.Errorf("Investigator's PGP Fingerprint already exists in database")
|
|
}
|
|
return iid, fmt.Errorf("Failed to create investigator: '%v'", err)
|
|
}
|
|
inv, err = db.InvestigatorByFingerprint(inv.PGPFingerprint)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("Failed to retrieve investigator ID: '%v'", err)
|
|
}
|
|
iid = inv.ID
|
|
return
|
|
}
|
|
|
|
// UpdateInvestigatorStatus updates the status of an investigator
|
|
func (db *DB) UpdateInvestigatorStatus(inv mig.Investigator) (err error) {
|
|
if inv.Status != mig.StatusActiveInvestigator && inv.Status != mig.StatusDisabledInvestigator {
|
|
return fmt.Errorf("Invalid investigator status '%s'", inv.Status)
|
|
}
|
|
_, err = db.c.Exec(`UPDATE investigators SET (status) = ($1) WHERE id=$2`,
|
|
inv.Status, inv.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to update investigator: '%v'", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// UpdateInvestigatorAPIKey enables or disabled a standard API key for an investigator
|
|
func (db *DB) UpdateInvestigatorAPIKey(inv mig.Investigator, key []byte, salt []byte) (err error) {
|
|
if len(key) == 0 {
|
|
_, err = db.c.Exec(`UPDATE investigators SET apikey=NULL, apisalt=NULL
|
|
WHERE id=$1`, inv.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err = db.c.Exec(`UPDATE investigators SET apikey=$1, apisalt=$2
|
|
WHERE id=$3`, key, salt, inv.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (db *DB) UpdateInvestigatorPerms(inv mig.Investigator) (err error) {
|
|
// If the desired permissions do not include an admin bit, do a check here
|
|
// to see how many administrators remain. If this change will reduce the
|
|
// number of admins to 0 prevent the modification.
|
|
mask := inv.Permissions.ToMask()
|
|
if ((mask & mig.PermInvestigator) == 0) || ((mask & mig.PermInvestigatorUpdate) == 0) {
|
|
var ts mig.InvestigatorPerms
|
|
ts.AdminSet()
|
|
tmask := ts.ToMask()
|
|
cnt := 0
|
|
err = db.c.QueryRow(`SELECT COUNT(*) FROM investigators
|
|
WHERE (permissions & $1) != 0 AND
|
|
id != $2`, tmask, inv.ID).Scan(&cnt)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to update investigator: '%v'", err)
|
|
}
|
|
if cnt < 1 {
|
|
return fmt.Errorf("Failed to update investigator: 'will not remove last admin'")
|
|
}
|
|
}
|
|
_, err = db.c.Exec(`UPDATE investigators SET (permissions) = ($1) WHERE id=$2`,
|
|
mask, inv.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to update investigator: '%v'", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetSchedulerPrivKey returns the first active private key found for user migscheduler
|
|
func (db *DB) GetSchedulerPrivKey() (key []byte, err error) {
|
|
err = db.c.QueryRow(`SELECT privatekey FROM investigators
|
|
WHERE name ='migscheduler' AND status='active'
|
|
ORDER BY id ASC LIMIT 1`).Scan(&key)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
err = fmt.Errorf("Error while retrieving scheduler private key: '%v'", err)
|
|
return
|
|
}
|
|
if err == sql.ErrNoRows { // having an empty DB is not an issue
|
|
err = fmt.Errorf("no private key found for migscheduler")
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetSchedulerInvestigator returns the first active scheduler investigator
|
|
func (db *DB) GetSchedulerInvestigator() (inv mig.Investigator, err error) {
|
|
err = db.c.QueryRow(`SELECT id, name, pgpfingerprint, publickey, status, createdat, lastmodified
|
|
FROM investigators WHERE name ='migscheduler' AND status='active' ORDER BY id ASC LIMIT 1`,
|
|
).Scan(&inv.ID, &inv.Name, &inv.PGPFingerprint, &inv.PublicKey, &inv.Status, &inv.CreatedAt, &inv.LastModified)
|
|
if err != nil {
|
|
err = fmt.Errorf("Error while retrieving scheduler investigator: '%v'", err)
|
|
return
|
|
}
|
|
if err == sql.ErrNoRows {
|
|
return
|
|
}
|
|
return
|
|
}
|