manager.go 18 KB

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