userdb_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. * Public Domain Software
  3. *
  4. * I (Matthias Ladkau) am the author of the source code in this file.
  5. * I have placed the source code in this file in the public domain.
  6. *
  7. * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. package datautil
  10. import (
  11. "fmt"
  12. "path"
  13. "testing"
  14. )
  15. func TestUserDB(t *testing.T) {
  16. // Create user DB instance and store a credential
  17. ud, err := NewUserDB(path.Join(testdbdir, "testuserdb"), "test123")
  18. if err != nil {
  19. t.Error(err)
  20. return
  21. }
  22. err = ud.AddUserEntry("fred", "s3cret", map[string]interface{}{
  23. "field1": "foo",
  24. "field2": 2,
  25. })
  26. if err != nil {
  27. t.Error(err)
  28. return
  29. }
  30. // Create a second user DB instance
  31. ud2, err := NewUserDB(path.Join(testdbdir, "testuserdb"), "test123")
  32. if err != nil {
  33. t.Error(err)
  34. return
  35. }
  36. // Check that the data was loaded
  37. if res := fmt.Sprint(ud2.AllUsers()); res != "[fred]" {
  38. t.Error("Unexpected result:", res)
  39. return
  40. }
  41. // Get the user data
  42. data, ok := ud2.UserData("fred")
  43. if !ok || data["field1"] != "foo" || data["field2"] != 2 {
  44. t.Error("Unexpected result:", ok, data)
  45. return
  46. }
  47. // Check user password
  48. if ok := ud2.CheckUserPassword("fred", "s3cret"); !ok || err != nil {
  49. t.Error("Unexpected result:", ok, err)
  50. return
  51. }
  52. if ok := ud2.CheckUserPassword("fred", "s4cret"); ok || err != nil {
  53. t.Error("Unexpected result:", ok, err)
  54. return
  55. }
  56. // Change data and password
  57. err = ud2.UpdateUserPassword("fred", "secret55")
  58. if err != nil {
  59. t.Error(err)
  60. return
  61. }
  62. err = ud2.UpdateUserData("fred", map[string]interface{}{
  63. "field5": "bar",
  64. "field2": 2,
  65. })
  66. if err != nil {
  67. t.Error(err)
  68. return
  69. }
  70. // ... and another instance
  71. ud3, err := NewUserDB(path.Join(testdbdir, "testuserdb"), "test123")
  72. if err != nil {
  73. t.Error(err)
  74. return
  75. }
  76. // Check that all updated information are correct
  77. data, ok = ud3.UserData("fred")
  78. if !ok || data["field5"] != "bar" || data["field2"] != 2 {
  79. t.Error("Unexpected result:", ok, data)
  80. return
  81. }
  82. // Check user password
  83. if ok := ud3.CheckUserPassword("fred", "s3cret"); ok || err != nil {
  84. t.Error("Unexpected result:", ok, err)
  85. return
  86. }
  87. if ok := ud3.CheckUserPassword("fred", "secret55"); !ok || err != nil {
  88. t.Error("Unexpected result:", ok, err)
  89. return
  90. }
  91. // Remove now the entry
  92. ud3.RemoveUserEntry("fred")
  93. ud4, err := NewUserDB(path.Join(testdbdir, "testuserdb"), "test123")
  94. if err != nil {
  95. t.Error(err)
  96. return
  97. }
  98. // Check that the data was removed
  99. if res := fmt.Sprint(ud4.AllUsers()); res != "[]" {
  100. t.Error("Unexpected result:", res)
  101. return
  102. }
  103. }
  104. func TestUserDBPasswordHistory(t *testing.T) {
  105. oldMaxPassHistory := MaxPassHistory
  106. MaxPassHistory = 3
  107. defer func() {
  108. MaxPassHistory = oldMaxPassHistory
  109. }()
  110. // Create user DB instance and store a credential
  111. ud, err := NewUserDB(path.Join(testdbdir, "testuserdbhistory"), "test123")
  112. if err != nil {
  113. t.Error(err)
  114. return
  115. }
  116. if err = ud.AddUserEntry("fred", "s3cret1", nil); err != nil {
  117. t.Error(err)
  118. return
  119. }
  120. if len(ud.Data["fred"].PasshashHistory) != 0 {
  121. t.Error("Unexpected result:", ud.Data["fred"].PasshashHistory)
  122. return
  123. }
  124. if err = ud.UpdateUserPassword("fred", "s3cret1"); err.Error() != "Cannot reuse current password" {
  125. t.Error(err)
  126. return
  127. }
  128. if err = ud.UpdateUserPassword("fred", "s3cret2"); err != nil {
  129. t.Error(err)
  130. return
  131. }
  132. if len(ud.Data["fred"].PasshashHistory) != 1 {
  133. t.Error("Unexpected result:", ud.Data["fred"].PasshashHistory)
  134. return
  135. }
  136. if ok := ud.CheckUserPasswordHistory("fred", "s3cret1"); !ok || err != nil {
  137. t.Error("Unexpected result")
  138. return
  139. }
  140. if ok := ud.CheckUserPasswordHistory("fred", "s3cret2"); ok || err != nil {
  141. t.Error("Unexpected result")
  142. return
  143. }
  144. ud.UpdateUserPassword("fred", "s3cret3")
  145. if ok := ud.CheckUserPasswordHistory("fred", "s3cret1"); !ok || err != nil {
  146. t.Error("Unexpected result")
  147. return
  148. }
  149. if ok := ud.CheckUserPasswordHistory("fred", "s3cret2"); !ok || err != nil {
  150. t.Error("Unexpected result")
  151. return
  152. }
  153. if len(ud.Data["fred"].PasshashHistory) != 2 {
  154. t.Error("Unexpected result:", ud.Data["fred"].PasshashHistory)
  155. return
  156. }
  157. ud.UpdateUserPassword("fred", "s3cret4")
  158. if ok := ud.CheckUserPasswordHistory("fred", "s3cret1"); !ok || err != nil {
  159. t.Error("Unexpected result")
  160. return
  161. }
  162. if ok := ud.CheckUserPasswordHistory("fred", "s3cret2"); !ok || err != nil {
  163. t.Error("Unexpected result")
  164. return
  165. }
  166. if ok := ud.CheckUserPasswordHistory("fred", "s3cret3"); !ok || err != nil {
  167. t.Error("Unexpected result")
  168. return
  169. }
  170. if len(ud.Data["fred"].PasshashHistory) != 3 {
  171. t.Error("Unexpected result:", ud.Data["fred"].PasshashHistory)
  172. return
  173. }
  174. ud.UpdateUserPassword("fred", "s3cret5")
  175. if ok := ud.CheckUserPasswordHistory("fred", "s3cret2"); !ok || err != nil {
  176. t.Error("Unexpected result")
  177. return
  178. }
  179. if ok := ud.CheckUserPasswordHistory("fred", "s3cret3"); !ok || err != nil {
  180. t.Error("Unexpected result")
  181. return
  182. }
  183. if ok := ud.CheckUserPasswordHistory("fred", "s3cret4"); !ok || err != nil {
  184. t.Error("Unexpected result")
  185. return
  186. }
  187. if len(ud.Data["fred"].PasshashHistory) != 3 {
  188. t.Error("Unexpected result:", ud.Data["fred"].PasshashHistory)
  189. return
  190. }
  191. if ok := ud.CheckUserPasswordHistory("fred", "s3cret1"); ok || err != nil {
  192. t.Error("Unexpected result")
  193. return
  194. }
  195. ud.UpdateUserPassword("fred", "s3cret6")
  196. if ok := ud.CheckUserPasswordHistory("fred", "s3cret3"); !ok || err != nil {
  197. t.Error("Unexpected result")
  198. return
  199. }
  200. if ok := ud.CheckUserPasswordHistory("fred", "s3cret4"); !ok || err != nil {
  201. t.Error("Unexpected result")
  202. return
  203. }
  204. if ok := ud.CheckUserPasswordHistory("fred", "s3cret5"); !ok || err != nil {
  205. t.Error("Unexpected result")
  206. return
  207. }
  208. if len(ud.Data["fred"].PasshashHistory) != 3 {
  209. t.Error("Unexpected result:", ud.Data["fred"].PasshashHistory)
  210. return
  211. }
  212. if ok := ud.CheckUserPasswordHistory("fred", "s3cret2"); ok || err != nil {
  213. t.Error("Unexpected result")
  214. return
  215. }
  216. if ok := ud.CheckUserPasswordHistory("fred", "s3cret6"); ok || err != nil {
  217. t.Error("Unexpected result")
  218. return
  219. }
  220. if ok := ud.CheckUserPassword("fred", "s3cret6"); !ok || err != nil {
  221. t.Error("Unexpected result")
  222. return
  223. }
  224. }
  225. func TestUserDBErrorCases(t *testing.T) {
  226. ud, err := NewUserDB(path.Join(testdbdir, invalidFileName), "test123")
  227. if err == nil || ud != nil {
  228. t.Error("Unexpected result:", err, ud)
  229. return
  230. }
  231. ud, err = NewUserDB(path.Join(testdbdir, "errtest"), "test123")
  232. if err != nil {
  233. t.Error(err)
  234. return
  235. }
  236. err = ud.AddUserEntry("foo", "bar", nil)
  237. if err != nil {
  238. t.Error(err)
  239. return
  240. }
  241. err = ud.AddUserEntry("foo", "bar", nil)
  242. if err == nil || err.Error() != "User foo already exists" {
  243. t.Error(err)
  244. return
  245. }
  246. err = ud.UpdateUserData("fred", nil)
  247. if err == nil || err.Error() != "Unknown user fred" {
  248. t.Error(err)
  249. return
  250. }
  251. err = ud.UpdateUserPassword("fred", "")
  252. if err == nil || err.Error() != "Unknown user fred" {
  253. t.Error(err)
  254. return
  255. }
  256. err = ud.RemoveUserEntry("fred")
  257. if err == nil || err.Error() != "Unknown user fred" {
  258. t.Error(err)
  259. return
  260. }
  261. }
  262. func TestEnforcedUserDB(t *testing.T) {
  263. // Create user DB instance and store a credential
  264. eud, err := NewEnforcedUserDB(path.Join(testdbdir, "testenforceuserdb"), "test123")
  265. if err != nil {
  266. t.Error(err)
  267. return
  268. }
  269. eud.SetPasswordCheckParam("NotContainSequence", false)
  270. if err := eud.AddUserEntry("fritz", "#Secr3taaa", nil); err != nil {
  271. t.Error(err)
  272. return
  273. }
  274. if eud.UserExists("foo") {
  275. t.Error("User foo should not exist")
  276. return
  277. }
  278. if !eud.UserExists("fritz") {
  279. t.Error("User fritz should exist")
  280. return
  281. }
  282. eud.SetPasswordCheckParam("NotContainSequence", true)
  283. if res := len(eud.PasswordCheckParams()); res != 8 {
  284. t.Error("Unexpected result:", res)
  285. return
  286. }
  287. if err := eud.UpdateUserPassword("fritz", "#Secr3tbbb"); err.Error() != "Password must not contain a same character sequence" {
  288. t.Error(err)
  289. return
  290. }
  291. if err := eud.UpdateUserPassword("fritz", "#Secr3tabc"); err != nil {
  292. t.Error(err)
  293. return
  294. }
  295. if err := eud.UpdateUserPassword("fritz", "#Secr3taaa"); err.Error() != "Password was used before within the last 10 changes; Password must not contain a same character sequence" {
  296. t.Error(err)
  297. return
  298. }
  299. if err := eud.AddUserEntry("hans", "aaa", nil); err.Error() != "Password matches a common dictionary password; Password must be at least 8 characters long; Password must contain an upper case character; Password must contain a number; Password must contain a special character; Password must not contain a same character sequence" {
  300. t.Error(err)
  301. return
  302. }
  303. // Test multiple errors
  304. if err := eud.UpdateUserPassword("fritz", "aaa"); err == nil || err.Error() != "Password matches a common dictionary password; Password must be at least 8 characters long; Password must contain an upper case character; Password must contain a number; Password must contain a special character; Password must not contain a same character sequence" {
  305. t.Error(err)
  306. return
  307. }
  308. if err := eud.IsAcceptablePassword("fritz", "#Secr3tabc"); err == nil || err.Error() != "Cannot reuse current password" {
  309. t.Error(err)
  310. return
  311. }
  312. if err := eud.IsAcceptablePassword("fritz", "AA1"); err == nil || err.Error() != "Password is too similar to the common dictionary password aa1234 (50% match); Password must be at least 8 characters long; Password must contain a lower case character; Password must contain a special character" {
  313. t.Error(err)
  314. return
  315. }
  316. if err := eud.IsAcceptablePassword("fritz", "xxx"); err == nil || err.Error() != "Password must be at least 8 characters long; Password must contain an upper case character; Password must contain a number; Password must contain a special character; Password must not contain a same character sequence" {
  317. t.Error(err)
  318. return
  319. }
  320. if err := eud.IsAcceptablePassword("fritz", "AA2"); err == nil || err.Error() != "Password is too similar to the common dictionary password aaa (66% match); Password must be at least 8 characters long; Password must contain a lower case character; Password must contain a special character" {
  321. t.Error(err)
  322. return
  323. }
  324. if err := eud.IsAcceptablePassword("fritz", "Test1234#"); err == nil || err.Error() != "Password is too similar to the common dictionary password test12345 (88% match)" {
  325. t.Error(err)
  326. return
  327. }
  328. if err := eud.IsAcceptablePassword("fritz", "#Test1234"); err == nil || err.Error() != "Password is too similar to the common dictionary password test1234 (88% match)" {
  329. t.Error(err)
  330. return
  331. }
  332. // Test EvalPasswordStrength
  333. if score, warn, err := eud.EvalPasswordStrength("fritz", "aaa"); fmt.Sprintf("%v#%v#%v", score, warn, err) != "0#[]#Password matches a common dictionary password; Password must be at least 8 characters long; Password must contain an upper case character; Password must contain a number; Password must contain a special character; Password must not contain a same character sequence" {
  334. t.Error("Unexpected result:", fmt.Sprintf("%v#%v#%v", score, warn, err))
  335. return
  336. }
  337. if score, warn, err := eud.EvalPasswordStrength("fritz", "#Secr3ttest"); fmt.Sprintf("%v#%v#%v", score, warn, err) != "1#[Password should be at least 12 characters long Password should contain at least 2 upper case characters Password should contain at least 2 numbers Password should contain at least 2 special characters Password is vaguely similar to the common dictionary password secre (45% match)]#<nil>" {
  338. t.Error("Unexpected result:", fmt.Sprintf("%v#%v#%v", score, warn, err))
  339. return
  340. }
  341. if score, warn, err := eud.EvalPasswordStrength("fritz", "#SECR3TTEsT"); fmt.Sprintf("%v#%v#%v", score, warn, err) != "1#[Password should be at least 12 characters long Password should contain at least 2 lower case characters Password should contain at least 2 numbers Password should contain at least 2 special characters Password is vaguely similar to the common dictionary password secre (45% match)]#<nil>" {
  342. t.Error("Unexpected result:", fmt.Sprintf("%v#%v#%v", score, warn, err))
  343. return
  344. }
  345. if score, warn, err := eud.EvalPasswordStrength("fritz", "#ArchBoo0815!"); fmt.Sprintf("%v#%v#%v", score, warn, err) != "10#[]#<nil>" {
  346. t.Error("Unexpected result:", fmt.Sprintf("%v#%v#%v", score, warn, err))
  347. return
  348. }
  349. }
  350. func TestDictPasswordDetection(t *testing.T) {
  351. // No match
  352. match, word, dist := CheckForDictPassword("ZYxzzyxzzy55xz#")
  353. if res := fmt.Sprintf("%v#%v#%v", match, word, dist); res != "false##-1" {
  354. t.Error("Unexpected result:", res)
  355. return
  356. }
  357. // Direct match
  358. match, word, dist = CheckForDictPassword("fireball")
  359. if res := fmt.Sprintf("%v#%v#%v", match, word, dist); res != "true#fireball#0" {
  360. t.Error("Unexpected result:", res)
  361. return
  362. }
  363. // Partial match
  364. match, word, dist = CheckForDictPassword("testfire")
  365. if res := fmt.Sprintf("%v#%v#%v", match, word, dist); res != "false#testibil#4" {
  366. t.Error("Unexpected result:", res)
  367. return
  368. }
  369. match, word, dist = CheckForDictPassword("tuberbla")
  370. if res := fmt.Sprintf("%v#%v#%v", match, word, dist); res != "false#erbol#5" {
  371. t.Error("Unexpected result:", res)
  372. return
  373. }
  374. }