manager.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. package user
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. _ "github.com/mattn/go-sqlite3" // SQLite driver
  8. "golang.org/x/crypto/bcrypt"
  9. "heckel.io/ntfy/log"
  10. "heckel.io/ntfy/util"
  11. "strings"
  12. "sync"
  13. "time"
  14. )
  15. const (
  16. bcryptCost = 10
  17. intentionalSlowDownHash = "$2a$10$YFCQvqQDwIIwnJM1xkAYOeih0dg17UVGanaTStnrSzC8NCWxcLDwy" // Cost should match bcryptCost
  18. userStatsQueueWriterInterval = 33 * time.Second
  19. tokenLength = 32
  20. tokenExpiryDuration = 72 * time.Hour // Extend tokens by this much
  21. tokenMaxCount = 10 // Only keep this many tokens in the table per user
  22. )
  23. var (
  24. errNoTokenProvided = errors.New("no token provided")
  25. errTopicOwnedByOthers = errors.New("topic owned by others")
  26. errNoRows = errors.New("no rows found")
  27. )
  28. // Manager-related queries
  29. const (
  30. createTablesQueriesNoTx = `
  31. CREATE TABLE IF NOT EXISTS tier (
  32. id INTEGER PRIMARY KEY AUTOINCREMENT,
  33. code TEXT NOT NULL,
  34. messages_limit INT NOT NULL,
  35. messages_expiry_duration INT NOT NULL,
  36. emails_limit INT NOT NULL,
  37. reservations_limit INT NOT NULL,
  38. attachment_file_size_limit INT NOT NULL,
  39. attachment_total_size_limit INT NOT NULL,
  40. attachment_expiry_duration INT NOT NULL
  41. );
  42. CREATE TABLE IF NOT EXISTS user (
  43. id INTEGER PRIMARY KEY AUTOINCREMENT,
  44. tier_id INT,
  45. user TEXT NOT NULL,
  46. pass TEXT NOT NULL,
  47. role TEXT NOT NULL,
  48. messages INT NOT NULL DEFAULT (0),
  49. emails INT NOT NULL DEFAULT (0),
  50. settings JSON,
  51. FOREIGN KEY (tier_id) REFERENCES tier (id)
  52. );
  53. CREATE UNIQUE INDEX idx_user ON user (user);
  54. CREATE TABLE IF NOT EXISTS user_access (
  55. user_id INT NOT NULL,
  56. topic TEXT NOT NULL,
  57. read INT NOT NULL,
  58. write INT NOT NULL,
  59. owner_user_id INT,
  60. PRIMARY KEY (user_id, topic),
  61. FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE,
  62. FOREIGN KEY (owner_user_id) REFERENCES user (id) ON DELETE CASCADE
  63. );
  64. CREATE TABLE IF NOT EXISTS user_token (
  65. user_id INT NOT NULL,
  66. token TEXT NOT NULL,
  67. expires INT NOT NULL,
  68. PRIMARY KEY (user_id, token),
  69. FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
  70. );
  71. CREATE TABLE IF NOT EXISTS schemaVersion (
  72. id INT PRIMARY KEY,
  73. version INT NOT NULL
  74. );
  75. INSERT INTO user (id, user, pass, role) VALUES (1, '*', '', 'anonymous') ON CONFLICT (id) DO NOTHING;
  76. `
  77. createTablesQueries = `BEGIN; ` + createTablesQueriesNoTx + ` COMMIT;`
  78. builtinStartupQueries = `
  79. PRAGMA foreign_keys = ON;
  80. `
  81. selectUserByNameQuery = `
  82. SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.messages_expiry_duration, p.emails_limit, p.reservations_limit, p.attachment_file_size_limit, p.attachment_total_size_limit, p.attachment_expiry_duration
  83. FROM user u
  84. LEFT JOIN tier p on p.id = u.tier_id
  85. WHERE user = ?
  86. `
  87. selectUserByTokenQuery = `
  88. SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.messages_expiry_duration, p.emails_limit, p.reservations_limit, p.attachment_file_size_limit, p.attachment_total_size_limit, p.attachment_expiry_duration
  89. FROM user u
  90. JOIN user_token t on u.id = t.user_id
  91. LEFT JOIN tier p on p.id = u.tier_id
  92. WHERE t.token = ? AND t.expires >= ?
  93. `
  94. selectTopicPermsQuery = `
  95. SELECT read, write
  96. FROM user_access a
  97. JOIN user u ON u.id = a.user_id
  98. WHERE (u.user = ? OR u.user = ?) AND ? LIKE a.topic
  99. ORDER BY u.user DESC
  100. `
  101. insertUserQuery = `INSERT INTO user (user, pass, role) VALUES (?, ?, ?)`
  102. selectUsernamesQuery = `
  103. SELECT user
  104. FROM user
  105. ORDER BY
  106. CASE role
  107. WHEN 'admin' THEN 1
  108. WHEN 'anonymous' THEN 3
  109. ELSE 2
  110. END, user
  111. `
  112. updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
  113. updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
  114. updateUserSettingsQuery = `UPDATE user SET settings = ? WHERE user = ?`
  115. updateUserStatsQuery = `UPDATE user SET messages = ?, emails = ? WHERE user = ?`
  116. deleteUserQuery = `DELETE FROM user WHERE user = ?`
  117. upsertUserAccessQuery = `
  118. INSERT INTO user_access (user_id, topic, read, write, owner_user_id)
  119. VALUES ((SELECT id FROM user WHERE user = ?), ?, ?, ?, (SELECT IIF(?='',NULL,(SELECT id FROM user WHERE user=?))))
  120. ON CONFLICT (user_id, topic)
  121. DO UPDATE SET read=excluded.read, write=excluded.write, owner_user_id=excluded.owner_user_id
  122. `
  123. selectUserAccessQuery = `
  124. SELECT topic, read, write
  125. FROM user_access
  126. WHERE user_id = (SELECT id FROM user WHERE user = ?)
  127. ORDER BY write DESC, read DESC, topic
  128. `
  129. selectUserReservationsQuery = `
  130. SELECT a_user.topic, a_user.read, a_user.write, a_everyone.read AS everyone_read, a_everyone.write AS everyone_write
  131. FROM user_access a_user
  132. LEFT JOIN user_access a_everyone ON a_user.topic = a_everyone.topic AND a_everyone.user_id = (SELECT id FROM user WHERE user = ?)
  133. WHERE a_user.user_id = a_user.owner_user_id
  134. AND a_user.owner_user_id = (SELECT id FROM user WHERE user = ?)
  135. ORDER BY a_user.topic
  136. `
  137. selectUserReservationsCountQuery = `
  138. SELECT COUNT(*)
  139. FROM user_access
  140. WHERE user_id = owner_user_id AND owner_user_id = (SELECT id FROM user WHERE user = ?)
  141. `
  142. selectUserHasReservationQuery = `
  143. SELECT COUNT(*)
  144. FROM user_access
  145. WHERE user_id = owner_user_id
  146. AND owner_user_id = (SELECT id FROM user WHERE user = ?)
  147. AND topic = ?
  148. `
  149. selectOtherAccessCountQuery = `
  150. SELECT COUNT(*)
  151. FROM user_access
  152. WHERE (topic = ? OR ? LIKE topic)
  153. AND (owner_user_id IS NULL OR owner_user_id != (SELECT id FROM user WHERE user = ?))
  154. `
  155. deleteAllAccessQuery = `DELETE FROM user_access`
  156. deleteUserAccessQuery = `DELETE FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?)`
  157. deleteTopicAccessQuery = `DELETE FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?) AND topic = ?`
  158. selectTokenCountQuery = `SELECT COUNT(*) FROM user_token WHERE (SELECT id FROM user WHERE user = ?)`
  159. insertTokenQuery = `INSERT INTO user_token (user_id, token, expires) VALUES ((SELECT id FROM user WHERE user = ?), ?, ?)`
  160. updateTokenExpiryQuery = `UPDATE user_token SET expires = ? WHERE user_id = (SELECT id FROM user WHERE user = ?) AND token = ?`
  161. deleteTokenQuery = `DELETE FROM user_token WHERE user_id = (SELECT id FROM user WHERE user = ?) AND token = ?`
  162. deleteExpiredTokensQuery = `DELETE FROM user_token WHERE expires < ?`
  163. deleteExcessTokensQuery = `
  164. DELETE FROM user_token
  165. WHERE (user_id, token) NOT IN (
  166. SELECT user_id, token
  167. FROM user_token
  168. WHERE user_id = (SELECT id FROM user WHERE user = ?)
  169. ORDER BY expires DESC
  170. LIMIT ?
  171. )
  172. `
  173. insertTierQuery = `
  174. INSERT INTO tier (code, messages_limit, messages_expiry_duration, emails_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration)
  175. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  176. `
  177. selectTierIDQuery = `SELECT id FROM tier WHERE code = ?`
  178. updateUserTierQuery = `UPDATE user SET tier_id = ? WHERE user = ?`
  179. )
  180. // Schema management queries
  181. const (
  182. currentSchemaVersion = 2
  183. insertSchemaVersion = `INSERT INTO schemaVersion VALUES (1, ?)`
  184. updateSchemaVersion = `UPDATE schemaVersion SET version = ? WHERE id = 1`
  185. selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
  186. // 1 -> 2 (complex migration!)
  187. migrate1To2RenameUserTableQueryNoTx = `
  188. ALTER TABLE user RENAME TO user_old;
  189. `
  190. migrate1To2InsertFromOldTablesAndDropNoTx = `
  191. INSERT INTO user (user, pass, role)
  192. SELECT user, pass, role FROM user_old;
  193. INSERT INTO user_access (user_id, topic, read, write)
  194. SELECT u.id, a.topic, a.read, a.write
  195. FROM user u
  196. JOIN access a ON u.user = a.user;
  197. DROP TABLE access;
  198. DROP TABLE user_old;
  199. `
  200. )
  201. // Manager is an implementation of Manager. It stores users and access control list
  202. // in a SQLite database.
  203. type Manager struct {
  204. db *sql.DB
  205. defaultAccess Permission // Default permission if no ACL matches
  206. statsQueue map[string]*User // Username -> User, for "unimportant" user updates
  207. mu sync.Mutex
  208. }
  209. var _ Auther = (*Manager)(nil)
  210. // NewManager creates a new Manager instance
  211. func NewManager(filename, startupQueries string, defaultAccess Permission) (*Manager, error) {
  212. return newManager(filename, startupQueries, defaultAccess, userStatsQueueWriterInterval)
  213. }
  214. // NewManager creates a new Manager instance
  215. func newManager(filename, startupQueries string, defaultAccess Permission, statsWriterInterval time.Duration) (*Manager, error) {
  216. db, err := sql.Open("sqlite3", filename)
  217. if err != nil {
  218. return nil, err
  219. }
  220. if err := setupDB(db); err != nil {
  221. return nil, err
  222. }
  223. if err := runStartupQueries(db, startupQueries); err != nil {
  224. return nil, err
  225. }
  226. manager := &Manager{
  227. db: db,
  228. defaultAccess: defaultAccess,
  229. statsQueue: make(map[string]*User),
  230. }
  231. go manager.userStatsQueueWriter(statsWriterInterval)
  232. return manager, nil
  233. }
  234. // Authenticate checks username and password and returns a User if correct. The method
  235. // returns in constant-ish time, regardless of whether the user exists or the password is
  236. // correct or incorrect.
  237. func (a *Manager) Authenticate(username, password string) (*User, error) {
  238. if username == Everyone {
  239. return nil, ErrUnauthenticated
  240. }
  241. user, err := a.User(username)
  242. if err != nil {
  243. log.Trace("authentication of user %s failed (1): %s", username, err.Error())
  244. bcrypt.CompareHashAndPassword([]byte(intentionalSlowDownHash), []byte("intentional slow-down to avoid timing attacks"))
  245. return nil, ErrUnauthenticated
  246. }
  247. if err := bcrypt.CompareHashAndPassword([]byte(user.Hash), []byte(password)); err != nil {
  248. log.Trace("authentication of user %s failed (2): %s", username, err.Error())
  249. return nil, ErrUnauthenticated
  250. }
  251. return user, nil
  252. }
  253. // AuthenticateToken checks if the token exists and returns the associated User if it does.
  254. // The method sets the User.Token value to the token that was used for authentication.
  255. func (a *Manager) AuthenticateToken(token string) (*User, error) {
  256. if len(token) != tokenLength {
  257. return nil, ErrUnauthenticated
  258. }
  259. user, err := a.userByToken(token)
  260. if err != nil {
  261. return nil, ErrUnauthenticated
  262. }
  263. user.Token = token
  264. return user, nil
  265. }
  266. // CreateToken generates a random token for the given user and returns it. The token expires
  267. // after a fixed duration unless ExtendToken is called. This function also prunes tokens for the
  268. // given user, if there are too many of them.
  269. func (a *Manager) CreateToken(user *User) (*Token, error) {
  270. token, expires := util.RandomString(tokenLength), time.Now().Add(tokenExpiryDuration)
  271. tx, err := a.db.Begin()
  272. if err != nil {
  273. return nil, err
  274. }
  275. defer tx.Rollback()
  276. if _, err := tx.Exec(insertTokenQuery, user.Name, token, expires.Unix()); err != nil {
  277. return nil, err
  278. }
  279. rows, err := tx.Query(selectTokenCountQuery, user.Name)
  280. if err != nil {
  281. return nil, err
  282. }
  283. defer rows.Close()
  284. if !rows.Next() {
  285. return nil, errNoRows
  286. }
  287. var tokenCount int
  288. if err := rows.Scan(&tokenCount); err != nil {
  289. return nil, err
  290. }
  291. if tokenCount >= tokenMaxCount {
  292. // This pruning logic is done in two queries for efficiency. The SELECT above is a lookup
  293. // on two indices, whereas the query below is a full table scan.
  294. if _, err := tx.Exec(deleteExcessTokensQuery, user.Name, tokenMaxCount); err != nil {
  295. return nil, err
  296. }
  297. }
  298. if err := tx.Commit(); err != nil {
  299. return nil, err
  300. }
  301. return &Token{
  302. Value: token,
  303. Expires: expires,
  304. }, nil
  305. }
  306. // ExtendToken sets the new expiry date for a token, thereby extending its use further into the future.
  307. func (a *Manager) ExtendToken(user *User) (*Token, error) {
  308. if user.Token == "" {
  309. return nil, errNoTokenProvided
  310. }
  311. newExpires := time.Now().Add(tokenExpiryDuration)
  312. if _, err := a.db.Exec(updateTokenExpiryQuery, newExpires.Unix(), user.Name, user.Token); err != nil {
  313. return nil, err
  314. }
  315. return &Token{
  316. Value: user.Token,
  317. Expires: newExpires,
  318. }, nil
  319. }
  320. // RemoveToken deletes the token defined in User.Token
  321. func (a *Manager) RemoveToken(user *User) error {
  322. if user.Token == "" {
  323. return ErrUnauthorized
  324. }
  325. if _, err := a.db.Exec(deleteTokenQuery, user.Name, user.Token); err != nil {
  326. return err
  327. }
  328. return nil
  329. }
  330. // RemoveExpiredTokens deletes all expired tokens from the database
  331. func (a *Manager) RemoveExpiredTokens() error {
  332. if _, err := a.db.Exec(deleteExpiredTokensQuery, time.Now().Unix()); err != nil {
  333. return err
  334. }
  335. return nil
  336. }
  337. // ChangeSettings persists the user settings
  338. func (a *Manager) ChangeSettings(user *User) error {
  339. settings, err := json.Marshal(user.Prefs)
  340. if err != nil {
  341. return err
  342. }
  343. if _, err := a.db.Exec(updateUserSettingsQuery, string(settings), user.Name); err != nil {
  344. return err
  345. }
  346. return nil
  347. }
  348. // EnqueueStats adds the user to a queue which writes out user stats (messages, emails, ..) in
  349. // batches at a regular interval
  350. func (a *Manager) EnqueueStats(user *User) {
  351. a.mu.Lock()
  352. defer a.mu.Unlock()
  353. a.statsQueue[user.Name] = user
  354. }
  355. func (a *Manager) userStatsQueueWriter(interval time.Duration) {
  356. ticker := time.NewTicker(interval)
  357. for range ticker.C {
  358. if err := a.writeUserStatsQueue(); err != nil {
  359. log.Warn("User Manager: Writing user stats queue failed: %s", err.Error())
  360. }
  361. }
  362. }
  363. func (a *Manager) writeUserStatsQueue() error {
  364. a.mu.Lock()
  365. if len(a.statsQueue) == 0 {
  366. a.mu.Unlock()
  367. log.Trace("User Manager: No user stats updates to commit")
  368. return nil
  369. }
  370. statsQueue := a.statsQueue
  371. a.statsQueue = make(map[string]*User)
  372. a.mu.Unlock()
  373. tx, err := a.db.Begin()
  374. if err != nil {
  375. return err
  376. }
  377. defer tx.Rollback()
  378. log.Debug("User Manager: Writing user stats queue for %d user(s)", len(statsQueue))
  379. for username, u := range statsQueue {
  380. log.Trace("User Manager: Updating stats for user %s: messages=%d, emails=%d", username, u.Stats.Messages, u.Stats.Emails)
  381. if _, err := tx.Exec(updateUserStatsQuery, u.Stats.Messages, u.Stats.Emails, username); err != nil {
  382. return err
  383. }
  384. }
  385. return tx.Commit()
  386. }
  387. // Authorize returns nil if the given user has access to the given topic using the desired
  388. // permission. The user param may be nil to signal an anonymous user.
  389. func (a *Manager) Authorize(user *User, topic string, perm Permission) error {
  390. if user != nil && user.Role == RoleAdmin {
  391. return nil // Admin can do everything
  392. }
  393. username := Everyone
  394. if user != nil {
  395. username = user.Name
  396. }
  397. // Select the read/write permissions for this user/topic combo. The query may return two
  398. // rows (one for everyone, and one for the user), but prioritizes the user.
  399. rows, err := a.db.Query(selectTopicPermsQuery, Everyone, username, topic)
  400. if err != nil {
  401. return err
  402. }
  403. defer rows.Close()
  404. if !rows.Next() {
  405. return a.resolvePerms(a.defaultAccess, perm)
  406. }
  407. var read, write bool
  408. if err := rows.Scan(&read, &write); err != nil {
  409. return err
  410. } else if err := rows.Err(); err != nil {
  411. return err
  412. }
  413. return a.resolvePerms(NewPermission(read, write), perm)
  414. }
  415. func (a *Manager) resolvePerms(base, perm Permission) error {
  416. if perm == PermissionRead && base.IsRead() {
  417. return nil
  418. } else if perm == PermissionWrite && base.IsWrite() {
  419. return nil
  420. }
  421. return ErrUnauthorized
  422. }
  423. // AddUser adds a user with the given username, password and role
  424. func (a *Manager) AddUser(username, password string, role Role) error {
  425. if !AllowedUsername(username) || !AllowedRole(role) {
  426. return ErrInvalidArgument
  427. }
  428. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
  429. if err != nil {
  430. return err
  431. }
  432. if _, err = a.db.Exec(insertUserQuery, username, hash, role); err != nil {
  433. return err
  434. }
  435. return nil
  436. }
  437. // RemoveUser deletes the user with the given username. The function returns nil on success, even
  438. // if the user did not exist in the first place.
  439. func (a *Manager) RemoveUser(username string) error {
  440. if !AllowedUsername(username) {
  441. return ErrInvalidArgument
  442. }
  443. // Rows in user_access, user_token, etc. are deleted via foreign keys
  444. if _, err := a.db.Exec(deleteUserQuery, username); err != nil {
  445. return err
  446. }
  447. return nil
  448. }
  449. // Users returns a list of users. It always also returns the Everyone user ("*").
  450. func (a *Manager) Users() ([]*User, error) {
  451. rows, err := a.db.Query(selectUsernamesQuery)
  452. if err != nil {
  453. return nil, err
  454. }
  455. defer rows.Close()
  456. usernames := make([]string, 0)
  457. for rows.Next() {
  458. var username string
  459. if err := rows.Scan(&username); err != nil {
  460. return nil, err
  461. } else if err := rows.Err(); err != nil {
  462. return nil, err
  463. }
  464. usernames = append(usernames, username)
  465. }
  466. rows.Close()
  467. users := make([]*User, 0)
  468. for _, username := range usernames {
  469. user, err := a.User(username)
  470. if err != nil {
  471. return nil, err
  472. }
  473. users = append(users, user)
  474. }
  475. return users, nil
  476. }
  477. // User returns the user with the given username if it exists, or ErrNotFound otherwise.
  478. // You may also pass Everyone to retrieve the anonymous user and its Grant list.
  479. func (a *Manager) User(username string) (*User, error) {
  480. rows, err := a.db.Query(selectUserByNameQuery, username)
  481. if err != nil {
  482. return nil, err
  483. }
  484. return a.readUser(rows)
  485. }
  486. func (a *Manager) userByToken(token string) (*User, error) {
  487. rows, err := a.db.Query(selectUserByTokenQuery, token, time.Now().Unix())
  488. if err != nil {
  489. return nil, err
  490. }
  491. return a.readUser(rows)
  492. }
  493. func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
  494. defer rows.Close()
  495. var username, hash, role string
  496. var settings, tierCode sql.NullString
  497. var messages, emails int64
  498. var messagesLimit, messagesExpiryDuration, emailsLimit, reservationsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit, attachmentExpiryDuration sql.NullInt64
  499. if !rows.Next() {
  500. return nil, ErrNotFound
  501. }
  502. if err := rows.Scan(&username, &hash, &role, &messages, &emails, &settings, &tierCode, &messagesLimit, &messagesExpiryDuration, &emailsLimit, &reservationsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit, &attachmentExpiryDuration); err != nil {
  503. return nil, err
  504. } else if err := rows.Err(); err != nil {
  505. return nil, err
  506. }
  507. user := &User{
  508. Name: username,
  509. Hash: hash,
  510. Role: Role(role),
  511. Stats: &Stats{
  512. Messages: messages,
  513. Emails: emails,
  514. },
  515. }
  516. if settings.Valid {
  517. user.Prefs = &Prefs{}
  518. if err := json.Unmarshal([]byte(settings.String), user.Prefs); err != nil {
  519. return nil, err
  520. }
  521. }
  522. if tierCode.Valid {
  523. user.Tier = &Tier{
  524. Code: tierCode.String,
  525. Upgradeable: false,
  526. MessagesLimit: messagesLimit.Int64,
  527. MessagesExpiryDuration: messagesExpiryDuration.Int64,
  528. EmailsLimit: emailsLimit.Int64,
  529. ReservationsLimit: reservationsLimit.Int64,
  530. AttachmentFileSizeLimit: attachmentFileSizeLimit.Int64,
  531. AttachmentTotalSizeLimit: attachmentTotalSizeLimit.Int64,
  532. AttachmentExpiryDuration: attachmentExpiryDuration.Int64,
  533. }
  534. }
  535. return user, nil
  536. }
  537. // Grants returns all user-specific access control entries
  538. func (a *Manager) Grants(username string) ([]Grant, error) {
  539. rows, err := a.db.Query(selectUserAccessQuery, username)
  540. if err != nil {
  541. return nil, err
  542. }
  543. defer rows.Close()
  544. grants := make([]Grant, 0)
  545. for rows.Next() {
  546. var topic string
  547. var read, write bool
  548. if err := rows.Scan(&topic, &read, &write); err != nil {
  549. return nil, err
  550. } else if err := rows.Err(); err != nil {
  551. return nil, err
  552. }
  553. grants = append(grants, Grant{
  554. TopicPattern: fromSQLWildcard(topic),
  555. Allow: NewPermission(read, write),
  556. })
  557. }
  558. return grants, nil
  559. }
  560. // Reservations returns all user-owned topics, and the associated everyone-access
  561. func (a *Manager) Reservations(username string) ([]Reservation, error) {
  562. rows, err := a.db.Query(selectUserReservationsQuery, Everyone, username)
  563. if err != nil {
  564. return nil, err
  565. }
  566. defer rows.Close()
  567. reservations := make([]Reservation, 0)
  568. for rows.Next() {
  569. var topic string
  570. var ownerRead, ownerWrite bool
  571. var everyoneRead, everyoneWrite sql.NullBool
  572. if err := rows.Scan(&topic, &ownerRead, &ownerWrite, &everyoneRead, &everyoneWrite); err != nil {
  573. return nil, err
  574. } else if err := rows.Err(); err != nil {
  575. return nil, err
  576. }
  577. reservations = append(reservations, Reservation{
  578. Topic: topic,
  579. Owner: NewPermission(ownerRead, ownerWrite),
  580. Everyone: NewPermission(everyoneRead.Bool, everyoneWrite.Bool), // false if null
  581. })
  582. }
  583. return reservations, nil
  584. }
  585. // HasReservation returns true if the given topic access is owned by the user
  586. func (a *Manager) HasReservation(username, topic string) (bool, error) {
  587. rows, err := a.db.Query(selectUserHasReservationQuery, username, topic)
  588. if err != nil {
  589. return false, err
  590. }
  591. defer rows.Close()
  592. if !rows.Next() {
  593. return false, errNoRows
  594. }
  595. var count int64
  596. if err := rows.Scan(&count); err != nil {
  597. return false, err
  598. }
  599. return count > 0, nil
  600. }
  601. // ReservationsCount returns the number of reservations owned by this user
  602. func (a *Manager) ReservationsCount(username string) (int64, error) {
  603. rows, err := a.db.Query(selectUserReservationsCountQuery, username)
  604. if err != nil {
  605. return 0, err
  606. }
  607. defer rows.Close()
  608. if !rows.Next() {
  609. return 0, errNoRows
  610. }
  611. var count int64
  612. if err := rows.Scan(&count); err != nil {
  613. return 0, err
  614. }
  615. return count, nil
  616. }
  617. // ChangePassword changes a user's password
  618. func (a *Manager) ChangePassword(username, password string) error {
  619. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
  620. if err != nil {
  621. return err
  622. }
  623. if _, err := a.db.Exec(updateUserPassQuery, hash, username); err != nil {
  624. return err
  625. }
  626. return nil
  627. }
  628. // ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin,
  629. // all existing access control entries (Grant) are removed, since they are no longer needed.
  630. func (a *Manager) ChangeRole(username string, role Role) error {
  631. if !AllowedUsername(username) || !AllowedRole(role) {
  632. return ErrInvalidArgument
  633. }
  634. if _, err := a.db.Exec(updateUserRoleQuery, string(role), username); err != nil {
  635. return err
  636. }
  637. if role == RoleAdmin {
  638. if _, err := a.db.Exec(deleteUserAccessQuery, username); err != nil {
  639. return err
  640. }
  641. }
  642. return nil
  643. }
  644. // ChangeTier changes a user's tier using the tier code
  645. func (a *Manager) ChangeTier(username, tier string) error {
  646. if !AllowedUsername(username) {
  647. return ErrInvalidArgument
  648. }
  649. rows, err := a.db.Query(selectTierIDQuery, tier)
  650. if err != nil {
  651. return err
  652. }
  653. defer rows.Close()
  654. if !rows.Next() {
  655. return ErrInvalidArgument
  656. }
  657. var tierID int64
  658. if err := rows.Scan(&tierID); err != nil {
  659. return err
  660. }
  661. rows.Close()
  662. if _, err := a.db.Exec(updateUserTierQuery, tierID, username); err != nil {
  663. return err
  664. }
  665. return nil
  666. }
  667. // CheckAllowAccess tests if a user may create an access control entry for the given topic.
  668. // If there are any ACL entries that are not owned by the user, an error is returned.
  669. func (a *Manager) CheckAllowAccess(username string, topic string) error {
  670. if (!AllowedUsername(username) && username != Everyone) || !AllowedTopic(topic) {
  671. return ErrInvalidArgument
  672. }
  673. rows, err := a.db.Query(selectOtherAccessCountQuery, topic, topic, username)
  674. if err != nil {
  675. return err
  676. }
  677. defer rows.Close()
  678. if !rows.Next() {
  679. return errNoRows
  680. }
  681. var otherCount int
  682. if err := rows.Scan(&otherCount); err != nil {
  683. return err
  684. }
  685. if otherCount > 0 {
  686. return errTopicOwnedByOthers
  687. }
  688. return nil
  689. }
  690. // AllowAccess adds or updates an entry in th access control list for a specific user. It controls
  691. // read/write access to a topic. The parameter topicPattern may include wildcards (*). The ACL entry
  692. // owner may either be a user (username), or the system (empty).
  693. func (a *Manager) AllowAccess(owner, username string, topicPattern string, read bool, write bool) error {
  694. if !AllowedUsername(username) && username != Everyone {
  695. return ErrInvalidArgument
  696. } else if owner != "" && !AllowedUsername(owner) {
  697. return ErrInvalidArgument
  698. } else if !AllowedTopicPattern(topicPattern) {
  699. return ErrInvalidArgument
  700. }
  701. if _, err := a.db.Exec(upsertUserAccessQuery, username, toSQLWildcard(topicPattern), read, write, owner, owner); err != nil {
  702. return err
  703. }
  704. return nil
  705. }
  706. // ResetAccess removes an access control list entry for a specific username/topic, or (if topic is
  707. // empty) for an entire user. The parameter topicPattern may include wildcards (*).
  708. func (a *Manager) ResetAccess(username string, topicPattern string) error {
  709. if !AllowedUsername(username) && username != Everyone && username != "" {
  710. return ErrInvalidArgument
  711. } else if !AllowedTopicPattern(topicPattern) && topicPattern != "" {
  712. return ErrInvalidArgument
  713. }
  714. if username == "" && topicPattern == "" {
  715. _, err := a.db.Exec(deleteAllAccessQuery, username)
  716. return err
  717. } else if topicPattern == "" {
  718. _, err := a.db.Exec(deleteUserAccessQuery, username)
  719. return err
  720. }
  721. _, err := a.db.Exec(deleteTopicAccessQuery, username, toSQLWildcard(topicPattern))
  722. return err
  723. }
  724. // DefaultAccess returns the default read/write access if no access control entry matches
  725. func (a *Manager) DefaultAccess() Permission {
  726. return a.defaultAccess
  727. }
  728. // CreateTier creates a new tier in the database
  729. func (a *Manager) CreateTier(tier *Tier) error {
  730. if _, err := a.db.Exec(insertTierQuery, tier.Code, tier.MessagesLimit, tier.MessagesExpiryDuration, tier.EmailsLimit, tier.ReservationsLimit, tier.AttachmentFileSizeLimit, tier.AttachmentTotalSizeLimit, tier.AttachmentExpiryDuration); err != nil {
  731. return err
  732. }
  733. return nil
  734. }
  735. func toSQLWildcard(s string) string {
  736. return strings.ReplaceAll(s, "*", "%")
  737. }
  738. func fromSQLWildcard(s string) string {
  739. return strings.ReplaceAll(s, "%", "*")
  740. }
  741. func runStartupQueries(db *sql.DB, startupQueries string) error {
  742. if _, err := db.Exec(startupQueries); err != nil {
  743. return err
  744. }
  745. if _, err := db.Exec(builtinStartupQueries); err != nil {
  746. return err
  747. }
  748. return nil
  749. }
  750. func setupDB(db *sql.DB) error {
  751. // If 'schemaVersion' table does not exist, this must be a new database
  752. rowsSV, err := db.Query(selectSchemaVersionQuery)
  753. if err != nil {
  754. return setupNewDB(db)
  755. }
  756. defer rowsSV.Close()
  757. // If 'schemaVersion' table exists, read version and potentially upgrade
  758. schemaVersion := 0
  759. if !rowsSV.Next() {
  760. return errors.New("cannot determine schema version: database file may be corrupt")
  761. }
  762. if err := rowsSV.Scan(&schemaVersion); err != nil {
  763. return err
  764. }
  765. rowsSV.Close()
  766. // Do migrations
  767. if schemaVersion == currentSchemaVersion {
  768. return nil
  769. } else if schemaVersion == 1 {
  770. return migrateFrom1(db)
  771. }
  772. return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
  773. }
  774. func setupNewDB(db *sql.DB) error {
  775. if _, err := db.Exec(createTablesQueries); err != nil {
  776. return err
  777. }
  778. if _, err := db.Exec(insertSchemaVersion, currentSchemaVersion); err != nil {
  779. return err
  780. }
  781. return nil
  782. }
  783. func migrateFrom1(db *sql.DB) error {
  784. log.Info("Migrating user database schema: from 1 to 2")
  785. tx, err := db.Begin()
  786. if err != nil {
  787. return err
  788. }
  789. defer tx.Rollback()
  790. if _, err := tx.Exec(migrate1To2RenameUserTableQueryNoTx); err != nil {
  791. return err
  792. }
  793. if _, err := tx.Exec(createTablesQueriesNoTx); err != nil {
  794. return err
  795. }
  796. if _, err := tx.Exec(migrate1To2InsertFromOldTablesAndDropNoTx); err != nil {
  797. return err
  798. }
  799. if _, err := tx.Exec(updateSchemaVersion, 2); err != nil {
  800. return err
  801. }
  802. if err := tx.Commit(); err != nil {
  803. return err
  804. }
  805. return nil // Update this when a new version is added
  806. }