manager.go 18 KB

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