manager.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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. tokenLength = 32
  17. bcryptCost = 10
  18. intentionalSlowDownHash = "$2a$10$YFCQvqQDwIIwnJM1xkAYOeih0dg17UVGanaTStnrSzC8NCWxcLDwy" // Cost should match bcryptCost
  19. userStatsQueueWriterInterval = 33 * time.Second
  20. userTokenExpiryDuration = 72 * time.Hour
  21. )
  22. // Manager-related queries
  23. const (
  24. createTablesQueriesNoTx = `
  25. CREATE TABLE IF NOT EXISTS plan (
  26. id INT NOT NULL,
  27. code TEXT NOT NULL,
  28. messages_limit INT NOT NULL,
  29. emails_limit INT NOT NULL,
  30. attachment_file_size_limit INT NOT NULL,
  31. attachment_total_size_limit INT NOT NULL,
  32. PRIMARY KEY (id)
  33. );
  34. CREATE TABLE IF NOT EXISTS user (
  35. id INTEGER PRIMARY KEY AUTOINCREMENT,
  36. plan_id INT,
  37. user TEXT NOT NULL,
  38. pass TEXT NOT NULL,
  39. role TEXT NOT NULL,
  40. messages INT NOT NULL DEFAULT (0),
  41. emails INT NOT NULL DEFAULT (0),
  42. settings JSON,
  43. FOREIGN KEY (plan_id) REFERENCES plan (id)
  44. );
  45. CREATE UNIQUE INDEX idx_user ON user (user);
  46. CREATE TABLE IF NOT EXISTS user_access (
  47. user_id INT NOT NULL,
  48. owner_user_id INT,
  49. topic TEXT NOT NULL,
  50. read INT NOT NULL,
  51. write INT NOT NULL,
  52. PRIMARY KEY (user_id, topic),
  53. FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
  54. );
  55. CREATE TABLE IF NOT EXISTS user_token (
  56. user_id INT NOT NULL,
  57. token TEXT NOT NULL,
  58. expires INT NOT NULL,
  59. PRIMARY KEY (user_id, token),
  60. FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
  61. );
  62. CREATE TABLE IF NOT EXISTS schemaVersion (
  63. id INT PRIMARY KEY,
  64. version INT NOT NULL
  65. );
  66. INSERT INTO user (id, user, pass, role) VALUES (1, '*', '', 'anonymous') ON CONFLICT (id) DO NOTHING;
  67. `
  68. createTablesQueries = `BEGIN; ` + createTablesQueriesNoTx + ` COMMIT;`
  69. selectUserByNameQuery = `
  70. SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.emails_limit, p.attachment_file_size_limit, p.attachment_total_size_limit
  71. FROM user u
  72. LEFT JOIN plan p on p.id = u.plan_id
  73. WHERE user = ?
  74. `
  75. selectUserByTokenQuery = `
  76. SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.emails_limit, p.attachment_file_size_limit, p.attachment_total_size_limit
  77. FROM user u
  78. JOIN user_token t on u.id = t.user_id
  79. LEFT JOIN plan p on p.id = u.plan_id
  80. WHERE t.token = ? AND t.expires >= ?
  81. `
  82. selectTopicPermsQuery = `
  83. SELECT read, write
  84. FROM user_access a
  85. JOIN user u ON u.id = a.user_id
  86. WHERE (u.user = '*' OR u.user = ?) AND ? LIKE a.topic
  87. ORDER BY u.user DESC
  88. `
  89. )
  90. // Manager-related queries
  91. const (
  92. insertUserQuery = `INSERT INTO user (user, pass, role) VALUES (?, ?, ?)`
  93. selectUsernamesQuery = `
  94. SELECT user
  95. FROM user
  96. ORDER BY
  97. CASE role
  98. WHEN 'admin' THEN 1
  99. WHEN 'anonymous' THEN 3
  100. ELSE 2
  101. END, user
  102. `
  103. updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
  104. updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
  105. updateUserSettingsQuery = `UPDATE user SET settings = ? WHERE user = ?`
  106. updateUserStatsQuery = `UPDATE user SET messages = ?, emails = ? WHERE user = ?`
  107. deleteUserQuery = `DELETE FROM user WHERE user = ?`
  108. upsertUserAccessQuery = `
  109. INSERT INTO user_access (user_id, topic, read, write)
  110. VALUES ((SELECT id FROM user WHERE user = ?), ?, ?, ?)
  111. ON CONFLICT (user_id, topic)
  112. DO UPDATE SET read=excluded.read, write=excluded.write
  113. `
  114. selectUserAccessQuery = `SELECT topic, read, write FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?) ORDER BY write DESC, read DESC, topic`
  115. deleteAllAccessQuery = `DELETE FROM user_access`
  116. deleteUserAccessQuery = `DELETE FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?)`
  117. deleteTopicAccessQuery = `DELETE FROM user_access WHERE user_id = (SELECT id FROM user WHERE user = ?) AND topic = ?`
  118. insertTokenQuery = `INSERT INTO user_token (user_id, token, expires) VALUES ((SELECT id FROM user WHERE user = ?), ?, ?)`
  119. updateTokenExpiryQuery = `UPDATE user_token SET expires = ? WHERE user_id = (SELECT id FROM user WHERE user = ?) AND token = ?`
  120. deleteTokenQuery = `DELETE FROM user_token WHERE user_id = (SELECT id FROM user WHERE user = ?) AND token = ?`
  121. deleteExpiredTokensQuery = `DELETE FROM user_token WHERE expires < ?`
  122. deleteUserTokensQuery = `DELETE FROM user_token WHERE user_id = (SELECT id FROM user WHERE user = ?)`
  123. )
  124. // Schema management queries
  125. const (
  126. currentSchemaVersion = 2
  127. insertSchemaVersion = `INSERT INTO schemaVersion VALUES (1, ?)`
  128. updateSchemaVersion = `UPDATE schemaVersion SET version = ? WHERE id = 1`
  129. selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
  130. // 1 -> 2 (complex migration!)
  131. migrate1To2RenameUserTableQueryNoTx = `
  132. ALTER TABLE user RENAME TO user_old;
  133. `
  134. migrate1To2InsertFromOldTablesAndDropNoTx = `
  135. INSERT INTO user (user, pass, role)
  136. SELECT user, pass, role FROM user_old;
  137. INSERT INTO user_access (user_id, topic, read, write)
  138. SELECT u.id, a.topic, a.read, a.write
  139. FROM user u
  140. JOIN access a ON u.user = a.user;
  141. DROP TABLE access;
  142. DROP TABLE user_old;
  143. `
  144. )
  145. // Manager is an implementation of Manager. It stores users and access control list
  146. // in a SQLite database.
  147. type Manager struct {
  148. db *sql.DB
  149. defaultRead bool // Default read permission if no ACL matches
  150. defaultWrite bool // Default write permission if no ACL matches
  151. statsQueue map[string]*User // Username -> User, for "unimportant" user updates
  152. tokenExpiryInterval time.Duration // Duration after which tokens expire, and by which tokens are extended
  153. mu sync.Mutex
  154. }
  155. var _ Auther = (*Manager)(nil)
  156. // NewManager creates a new Manager instance
  157. func NewManager(filename string, defaultRead, defaultWrite bool) (*Manager, error) {
  158. return newManager(filename, defaultRead, defaultWrite, userTokenExpiryDuration, userStatsQueueWriterInterval)
  159. }
  160. // NewManager creates a new Manager instance
  161. func newManager(filename string, defaultRead, defaultWrite bool, tokenExpiryDuration, statsWriterInterval time.Duration) (*Manager, error) {
  162. db, err := sql.Open("sqlite3", filename)
  163. if err != nil {
  164. return nil, err
  165. }
  166. if err := setupDB(db); err != nil {
  167. return nil, err
  168. }
  169. manager := &Manager{
  170. db: db,
  171. defaultRead: defaultRead,
  172. defaultWrite: defaultWrite,
  173. statsQueue: make(map[string]*User),
  174. tokenExpiryInterval: tokenExpiryDuration,
  175. }
  176. go manager.userStatsQueueWriter(statsWriterInterval)
  177. return manager, nil
  178. }
  179. // Authenticate checks username and password and returns a User if correct. The method
  180. // returns in constant-ish time, regardless of whether the user exists or the password is
  181. // correct or incorrect.
  182. func (a *Manager) Authenticate(username, password string) (*User, error) {
  183. if username == Everyone {
  184. return nil, ErrUnauthenticated
  185. }
  186. user, err := a.User(username)
  187. if err != nil {
  188. bcrypt.CompareHashAndPassword([]byte(intentionalSlowDownHash),
  189. []byte("intentional slow-down to avoid timing attacks"))
  190. return nil, ErrUnauthenticated
  191. }
  192. if err := bcrypt.CompareHashAndPassword([]byte(user.Hash), []byte(password)); err != nil {
  193. return nil, ErrUnauthenticated
  194. }
  195. return user, nil
  196. }
  197. // AuthenticateToken checks if the token exists and returns the associated User if it does.
  198. // The method sets the User.Token value to the token that was used for authentication.
  199. func (a *Manager) AuthenticateToken(token string) (*User, error) {
  200. if len(token) != tokenLength {
  201. return nil, ErrUnauthenticated
  202. }
  203. user, err := a.userByToken(token)
  204. if err != nil {
  205. return nil, ErrUnauthenticated
  206. }
  207. user.Token = token
  208. return user, nil
  209. }
  210. // CreateToken generates a random token for the given user and returns it. The token expires
  211. // after a fixed duration unless ExtendToken is called.
  212. func (a *Manager) CreateToken(user *User) (*Token, error) {
  213. token, expires := util.RandomString(tokenLength), time.Now().Add(userTokenExpiryDuration)
  214. if _, err := a.db.Exec(insertTokenQuery, user.Name, token, expires.Unix()); err != nil {
  215. return nil, err
  216. }
  217. return &Token{
  218. Value: token,
  219. Expires: expires,
  220. }, nil
  221. }
  222. // ExtendToken sets the new expiry date for a token, thereby extending its use further into the future.
  223. func (a *Manager) ExtendToken(user *User) (*Token, error) {
  224. newExpires := time.Now().Add(userTokenExpiryDuration)
  225. if _, err := a.db.Exec(updateTokenExpiryQuery, newExpires.Unix(), user.Name, user.Token); err != nil {
  226. return nil, err
  227. }
  228. return &Token{
  229. Value: user.Token,
  230. Expires: newExpires,
  231. }, nil
  232. }
  233. // RemoveToken deletes the token defined in User.Token
  234. func (a *Manager) RemoveToken(user *User) error {
  235. if user.Token == "" {
  236. return ErrUnauthorized
  237. }
  238. if _, err := a.db.Exec(deleteTokenQuery, user.Name, user.Token); err != nil {
  239. return err
  240. }
  241. return nil
  242. }
  243. // RemoveExpiredTokens deletes all expired tokens from the database
  244. func (a *Manager) RemoveExpiredTokens() error {
  245. if _, err := a.db.Exec(deleteExpiredTokensQuery, time.Now().Unix()); err != nil {
  246. return err
  247. }
  248. return nil
  249. }
  250. // ChangeSettings persists the user settings
  251. func (a *Manager) ChangeSettings(user *User) error {
  252. settings, err := json.Marshal(user.Prefs)
  253. if err != nil {
  254. return err
  255. }
  256. if _, err := a.db.Exec(updateUserSettingsQuery, string(settings), user.Name); err != nil {
  257. return err
  258. }
  259. return nil
  260. }
  261. // EnqueueStats adds the user to a queue which writes out user stats (messages, emails, ..) in
  262. // batches at a regular interval
  263. func (a *Manager) EnqueueStats(user *User) {
  264. a.mu.Lock()
  265. defer a.mu.Unlock()
  266. a.statsQueue[user.Name] = user
  267. }
  268. func (a *Manager) userStatsQueueWriter(interval time.Duration) {
  269. ticker := time.NewTicker(interval)
  270. for range ticker.C {
  271. if err := a.writeUserStatsQueue(); err != nil {
  272. log.Warn("UserManager: Writing user stats queue failed: %s", err.Error())
  273. }
  274. }
  275. }
  276. func (a *Manager) writeUserStatsQueue() error {
  277. a.mu.Lock()
  278. if len(a.statsQueue) == 0 {
  279. a.mu.Unlock()
  280. log.Trace("UserManager: No user stats updates to commit")
  281. return nil
  282. }
  283. statsQueue := a.statsQueue
  284. a.statsQueue = make(map[string]*User)
  285. a.mu.Unlock()
  286. tx, err := a.db.Begin()
  287. if err != nil {
  288. return err
  289. }
  290. defer tx.Rollback()
  291. log.Debug("UserManager: Writing user stats queue for %d user(s)", len(statsQueue))
  292. for username, u := range statsQueue {
  293. log.Trace("UserManager: Updating stats for user %s: messages=%d, emails=%d", username, u.Stats.Messages, u.Stats.Emails)
  294. if _, err := tx.Exec(updateUserStatsQuery, u.Stats.Messages, u.Stats.Emails, username); err != nil {
  295. return err
  296. }
  297. }
  298. return tx.Commit()
  299. }
  300. // Authorize returns nil if the given user has access to the given topic using the desired
  301. // permission. The user param may be nil to signal an anonymous user.
  302. func (a *Manager) Authorize(user *User, topic string, perm Permission) error {
  303. if user != nil && user.Role == RoleAdmin {
  304. return nil // Admin can do everything
  305. }
  306. username := Everyone
  307. if user != nil {
  308. username = user.Name
  309. }
  310. // Select the read/write permissions for this user/topic combo. The query may return two
  311. // rows (one for everyone, and one for the user), but prioritizes the user. The value for
  312. // user.Name may be empty (= everyone).
  313. rows, err := a.db.Query(selectTopicPermsQuery, username, topic)
  314. if err != nil {
  315. return err
  316. }
  317. defer rows.Close()
  318. if !rows.Next() {
  319. return a.resolvePerms(a.defaultRead, a.defaultWrite, perm)
  320. }
  321. var read, write bool
  322. if err := rows.Scan(&read, &write); err != nil {
  323. return err
  324. } else if err := rows.Err(); err != nil {
  325. return err
  326. }
  327. return a.resolvePerms(read, write, perm)
  328. }
  329. func (a *Manager) resolvePerms(read, write bool, perm Permission) error {
  330. if perm == PermissionRead && read {
  331. return nil
  332. } else if perm == PermissionWrite && write {
  333. return nil
  334. }
  335. return ErrUnauthorized
  336. }
  337. // AddUser adds a user with the given username, password and role
  338. func (a *Manager) AddUser(username, password string, role Role) error {
  339. if !AllowedUsername(username) || !AllowedRole(role) {
  340. return ErrInvalidArgument
  341. }
  342. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
  343. if err != nil {
  344. return err
  345. }
  346. if _, err = a.db.Exec(insertUserQuery, username, hash, role); err != nil {
  347. return err
  348. }
  349. return nil
  350. }
  351. // RemoveUser deletes the user with the given username. The function returns nil on success, even
  352. // if the user did not exist in the first place.
  353. func (a *Manager) RemoveUser(username string) error {
  354. if !AllowedUsername(username) {
  355. return ErrInvalidArgument
  356. }
  357. tx, err := a.db.Begin()
  358. if err != nil {
  359. return err
  360. }
  361. defer tx.Rollback()
  362. if _, err := tx.Exec(deleteUserAccessQuery, username); err != nil {
  363. return err
  364. }
  365. if _, err := tx.Exec(deleteUserTokensQuery, username); err != nil {
  366. return err
  367. }
  368. if _, err := tx.Exec(deleteUserQuery, username); err != nil {
  369. return err
  370. }
  371. return tx.Commit()
  372. }
  373. // Users returns a list of users. It always also returns the Everyone user ("*").
  374. func (a *Manager) Users() ([]*User, error) {
  375. rows, err := a.db.Query(selectUsernamesQuery)
  376. if err != nil {
  377. return nil, err
  378. }
  379. defer rows.Close()
  380. usernames := make([]string, 0)
  381. for rows.Next() {
  382. var username string
  383. if err := rows.Scan(&username); err != nil {
  384. return nil, err
  385. } else if err := rows.Err(); err != nil {
  386. return nil, err
  387. }
  388. usernames = append(usernames, username)
  389. }
  390. rows.Close()
  391. users := make([]*User, 0)
  392. for _, username := range usernames {
  393. user, err := a.User(username)
  394. if err != nil {
  395. return nil, err
  396. }
  397. users = append(users, user)
  398. }
  399. return users, nil
  400. }
  401. // User returns the user with the given username if it exists, or ErrNotFound otherwise.
  402. // You may also pass Everyone to retrieve the anonymous user and its Grant list.
  403. func (a *Manager) User(username string) (*User, error) {
  404. rows, err := a.db.Query(selectUserByNameQuery, username)
  405. if err != nil {
  406. return nil, err
  407. }
  408. return a.readUser(rows)
  409. }
  410. func (a *Manager) userByToken(token string) (*User, error) {
  411. rows, err := a.db.Query(selectUserByTokenQuery, token, time.Now().Unix())
  412. if err != nil {
  413. return nil, err
  414. }
  415. return a.readUser(rows)
  416. }
  417. func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
  418. defer rows.Close()
  419. var username, hash, role string
  420. var settings, planCode sql.NullString
  421. var messages, emails int64
  422. var messagesLimit, emailsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit sql.NullInt64
  423. if !rows.Next() {
  424. return nil, ErrNotFound
  425. }
  426. if err := rows.Scan(&username, &hash, &role, &messages, &emails, &settings, &planCode, &messagesLimit, &emailsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit); err != nil {
  427. return nil, err
  428. } else if err := rows.Err(); err != nil {
  429. return nil, err
  430. }
  431. grants, err := a.readGrants(username)
  432. if err != nil {
  433. return nil, err
  434. }
  435. user := &User{
  436. Name: username,
  437. Hash: hash,
  438. Role: Role(role),
  439. Grants: grants,
  440. Stats: &Stats{
  441. Messages: messages,
  442. Emails: emails,
  443. },
  444. }
  445. if settings.Valid {
  446. user.Prefs = &Prefs{}
  447. if err := json.Unmarshal([]byte(settings.String), user.Prefs); err != nil {
  448. return nil, err
  449. }
  450. }
  451. if planCode.Valid {
  452. user.Plan = &Plan{
  453. Code: planCode.String,
  454. Upgradable: true, // FIXME
  455. MessagesLimit: messagesLimit.Int64,
  456. EmailsLimit: emailsLimit.Int64,
  457. AttachmentFileSizeLimit: attachmentFileSizeLimit.Int64,
  458. AttachmentTotalSizeLimit: attachmentTotalSizeLimit.Int64,
  459. }
  460. }
  461. return user, nil
  462. }
  463. func (a *Manager) readGrants(username string) ([]Grant, error) {
  464. rows, err := a.db.Query(selectUserAccessQuery, username)
  465. if err != nil {
  466. return nil, err
  467. }
  468. defer rows.Close()
  469. grants := make([]Grant, 0)
  470. for rows.Next() {
  471. var topic string
  472. var read, write bool
  473. if err := rows.Scan(&topic, &read, &write); err != nil {
  474. return nil, err
  475. } else if err := rows.Err(); err != nil {
  476. return nil, err
  477. }
  478. grants = append(grants, Grant{
  479. TopicPattern: fromSQLWildcard(topic),
  480. AllowRead: read,
  481. AllowWrite: write,
  482. })
  483. }
  484. return grants, nil
  485. }
  486. // ChangePassword changes a user's password
  487. func (a *Manager) ChangePassword(username, password string) error {
  488. hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
  489. if err != nil {
  490. return err
  491. }
  492. if _, err := a.db.Exec(updateUserPassQuery, hash, username); err != nil {
  493. return err
  494. }
  495. return nil
  496. }
  497. // ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin,
  498. // all existing access control entries (Grant) are removed, since they are no longer needed.
  499. func (a *Manager) ChangeRole(username string, role Role) error {
  500. if !AllowedUsername(username) || !AllowedRole(role) {
  501. return ErrInvalidArgument
  502. }
  503. if _, err := a.db.Exec(updateUserRoleQuery, string(role), username); err != nil {
  504. return err
  505. }
  506. if role == RoleAdmin {
  507. if _, err := a.db.Exec(deleteUserAccessQuery, username); err != nil {
  508. return err
  509. }
  510. }
  511. return nil
  512. }
  513. // AllowAccess adds or updates an entry in th access control list for a specific user. It controls
  514. // read/write access to a topic. The parameter topicPattern may include wildcards (*).
  515. func (a *Manager) AllowAccess(username string, topicPattern string, read bool, write bool) error {
  516. if (!AllowedUsername(username) && username != Everyone) || !AllowedTopicPattern(topicPattern) {
  517. return ErrInvalidArgument
  518. }
  519. if _, err := a.db.Exec(upsertUserAccessQuery, username, toSQLWildcard(topicPattern), read, write); err != nil {
  520. return err
  521. }
  522. return nil
  523. }
  524. // ResetAccess removes an access control list entry for a specific username/topic, or (if topic is
  525. // empty) for an entire user. The parameter topicPattern may include wildcards (*).
  526. func (a *Manager) ResetAccess(username string, topicPattern string) error {
  527. if !AllowedUsername(username) && username != Everyone && username != "" {
  528. return ErrInvalidArgument
  529. } else if !AllowedTopicPattern(topicPattern) && topicPattern != "" {
  530. return ErrInvalidArgument
  531. }
  532. if username == "" && topicPattern == "" {
  533. _, err := a.db.Exec(deleteAllAccessQuery, username)
  534. return err
  535. } else if topicPattern == "" {
  536. _, err := a.db.Exec(deleteUserAccessQuery, username)
  537. return err
  538. }
  539. _, err := a.db.Exec(deleteTopicAccessQuery, username, toSQLWildcard(topicPattern))
  540. return err
  541. }
  542. // DefaultAccess returns the default read/write access if no access control entry matches
  543. func (a *Manager) DefaultAccess() (read bool, write bool) {
  544. return a.defaultRead, a.defaultWrite
  545. }
  546. func toSQLWildcard(s string) string {
  547. return strings.ReplaceAll(s, "*", "%")
  548. }
  549. func fromSQLWildcard(s string) string {
  550. return strings.ReplaceAll(s, "%", "*")
  551. }
  552. func setupDB(db *sql.DB) error {
  553. // If 'schemaVersion' table does not exist, this must be a new database
  554. rowsSV, err := db.Query(selectSchemaVersionQuery)
  555. if err != nil {
  556. return setupNewDB(db)
  557. }
  558. defer rowsSV.Close()
  559. // If 'schemaVersion' table exists, read version and potentially upgrade
  560. schemaVersion := 0
  561. if !rowsSV.Next() {
  562. return errors.New("cannot determine schema version: database file may be corrupt")
  563. }
  564. if err := rowsSV.Scan(&schemaVersion); err != nil {
  565. return err
  566. }
  567. rowsSV.Close()
  568. // Do migrations
  569. if schemaVersion == currentSchemaVersion {
  570. return nil
  571. } else if schemaVersion == 1 {
  572. return migrateFrom1(db)
  573. }
  574. return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
  575. }
  576. func setupNewDB(db *sql.DB) error {
  577. if _, err := db.Exec(createTablesQueries); err != nil {
  578. return err
  579. }
  580. if _, err := db.Exec(insertSchemaVersion, currentSchemaVersion); err != nil {
  581. return err
  582. }
  583. return nil
  584. }
  585. func migrateFrom1(db *sql.DB) error {
  586. log.Info("Migrating user database schema: from 1 to 2")
  587. tx, err := db.Begin()
  588. if err != nil {
  589. return err
  590. }
  591. defer tx.Rollback()
  592. if _, err := tx.Exec(migrate1To2RenameUserTableQueryNoTx); err != nil {
  593. return err
  594. }
  595. if _, err := tx.Exec(createTablesQueriesNoTx); err != nil {
  596. return err
  597. }
  598. if _, err := tx.Exec(migrate1To2InsertFromOldTablesAndDropNoTx); err != nil {
  599. return err
  600. }
  601. if _, err := tx.Exec(updateSchemaVersion, 2); err != nil {
  602. return err
  603. }
  604. if err := tx.Commit(); err != nil {
  605. return err
  606. }
  607. return nil // Update this when a new version is added
  608. }