userstate.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. package permissionsql
  2. import (
  3. "errors"
  4. "fmt"
  5. "math/rand"
  6. "net/http"
  7. "time"
  8. "github.com/xyproto/cookie" // For cookies
  9. "github.com/xyproto/pinterface" // For interfaces
  10. db "github.com/xyproto/simplemaria" // MariaDB/MySQL database wrapper
  11. )
  12. const (
  13. // username:password@host:port/database
  14. defaultConnectionString = "localhost:3306/"
  15. )
  16. var (
  17. minConfirmationCodeLength = 20 // minimum length of the confirmation code
  18. ErrCookieGetUsername = errors.New("Could not retrieve the username from browser cookie")
  19. ErrCookieEmptyUsername = errors.New("Can't set cookie for empty username")
  20. ErrCookieUserMissing = errors.New("Can't store cookie for non-existsing user")
  21. ErrOutOfConfirmationCodes = errors.New("Too many generated confirmation codes are not unique")
  22. ErrAllUsersConfirmedAlready = errors.New("All existing users are already confirmed")
  23. ErrConfirmationCodeExpired = errors.New("The confirmation code is no longer valid")
  24. ErrMissingUserAtConfirm = errors.New("The user that is to be confirmed no longer exists")
  25. ErrInvalidCharacters = errors.New("Only letters, numbers and underscore are allowed in usernames")
  26. ErrUsernameAsPassword = errors.New("Username and password must be different, try another password")
  27. )
  28. type UserState struct {
  29. users *db.HashMap // Hash map of users, with several different fields per user ("loggedin", "confirmed", "email" etc)
  30. usernames *db.Set // A list of all usernames, for easy enumeration
  31. unconfirmed *db.Set // A list of unconfirmed usernames, for easy enumeration
  32. host *db.Host // A database host
  33. cookieSecret string // Secret for storing secure cookies
  34. cookieTime int64 // How long a cookie should last, in seconds
  35. passwordAlgorithm string // The hashing algorithm to utilize default: "bcrypt+" allowed: ("sha256", "bcrypt", "bcrypt+")
  36. }
  37. // Create a new *UserState that can be used for managing users.
  38. // The random number generator will be seeded after generating the cookie secret.
  39. // A Host* for the local MariaDB/MySQL server will be created.
  40. func NewUserStateSimple() (*UserState, error) {
  41. // connection string | initialize random generator after generating the cookie secret
  42. return NewUserState(defaultConnectionString, true)
  43. }
  44. // Create a new *UserState that can be used for managing users.
  45. // connectionString may be on the form "username:password@host:port/database".
  46. // If randomseed is true, the random number generator will be seeded after generating the cookie secret (true is a good default value).
  47. func NewUserStateWithDSN(connectionString string, database_name string, randomseed bool) (*UserState, error) {
  48. // Test connection
  49. if err := db.TestConnectionHostWithDSN(connectionString); err != nil {
  50. return nil, err
  51. }
  52. host := db.NewHostWithDSN(connectionString, database_name)
  53. state := new(UserState)
  54. var err error
  55. state.users, err = db.NewHashMap(host, "users")
  56. if err != nil {
  57. return nil, err
  58. }
  59. state.usernames, err = db.NewSet(host, "usernames")
  60. if err != nil {
  61. return nil, err
  62. }
  63. state.unconfirmed, err = db.NewSet(host, "unconfirmed")
  64. if err != nil {
  65. return nil, err
  66. }
  67. state.host = host
  68. // For the secure cookies
  69. // This must happen before the random seeding, or
  70. // else people will have to log in again after every server restart
  71. state.cookieSecret = cookie.RandomCookieFriendlyString(30)
  72. // Seed the random number generator
  73. if randomseed {
  74. rand.Seed(time.Now().UnixNano())
  75. }
  76. // Cookies lasts for 24 hours by default. Specified in seconds.
  77. state.cookieTime = 3600 * 24
  78. // Default password hashing algorithm is "bcrypt+", which is the same as
  79. // "bcrypt", but with backwards compatibility for checking sha256 hashes.
  80. state.passwordAlgorithm = "bcrypt+" // "bcrypt+", "bcrypt" or "sha256"
  81. if err := host.Ping(); err != nil {
  82. defer host.Close()
  83. return nil, fmt.Errorf("Error when pinging %s: %s", connectionString, err)
  84. }
  85. return state, nil
  86. }
  87. // Create a new *UserState that can be used for managing users.
  88. // connectionString may be on the form "username:password@host:port/database".
  89. // If randomseed is true, the random number generator will be seeded after generating the cookie secret (true is a good default value).
  90. func NewUserState(connectionString string, randomseed bool) (*UserState, error) {
  91. // Test connection
  92. if err := db.TestConnectionHost(connectionString); err != nil {
  93. return nil, err
  94. }
  95. host := db.NewHost(connectionString)
  96. state := new(UserState)
  97. var err error
  98. state.users, err = db.NewHashMap(host, "users")
  99. if err != nil {
  100. return nil, err
  101. }
  102. state.usernames, err = db.NewSet(host, "usernames")
  103. if err != nil {
  104. return nil, err
  105. }
  106. state.unconfirmed, err = db.NewSet(host, "unconfirmed")
  107. if err != nil {
  108. return nil, err
  109. }
  110. state.host = host
  111. // For the secure cookies
  112. // This must happen before the random seeding, or
  113. // else people will have to log in again after every server restart
  114. state.cookieSecret = cookie.RandomCookieFriendlyString(30)
  115. // Seed the random number generator
  116. if randomseed {
  117. rand.Seed(time.Now().UnixNano())
  118. }
  119. // Cookies lasts for 24 hours by default. Specified in seconds.
  120. state.cookieTime = 3600 * 24
  121. // Default password hashing algorithm is "bcrypt+", which is the same as
  122. // "bcrypt", but with backwards compatibility for checking sha256 hashes.
  123. state.passwordAlgorithm = "bcrypt+" // "bcrypt+", "bcrypt" or "sha256"
  124. if err := host.Ping(); err != nil {
  125. defer host.Close()
  126. return nil, fmt.Errorf("Error when pinging %s: %s", connectionString, err)
  127. }
  128. return state, nil
  129. }
  130. // Get the database host
  131. func (state *UserState) Host() pinterface.IHost {
  132. return state.host
  133. }
  134. // Close the connection to the database host
  135. func (state *UserState) Close() {
  136. state.host.Close()
  137. }
  138. // Check if the current user is logged in and has user rights.
  139. func (state *UserState) UserRights(req *http.Request) bool {
  140. username, err := state.UsernameCookie(req)
  141. if err != nil {
  142. return false
  143. }
  144. return state.IsLoggedIn(username)
  145. }
  146. // Check if the given username exists.
  147. func (state *UserState) HasUser(username string) bool {
  148. val, err := state.usernames.Has(username)
  149. if err != nil {
  150. // This happened at concurrent connections before introducing the connection pool
  151. panic("ERROR: Lost connection to database?")
  152. }
  153. return val
  154. }
  155. // Return the boolean value for a given username and fieldname.
  156. // If the user or field is missing, false will be returned.
  157. // Useful for states where it makes sense that the returned value is not true
  158. // unless everything is in order.
  159. func (state *UserState) BooleanField(username, fieldname string) bool {
  160. hasUser := state.HasUser(username)
  161. if !hasUser {
  162. return false
  163. }
  164. value, err := state.users.Get(username, fieldname)
  165. if err != nil {
  166. return false
  167. }
  168. return value == "true"
  169. }
  170. // Store a boolean value for the given username and custom fieldname.
  171. func (state *UserState) SetBooleanField(username, fieldname string, val bool) {
  172. strval := "false"
  173. if val {
  174. strval = "true"
  175. }
  176. state.users.Set(username, fieldname, strval)
  177. }
  178. // Check if the given username is confirmed.
  179. func (state *UserState) IsConfirmed(username string) bool {
  180. return state.BooleanField(username, "confirmed")
  181. }
  182. // Checks if the given username is logged in.
  183. func (state *UserState) IsLoggedIn(username string) bool {
  184. if !state.HasUser(username) {
  185. return false
  186. }
  187. status, err := state.users.Get(username, "loggedin")
  188. if err != nil {
  189. // Returns "no" if the status can not be retrieved
  190. return false
  191. }
  192. return status == "true"
  193. }
  194. // Check if the current user is logged in and has administrator rights.
  195. func (state *UserState) AdminRights(req *http.Request) bool {
  196. username, err := state.UsernameCookie(req)
  197. if err != nil {
  198. return false
  199. }
  200. return state.IsLoggedIn(username) && state.IsAdmin(username)
  201. }
  202. // Check if the given username is an administrator.
  203. func (state *UserState) IsAdmin(username string) bool {
  204. if !state.HasUser(username) {
  205. return false
  206. }
  207. status, err := state.users.Get(username, "admin")
  208. if err != nil {
  209. return false
  210. }
  211. return status == "true"
  212. }
  213. // Retrieve the username that is stored in a cookie in the browser, if available.
  214. func (state *UserState) UsernameCookie(req *http.Request) (string, error) {
  215. username, ok := cookie.SecureCookie(req, "user", state.cookieSecret)
  216. if ok && (username != "") {
  217. return username, nil
  218. }
  219. return "", ErrCookieGetUsername
  220. }
  221. // Store the given username in a cookie in the browser, if possible.
  222. // The user must exist.
  223. func (state *UserState) SetUsernameCookie(w http.ResponseWriter, username string) error {
  224. if username == "" {
  225. return ErrCookieEmptyUsername
  226. }
  227. if !state.HasUser(username) {
  228. return ErrCookieUserMissing
  229. }
  230. // Create a cookie that lasts for a while ("timeout" seconds),
  231. // this is the equivivalent of a session for a given username.
  232. cookie.SetSecureCookiePath(w, "user", username, state.cookieTime, "/", state.cookieSecret)
  233. return nil
  234. }
  235. // Get a list of all usernames.
  236. func (state *UserState) AllUsernames() ([]string, error) {
  237. return state.usernames.GetAll()
  238. }
  239. // Get the email for the given username.
  240. func (state *UserState) Email(username string) (string, error) {
  241. return state.users.Get(username, "email")
  242. }
  243. // Get the password hash for the given username.
  244. func (state *UserState) PasswordHash(username string) (string, error) {
  245. return state.users.Get(username, "password")
  246. }
  247. // Get all registered users that are not yet confirmed.
  248. func (state *UserState) AllUnconfirmedUsernames() ([]string, error) {
  249. return state.unconfirmed.GetAll()
  250. }
  251. // Get the confirmation code for a specific user.
  252. func (state *UserState) ConfirmationCode(username string) (string, error) {
  253. return state.users.Get(username, "confirmationCode")
  254. }
  255. // Get the users HashMap.
  256. func (state *UserState) Users() pinterface.IHashMap {
  257. return state.users
  258. }
  259. // Add a user that is registered but not confirmed.
  260. func (state *UserState) AddUnconfirmed(username, confirmationCode string) {
  261. state.unconfirmed.Add(username)
  262. state.users.Set(username, "confirmationCode", confirmationCode)
  263. }
  264. // Remove a user that is registered but not confirmed.
  265. func (state *UserState) RemoveUnconfirmed(username string) {
  266. state.unconfirmed.Del(username)
  267. state.users.DelKey(username, "confirmationCode")
  268. }
  269. // Mark a user as confirmed.
  270. func (state *UserState) MarkConfirmed(username string) {
  271. state.users.Set(username, "confirmed", "true")
  272. }
  273. // Remove user and login status.
  274. func (state *UserState) RemoveUser(username string) {
  275. state.usernames.Del(username)
  276. // Remove additional data as well
  277. //state.users.DelKey(username, "loggedin")
  278. state.users.Del(username)
  279. }
  280. // Mark user as an administrator.
  281. func (state *UserState) SetAdminStatus(username string) {
  282. state.users.Set(username, "admin", "true")
  283. }
  284. // Mark user as a regular user.
  285. func (state *UserState) RemoveAdminStatus(username string) {
  286. state.users.Set(username, "admin", "false")
  287. }
  288. // Creates a user from the username and password hash, does not check for rights.
  289. func (state *UserState) addUserUnchecked(username, passwordHash, email string) {
  290. // Add the user
  291. state.usernames.Add(username)
  292. // Add password and email
  293. state.users.Set(username, "password", passwordHash)
  294. state.users.Set(username, "email", email)
  295. // Addditional fields
  296. additionalfields := []string{"loggedin", "confirmed", "admin"}
  297. for _, fieldname := range additionalfields {
  298. state.users.Set(username, fieldname, "false")
  299. }
  300. }
  301. // Creates a user and hashes the password, does not check for rights.
  302. // The given data must be valid.
  303. func (state *UserState) AddUser(username, password, email string) {
  304. passwordHash := state.HashPassword(username, password)
  305. state.addUserUnchecked(username, passwordHash, email)
  306. }
  307. // Mark the user as logged in. Use the Login function instead, unless cookies are not involved.
  308. func (state *UserState) SetLoggedIn(username string) {
  309. state.users.Set(username, "loggedin", "true")
  310. }
  311. // Mark the user as logged out.
  312. func (state *UserState) SetLoggedOut(username string) {
  313. state.users.Set(username, "loggedin", "false")
  314. }
  315. // Convenience function for logging a user in and storing the username in a cookie.
  316. // Returns an error if the cookie could not be set.
  317. func (state *UserState) Login(w http.ResponseWriter, username string) error {
  318. state.SetLoggedIn(username)
  319. return state.SetUsernameCookie(w, username)
  320. }
  321. // Try to clear the user cookie by setting it to expired.
  322. // Some browsers *may* be configured to keep cookies even after this.
  323. func (state *UserState) ClearCookie(w http.ResponseWriter) {
  324. cookie.ClearCookie(w, "user", "/")
  325. }
  326. // Convenience function for logging a user out.
  327. func (state *UserState) Logout(username string) {
  328. state.SetLoggedOut(username)
  329. }
  330. // Convenience function that will return a username (from the browser cookie) or an empty string.
  331. func (state *UserState) Username(req *http.Request) string {
  332. username, err := state.UsernameCookie(req)
  333. if err != nil {
  334. return ""
  335. }
  336. return username
  337. }
  338. // Get how long a login cookie should last, in seconds.
  339. func (state *UserState) CookieTimeout(username string) int64 {
  340. return state.cookieTime
  341. }
  342. // Set how long a login cookie should last, in seconds.
  343. func (state *UserState) SetCookieTimeout(cookieTime int64) {
  344. state.cookieTime = cookieTime
  345. }
  346. // CookieSecret returns the current cookie secret
  347. func (state *UserState) CookieSecret() string {
  348. return state.cookieSecret
  349. }
  350. // SetCookieSecret sets the current cookie secret
  351. func (state *UserState) SetCookieSecret(cookieSecret string) {
  352. state.cookieSecret = cookieSecret
  353. }
  354. // PasswordAlgo returns the current password hashing algorithm.
  355. func (state *UserState) PasswordAlgo() string {
  356. return state.passwordAlgorithm
  357. }
  358. // Set the password hashing algorithm that should be used.
  359. // The default is "bcrypt+".
  360. // Possible values are:
  361. // bcrypt -> Store and check passwords with the bcrypt hash.
  362. // sha256 -> Store and check passwords with the sha256 hash.
  363. // bcrypt+ -> Store passwords with bcrypt, but check with both
  364. // bcrypt and sha256, for backwards compatibility
  365. // with old passwords that has been stored as sha256.
  366. func (state *UserState) SetPasswordAlgo(algorithm string) error {
  367. switch algorithm {
  368. case "sha256", "bcrypt", "bcrypt+":
  369. state.passwordAlgorithm = algorithm
  370. default:
  371. return errors.New("Permissions: " + algorithm + " is an unsupported encryption algorithm")
  372. }
  373. return nil
  374. }
  375. // Hash the password (takes a username as well, it can be used for salting).
  376. func (state *UserState) HashPassword(username, password string) string {
  377. switch state.passwordAlgorithm {
  378. case "sha256":
  379. return string(hash_sha256(state.cookieSecret, username, password))
  380. case "bcrypt", "bcrypt+":
  381. return string(hash_bcrypt(password))
  382. }
  383. // Only valid password algorithms should be allowed to set
  384. return ""
  385. }
  386. // SetPassword sets/changes the password for a user.
  387. // Does not take a password hash, will hash the password string.
  388. func (state *UserState) SetPassword(username, password string) {
  389. state.users.Set(username, "password", state.HashPassword(username, password))
  390. }
  391. // Return the stored hash, or an empty byte slice.
  392. func (state *UserState) stored_hash(username string) []byte {
  393. hashString, err := state.PasswordHash(username)
  394. if err != nil {
  395. return []byte{}
  396. }
  397. return []byte(hashString)
  398. }
  399. // Check if a password is correct. username is needed because it is part of the hash.
  400. func (state *UserState) CorrectPassword(username, password string) bool {
  401. if !state.HasUser(username) {
  402. return false
  403. }
  404. // Retrieve the stored password hash
  405. hash := state.stored_hash(username)
  406. if len(hash) == 0 {
  407. return false
  408. }
  409. // Check the password with the right password algorithm
  410. switch state.passwordAlgorithm {
  411. case "sha256":
  412. return correct_sha256(hash, state.cookieSecret, username, password)
  413. case "bcrypt":
  414. return correct_bcrypt(hash, password)
  415. case "bcrypt+": // for backwards compatibility with sha256
  416. if is_sha256(hash) && correct_sha256(hash, state.cookieSecret, username, password) {
  417. return true
  418. }
  419. return correct_bcrypt(hash, password)
  420. }
  421. return false
  422. }
  423. // Goes through all the confirmationCodes of all the unconfirmed users
  424. // and checks if this confirmationCode already is in use.
  425. func (state *UserState) AlreadyHasConfirmationCode(confirmationCode string) bool {
  426. unconfirmedUsernames, err := state.AllUnconfirmedUsernames()
  427. if err != nil {
  428. return false
  429. }
  430. for _, aUsername := range unconfirmedUsernames {
  431. aConfirmationCode, err := state.ConfirmationCode(aUsername)
  432. if err != nil {
  433. // If the confirmation code can not be found, that's okay too
  434. return false
  435. }
  436. if confirmationCode == aConfirmationCode {
  437. // Found it
  438. return true
  439. }
  440. }
  441. return false
  442. }
  443. // Given a unique confirmation code, find the corresponding username.
  444. func (state *UserState) FindUserByConfirmationCode(confirmationcode string) (string, error) {
  445. unconfirmedUsernames, err := state.AllUnconfirmedUsernames()
  446. if err != nil {
  447. return "", ErrAllUsersConfirmedAlready
  448. }
  449. // Find the username by looking up the confirmationcode on unconfirmed users
  450. username := ""
  451. for _, aUsername := range unconfirmedUsernames {
  452. aConfirmationCode, err := state.ConfirmationCode(aUsername)
  453. if err != nil {
  454. // If the confirmation code can not be found, just skip this one
  455. continue
  456. }
  457. if confirmationcode == aConfirmationCode {
  458. // Found the right user
  459. username = aUsername
  460. break
  461. }
  462. }
  463. // Check that the user is there
  464. if username == "" {
  465. return username, ErrConfirmationCodeExpired
  466. }
  467. if !state.HasUser(username) {
  468. return username, ErrMissingUserAtConfirm
  469. }
  470. return username, nil
  471. }
  472. // Remove the username from the list of unconfirmed users and mark the user as confirmed.
  473. func (state *UserState) Confirm(username string) {
  474. // Remove from the list of unconfirmed usernames
  475. state.RemoveUnconfirmed(username)
  476. // Mark user as confirmed
  477. state.MarkConfirmed(username)
  478. }
  479. // Take a confirmation code and mark the corresponding unconfirmed user as confirmed.
  480. func (state *UserState) ConfirmUserByConfirmationCode(confirmationcode string) error {
  481. username, err := state.FindUserByConfirmationCode(confirmationcode)
  482. if err != nil {
  483. return err
  484. }
  485. state.Confirm(username)
  486. return nil
  487. }
  488. // Set the minimum length of the user confirmation code. The default is 20.
  489. func (state *UserState) SetMinimumConfirmationCodeLength(length int) {
  490. minConfirmationCodeLength = length
  491. }
  492. // Generate a unique confirmation code that can be used for confirming users.
  493. func (state *UserState) GenerateUniqueConfirmationCode() (string, error) {
  494. const maxConfirmationCodeLength = 100 // when are the generated confirmation codes unreasonably long
  495. length := minConfirmationCodeLength
  496. confirmationCode := cookie.RandomHumanFriendlyString(length)
  497. for state.AlreadyHasConfirmationCode(confirmationCode) {
  498. // Increase the length of the confirmationCode random string every time there is a collision
  499. length++
  500. confirmationCode = cookie.RandomHumanFriendlyString(length)
  501. if length > maxConfirmationCodeLength {
  502. // This should never happen
  503. return confirmationCode, ErrOutOfConfirmationCodes
  504. }
  505. }
  506. return confirmationCode, nil
  507. }
  508. // Check that the given username and password are different.
  509. // Also check if the chosen username only contains letters, numbers and/or underscore.
  510. // Use the "CorrectPassword" function for checking if the password is correct.
  511. func ValidUsernamePassword(username, password string) error {
  512. const allowed_letters = "abcdefghijklmnopqrstuvwxyzæøåABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ_0123456789"
  513. NEXT:
  514. for _, letter := range username {
  515. for _, allowedLetter := range allowed_letters {
  516. if letter == allowedLetter {
  517. continue NEXT // check the next letter in the username
  518. }
  519. }
  520. return ErrInvalidCharacters
  521. }
  522. if username == password {
  523. return ErrUsernameAsPassword
  524. }
  525. return nil
  526. }
  527. // Return a struct for creating datastructures
  528. func (state *UserState) Creator() pinterface.ICreator {
  529. return db.NewCreator(state.host)
  530. }