manager.go 29 KB

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