github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/bitwarden/bitwarden_test.go (about) 1 package bitwarden 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "testing" 9 "time" 10 11 "github.com/cozy/cozy-stack/model/app" 12 "github.com/cozy/cozy-stack/model/bitwarden" 13 "github.com/cozy/cozy-stack/model/bitwarden/settings" 14 "github.com/cozy/cozy-stack/model/instance/lifecycle" 15 "github.com/cozy/cozy-stack/pkg/config/config" 16 "github.com/cozy/cozy-stack/pkg/consts" 17 "github.com/cozy/cozy-stack/pkg/couchdb" 18 "github.com/cozy/cozy-stack/pkg/crypto" 19 "github.com/cozy/cozy-stack/tests/testutils" 20 "github.com/cozy/cozy-stack/web/errors" 21 _ "github.com/cozy/cozy-stack/worker/mails" 22 "github.com/gavv/httpexpect/v2" 23 "github.com/labstack/echo/v4" 24 "github.com/sirupsen/logrus/hooks/test" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 ) 28 29 func TestBitwarden(t *testing.T) { 30 if testing.Short() { 31 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 32 } 33 34 var token, collID, orgaID, folderID, cipherID string 35 36 config.UseTestFile(t) 37 testutils.NeedCouchdb(t) 38 setup := testutils.NewSetup(t, t.Name()) 39 inst := setup.GetTestInstance(&lifecycle.Options{ 40 Domain: "bitwarden.example.net", 41 Passphrase: "cozy", 42 PublicName: "Pierre", 43 Email: "pierre@cozy.localhost", 44 }) 45 46 ts := setup.GetTestServer("/bitwarden", Routes) 47 ts.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler 48 49 // Install cozy-pass webapp (required for OAuth linked clients) 50 installer, err := app.NewInstaller(inst, app.Copier(consts.WebappType, inst), 51 &app.InstallerOptions{ 52 Operation: app.Install, 53 Type: consts.WebappType, 54 Slug: "passwords", 55 SourceURL: "registry://passwords", 56 Registries: inst.Registries(), 57 }, 58 ) 59 require.NoError(t, err) 60 61 _, err = installer.RunSync() 62 require.NoError(t, err) 63 64 t.Run("Prelogin", func(t *testing.T) { 65 e := testutils.CreateTestClient(t, ts.URL) 66 67 obj := e.POST("/bitwarden/api/accounts/prelogin"). 68 WithHeader("Content-Type", "application/json"). 69 WithBytes([]byte(`{ "email": "me@bitwarden.example.net" }`)). 70 Expect().Status(http.StatusOK). 71 JSON().Object() 72 73 obj.ValueEqual("Kdf", 0) 74 obj.ValueEqual("OIDC", false) 75 obj.ValueEqual("KdfIterations", crypto.DefaultPBKDF2Iterations) 76 obj.ValueEqual("HasCiphers", false) 77 }) 78 79 t.Run("Connect", func(t *testing.T) { 80 e := testutils.CreateTestClient(t, ts.URL) 81 82 testLogger := test.NewGlobal() 83 setting, err := settings.Get(inst) 84 assert.NoError(t, err) 85 setting.EncryptedOrgKey = "" 86 err = setting.Save(inst) 87 assert.NoError(t, err) 88 89 email := inst.PassphraseSalt() 90 iter := crypto.DefaultPBKDF2Iterations 91 pass, _ := crypto.HashPassWithPBKDF2([]byte("cozy"), email, iter) 92 93 obj := e.POST("/bitwarden/identity/connect/token"). 94 WithFormField("grant_type", "password"). 95 WithFormField("username", string(email)). 96 WithFormField("password", string(pass)). 97 WithFormField("scope", "api offline_access"). 98 WithFormField("client_id", "browser"). 99 WithFormField("deviceType", "3"). 100 Expect(). 101 Status(http.StatusOK). 102 JSON().Object() 103 104 obj.ValueEqual("token_type", "Bearer") 105 obj.ValueEqual("expires_in", consts.AccessTokenValidityDuration.Seconds()) 106 token = obj.Value("access_token").String().NotEmpty().Raw() 107 108 obj.Value("refresh_token").String().NotEmpty() 109 obj.Value("Key").String().NotEmpty() 110 obj.Value("PrivateKey").String().NotEmpty() 111 obj.Value("client_id").String().NotEmpty() 112 obj.Value("registration_access_token").String().NotEmpty() 113 obj.Value("Kdf").Number() 114 obj.Value("KdfIterations").Number() 115 116 entries := testLogger.AllEntries() 117 assert.NotZero(t, len(entries)) 118 orgKeyDoesNotExist := false 119 for _, entry := range entries { 120 if entry.Message == "Organization key does not exist" { 121 orgKeyDoesNotExist = true 122 } 123 } 124 assert.True(t, orgKeyDoesNotExist) 125 126 setting, err = settings.Get(inst) 127 assert.NoError(t, err) 128 orgKey, err := setting.OrganizationKey() 129 assert.NoError(t, err) 130 assert.NotEmpty(t, orgKey) 131 }) 132 133 t.Run("GetCozyOrg", func(t *testing.T) { 134 e := testutils.CreateTestClient(t, ts.URL) 135 136 e.GET("/bitwarden/organizations/cozy"). 137 WithHeader("Authorization", "Bearer invalid-token"). 138 Expect().Status(401) 139 140 obj := e.GET("/bitwarden/organizations/cozy"). 141 WithHeader("Authorization", "Bearer "+token). 142 Expect().Status(200). 143 JSON().Object() 144 145 orgaID = obj.Value("organizationId").String().NotEmpty().Raw() 146 collID = obj.Value("collectionId").String().NotEmpty().Raw() 147 orgKey := obj.Value("organizationKey").String().NotEmpty().Raw() 148 149 _, err := base64.StdEncoding.DecodeString(orgKey) 150 assert.NoError(t, err) 151 }) 152 153 t.Run("CreateFolder", func(t *testing.T) { 154 e := testutils.CreateTestClient(t, ts.URL) 155 156 obj := e.POST("/bitwarden/api/folders"). 157 WithHeader("Content-Type", "application/json"). 158 WithHeader("Authorization", "Bearer "+token). 159 WithBytes([]byte(`{ "name": "2.FQAwIBaDbczEGnEJw4g4hw==|7KreXaC0duAj0ulzZJ8ncA==|nu2sEvotjd4zusvGF8YZJPnS9SiJPDqc1VIfCrfve/o=" }`)). 160 Expect().Status(200). 161 JSON().Object() 162 163 obj.ValueEqual("Name", "2.FQAwIBaDbczEGnEJw4g4hw==|7KreXaC0duAj0ulzZJ8ncA==|nu2sEvotjd4zusvGF8YZJPnS9SiJPDqc1VIfCrfve/o=") 164 obj.ValueEqual("Object", "folder") 165 obj.Value("RevisionDate").String().DateTime(time.RFC3339) 166 167 folderID = obj.Value("Id").String().NotEmpty().Raw() 168 }) 169 170 t.Run("ListFolders", func(t *testing.T) { 171 e := testutils.CreateTestClient(t, ts.URL) 172 173 obj := e.GET("/bitwarden/api/folders"). 174 WithHeader("Authorization", "Bearer "+token). 175 Expect().Status(200). 176 JSON().Object() 177 178 obj.ValueEqual("Object", "list") 179 obj.Value("Data").Array().Length().Equal(1) 180 181 item := obj.Value("Data").Array().First().Object() 182 item.ValueEqual("Name", "2.FQAwIBaDbczEGnEJw4g4hw==|7KreXaC0duAj0ulzZJ8ncA==|nu2sEvotjd4zusvGF8YZJPnS9SiJPDqc1VIfCrfve/o=") 183 item.ValueEqual("Object", "folder") 184 item.ValueEqual("Id", folderID) 185 item.Value("RevisionDate").String().DateTime(time.RFC3339) 186 }) 187 188 t.Run("GetFolder", func(t *testing.T) { 189 e := testutils.CreateTestClient(t, ts.URL) 190 191 obj := e.GET("/bitwarden/api/folders/"+folderID). 192 WithHeader("Authorization", "Bearer "+token). 193 Expect().Status(200). 194 JSON().Object() 195 196 obj.ValueEqual("Name", "2.FQAwIBaDbczEGnEJw4g4hw==|7KreXaC0duAj0ulzZJ8ncA==|nu2sEvotjd4zusvGF8YZJPnS9SiJPDqc1VIfCrfve/o=") 197 obj.ValueEqual("Object", "folder") 198 obj.ValueEqual("Id", folderID) 199 obj.Value("RevisionDate").String().DateTime(time.RFC3339) 200 }) 201 202 t.Run("RenameFolder", func(t *testing.T) { 203 e := testutils.CreateTestClient(t, ts.URL) 204 205 obj := e.PUT("/bitwarden/api/folders/"+folderID). 206 WithHeader("Content-Type", "application/json"). 207 WithHeader("Authorization", "Bearer "+token). 208 WithBytes([]byte(`{ "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=" }`)). 209 Expect().Status(200). 210 JSON().Object() 211 212 obj.ValueEqual("Name", "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=") 213 obj.ValueEqual("Object", "folder") 214 obj.ValueEqual("Id", folderID) 215 obj.Value("RevisionDate").String().DateTime(time.RFC3339) 216 }) 217 218 t.Run("DeleteFolder", func(t *testing.T) { 219 e := testutils.CreateTestClient(t, ts.URL) 220 221 obj := e.POST("/bitwarden/api/folders"). 222 WithHeader("Content-Type", "application/json"). 223 WithHeader("Authorization", "Bearer "+token). 224 WithBytes([]byte(`{ "name": "2.FQAwIBaDbczEGnEJw4g4hw==|7KreXaC0duAj0ulzZJ8ncA==|nu2sEvotjd4zusvGF8YZJPnS9SiJPDqc1VIfCrfve/o=" }`)). 225 Expect().Status(200). 226 JSON().Object() 227 228 id := obj.Value("Id").String().NotEmpty().Raw() 229 230 obj = e.POST("/bitwarden/api/ciphers"). 231 WithHeader("Content-Type", "application/json"). 232 WithHeader("Authorization", "Bearer "+token). 233 WithBytes([]byte(`{ 234 "type": 1, 235 "favorite": false, 236 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 237 "notes": null, 238 "folderId": "` + id + `", 239 "organizationId": null, 240 "login": { 241 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 242 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 243 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 244 "totp": null 245 } 246 }`)). 247 Expect().Status(200). 248 JSON().Object() 249 250 cID := obj.Value("Id").String().NotEmpty().Raw() 251 252 e.DELETE("/bitwarden/api/folders/"+id). 253 WithHeader("Authorization", "Bearer "+token). 254 Expect().Status(200) 255 256 // Check that the cipher in this folder has been moved out 257 obj = e.GET("/bitwarden/api/ciphers/"+cID). 258 WithHeader("Authorization", "Bearer "+token). 259 Expect().Status(200). 260 JSON().Object() 261 262 obj.ValueEqual("Name", "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=") 263 obj.Value("FolderId").Null() // is empty 264 265 e.DELETE("/bitwarden/api/ciphers/"+cID). 266 WithHeader("Authorization", "Bearer "+token). 267 Expect().Status(200) 268 }) 269 270 t.Run("CreateNoType", func(t *testing.T) { 271 e := testutils.CreateTestClient(t, ts.URL) 272 273 obj := e.POST("/bitwarden/api/ciphers"). 274 WithHeader("Content-Type", "application/json"). 275 WithHeader("Authorization", "Bearer "+token). 276 WithBytes([]byte(`{ 277 "name": "2.G38TIU3t1pGOfkzjCQE7OQ==|Xa1RupttU7zrWdzIT6oK+w==|J3C6qU1xDrfTgyJD+OrDri1GjgGhU2nmRK75FbZHXoI=", 278 "organizationId": null 279 }`)). 280 Expect().Status(400). 281 JSON().Object() 282 283 obj.Value("error").String().NotEmpty() 284 }) 285 286 t.Run("CreateLogin", func(t *testing.T) { 287 e := testutils.CreateTestClient(t, ts.URL) 288 289 obj := e.POST("/bitwarden/api/ciphers"). 290 WithHeader("Content-Type", "application/json"). 291 WithHeader("Authorization", "Bearer "+token). 292 WithBytes([]byte(`{ 293 "type": 1, 294 "favorite": false, 295 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 296 "notes": null, 297 "folderId": null, 298 "organizationId": null, 299 "login": { 300 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 301 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 302 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 303 "passwordRevisionDate": "2019-09-13T12:26:42+02:00", 304 "totp": null 305 } 306 }`)). 307 Expect().Status(200). 308 JSON().Object() 309 310 assertCipherResponse(t, obj) 311 312 obj.Value("OrganizationId").Null() 313 cipherID = obj.Value("Id").String().NotEmpty().Raw() 314 }) 315 316 t.Run("ListCiphers", func(t *testing.T) { 317 e := testutils.CreateTestClient(t, ts.URL) 318 319 obj := e.GET("/bitwarden/api/ciphers"). 320 WithHeader("Authorization", "Bearer "+token). 321 Expect().Status(200). 322 JSON().Object() 323 324 obj.ValueEqual("Object", "list") 325 326 data := obj.Value("Data").Array() 327 data.Length().Equal(1) 328 329 assertCipherResponse(t, data.First().Object()) 330 331 data.First().Object().Value("OrganizationId").Null() 332 }) 333 334 t.Run("GetCipher", func(t *testing.T) { 335 e := testutils.CreateTestClient(t, ts.URL) 336 337 obj := e.GET("/bitwarden/api/ciphers/"+cipherID). 338 WithHeader("Authorization", "Bearer "+token). 339 Expect().Status(200). 340 JSON().Object() 341 342 assertCipherResponse(t, obj) 343 344 obj.Value("OrganizationId").Null() 345 }) 346 347 t.Run("UpdateCipher", func(t *testing.T) { 348 e := testutils.CreateTestClient(t, ts.URL) 349 350 obj := e.PUT("/bitwarden/api/ciphers/"+cipherID). 351 WithHeader("Content-Type", "application/json"). 352 WithHeader("Authorization", "Bearer "+token). 353 WithBytes([]byte(`{ 354 "type": 2, 355 "favorite": true, 356 "name": "2.G38TIU3t1pGOfkzjCQE7OQ==|Xa1RupttU7zrWdzIT6oK+w==|J3C6qU1xDrfTgyJD+OrDri1GjgGhU2nmRK75FbZHXoI=", 357 "folderId": "` + folderID + `", 358 "organizationId": null, 359 "notes": "2.rSw0uVQEFgUCEmOQx0JnDg==|MKqHLD25aqaXYHeYJPH/mor7l3EeSQKsI7A/R+0bFTI=|ODcUScISzKaZWHlUe4MRGuTT2S7jpyDmbOHl7d+6HiM=", 360 "secureNote": { 361 "type": 0 362 } 363 }`)). 364 Expect().Status(200). 365 JSON().Object() 366 367 assertUpdatedCipherResponse(t, obj, cipherID, folderID) 368 369 obj.Value("OrganizationId").Null() 370 }) 371 372 t.Run("DeleteCipher", func(t *testing.T) { 373 e := testutils.CreateTestClient(t, ts.URL) 374 375 obj := e.POST("/bitwarden/api/ciphers"). 376 WithHeader("Content-Type", "application/json"). 377 WithHeader("Authorization", "Bearer "+token). 378 WithBytes([]byte(`{ 379 "type": 1, 380 "favorite": false, 381 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 382 "notes": null, 383 "folderId": null, 384 "organizationId": null, 385 "login": { 386 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 387 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 388 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 389 "totp": null 390 } 391 }`)). 392 Expect().Status(200). 393 JSON().Object() 394 395 id := obj.Value("Id").String().NotEmpty().Raw() 396 397 e.DELETE("/bitwarden/api/ciphers/"+id). 398 WithHeader("Authorization", "Bearer "+token). 399 Expect().Status(200) 400 }) 401 402 t.Run("SoftDeleteCipher", func(t *testing.T) { 403 e := testutils.CreateTestClient(t, ts.URL) 404 405 obj := e.POST("/bitwarden/api/ciphers"). 406 WithHeader("Content-Type", "application/json"). 407 WithHeader("Authorization", "Bearer "+token). 408 WithBytes([]byte(`{ 409 "type": 1, 410 "favorite": false, 411 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 412 "notes": null, 413 "folderId": null, 414 "organizationId": null, 415 "login": { 416 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 417 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 418 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 419 "totp": null 420 } 421 }`)). 422 Expect().Status(200). 423 JSON().Object() 424 425 id := obj.Value("Id").String().NotEmpty().Raw() 426 427 e.PUT("/bitwarden/api/ciphers/"+id+"/delete"). 428 WithHeader("Authorization", "Bearer "+token). 429 Expect().Status(200) 430 431 obj = e.GET("/bitwarden/api/ciphers/"+id). 432 WithHeader("Authorization", "Bearer "+token). 433 Expect().Status(200). 434 JSON().Object() 435 436 obj.Value("DeletedDate").String().NotEmpty().DateTime(time.RFC3339) 437 }) 438 439 t.Run("RestoreCipher", func(t *testing.T) { 440 e := testutils.CreateTestClient(t, ts.URL) 441 442 obj := e.POST(ts.URL+"/bitwarden/api/ciphers"). 443 WithHeader("Content-Type", "application/json"). 444 WithHeader("Authorization", "Bearer "+token). 445 WithBytes([]byte(`{ 446 "type": 1, 447 "favorite": false, 448 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 449 "notes": null, 450 "folderId": null, 451 "organizationId": null, 452 "login": { 453 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 454 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 455 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 456 "totp": null 457 } 458 }`)). 459 Expect().Status(200). 460 JSON().Object() 461 462 id := obj.Value("Id").String().NotEmpty().Raw() 463 464 e.PUT(ts.URL+"/bitwarden/api/ciphers/"+id+"/delete"). 465 WithHeader("Authorization", "Bearer "+token). 466 Expect().Status(200) 467 468 e.PUT(ts.URL+"/bitwarden/api/ciphers/"+id+"/restore"). 469 WithHeader("Authorization", "Bearer "+token). 470 Expect().Status(200) 471 472 obj = e.GET(ts.URL+"/bitwarden/api/ciphers/"+id). 473 WithHeader("Authorization", "Bearer "+token). 474 Expect().Status(200). 475 JSON().Object() 476 477 obj.NotContainsKey("DeletedDate") 478 }) 479 480 t.Run("Sync", func(t *testing.T) { 481 e := testutils.CreateTestClient(t, ts.URL) 482 483 obj := e.GET("/bitwarden/api/sync"). 484 WithHeader("Authorization", "Bearer "+token). 485 Expect().Status(200). 486 JSON().Object() 487 488 obj.ValueEqual("Object", "sync") 489 490 profile := obj.Value("Profile").Object() 491 profile.Value("Id").NotNull() 492 profile.ValueEqual("Name", "Pierre") 493 profile.ValueEqual("Email", "me@bitwarden.example.net") 494 profile.ValueEqual("EmailVerified", false) 495 profile.ValueEqual("Premium", true) 496 profile.ValueEqual("MasterPasswordHint", nil) 497 profile.ValueEqual("Culture", "en") 498 profile.ValueEqual("TwoFactorEnabled", false) 499 profile.Value("Key").NotNull() 500 profile.Value("PrivateKey").NotNull() 501 profile.Value("SecurityStamp").NotNull() 502 profile.ValueEqual("Object", "profile") 503 504 ciphers := obj.Value("Ciphers").Array() 505 ciphers.Length().Equal(3) 506 assertUpdatedCipherResponse(t, ciphers.First().Object(), cipherID, folderID) 507 508 folders := obj.Value("Folders").Array() 509 folders.Length().Equal(1) 510 folders.First().Object().ValueEqual("Name", "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=") 511 folders.First().Object().ValueEqual("Object", "folder") 512 folders.First().Object().Value("RevisionDate").String().NotEmpty().DateTime(time.RFC3339) 513 folders.First().Object().ValueEqual("Id", folderID) 514 515 domains := obj.Value("Domains").Object() 516 domains.Value("EquivalentDomains").Null() 517 domains.Value("GlobalEquivalentDomains").Array().NotEmpty() 518 domains.ValueEqual("Object", "domains") 519 }) 520 521 t.Run("BulkDeleteCiphers", func(t *testing.T) { 522 e := testutils.CreateTestClient(t, ts.URL) 523 524 // Setup 525 nbCiphersToDelete := 5 526 nbCiphers, err := couchdb.CountAllDocs(inst, consts.BitwardenCiphers) 527 require.NoError(t, err) 528 529 var ids []string 530 for i := 0; i < nbCiphersToDelete; i++ { 531 obj := e.POST("/bitwarden/api/ciphers"). 532 WithHeader("Content-Type", "application/json"). 533 WithHeader("Authorization", "Bearer "+token). 534 WithBytes([]byte(`{ 535 "type": 1, 536 "favorite": false, 537 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 538 "notes": null, 539 "folderId": null, 540 "organizationId": null, 541 "login": { 542 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 543 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 544 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 545 "totp": null 546 } 547 }`)). 548 Expect().Status(200). 549 JSON().Object() 550 551 ids = append(ids, obj.Value("Id").String().NotEmpty().Raw()) 552 } 553 554 nb, err := couchdb.CountAllDocs(inst, consts.BitwardenCiphers) 555 assert.NoError(t, err) 556 assert.Equal(t, nbCiphers+nbCiphersToDelete, nb) 557 558 body, _ := json.Marshal(map[string][]string{"ids": ids}) 559 560 // Test soft delete in bulk 561 t.Run("Soft delete in bulk", func(t *testing.T) { 562 e := testutils.CreateTestClient(t, ts.URL) 563 564 e.PUT("/bitwarden/api/ciphers/delete"). 565 WithHeader("Content-Type", "application/json"). 566 WithHeader("Authorization", "Bearer "+token). 567 WithBytes(body). 568 Expect().Status(200) 569 570 for _, id := range ids { 571 obj := e.GET("/bitwarden/api/ciphers/"+id). 572 WithHeader("Authorization", "Bearer "+token). 573 Expect().Status(200). 574 JSON().Object() 575 576 obj.Value("DeletedDate").String().NotEmpty().DateTime(time.RFC3339) 577 } 578 }) 579 580 t.Run("Restore in bulk", func(t *testing.T) { 581 e := testutils.CreateTestClient(t, ts.URL) 582 583 obj := e.PUT("/bitwarden/api/ciphers/restore"). 584 WithHeader("Content-Type", "application/json"). 585 WithHeader("Authorization", "Bearer "+token). 586 WithBytes(body). 587 Expect().Status(200). 588 JSON().Object() 589 590 obj.ValueEqual("Object", "list") 591 data := obj.Value("Data").Array() 592 data.Length().Equal(nbCiphersToDelete) 593 594 for i, item := range data.Iter() { 595 item.Object().ValueEqual("Id", ids[i]) 596 item.Object().NotContainsKey("DeletedDate") 597 } 598 }) 599 600 t.Run("Delete in bulk", func(t *testing.T) { 601 e := testutils.CreateTestClient(t, ts.URL) 602 603 e.DELETE("/bitwarden/api/ciphers"). 604 WithHeader("Content-Type", "application/json"). 605 WithHeader("Authorization", "Bearer "+token). 606 WithBytes(body). 607 Expect().Status(200) 608 609 nb, err = couchdb.CountAllDocs(inst, consts.BitwardenCiphers) 610 assert.NoError(t, err) 611 assert.Equal(t, nbCiphers, nb) 612 }) 613 }) 614 615 t.Run("SharedCipher", func(t *testing.T) { 616 e := testutils.CreateTestClient(t, ts.URL) 617 618 obj := e.POST("/bitwarden/api/ciphers/create"). 619 WithHeader("Content-Type", "application/json"). 620 WithHeader("Authorization", "Bearer "+token). 621 WithBytes([]byte(`{ 622 "cipher": { 623 "type": 1, 624 "favorite": false, 625 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 626 "notes": null, 627 "folderId": null, 628 "organizationId": "` + orgaID + `", 629 "login": { 630 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 631 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 632 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 633 "passwordRevisionDate": "2019-09-13T12:26:42+02:00", 634 "totp": null 635 } 636 }, 637 "collectionIds": ["` + collID + `"] 638 }`)). 639 Expect().Status(200). 640 JSON().Object() 641 642 assertCipherResponse(t, obj) 643 obj.ValueEqual("OrganizationId", orgaID) 644 cipherID := obj.Value("Id").String().NotEmpty().Raw() 645 646 obj = e.PUT("/bitwarden/api/ciphers/"+cipherID). 647 WithHeader("Content-Type", "application/json"). 648 WithHeader("Authorization", "Bearer "+token). 649 WithBytes([]byte(`{ 650 "type": 2, 651 "favorite": true, 652 "name": "2.G38TIU3t1pGOfkzjCQE7OQ==|Xa1RupttU7zrWdzIT6oK+w==|J3C6qU1xDrfTgyJD+OrDri1GjgGhU2nmRK75FbZHXoI=", 653 "folderId": "` + folderID + `", 654 "organizationId": "` + orgaID + `", 655 "notes": "2.rSw0uVQEFgUCEmOQx0JnDg==|MKqHLD25aqaXYHeYJPH/mor7l3EeSQKsI7A/R+0bFTI=|ODcUScISzKaZWHlUe4MRGuTT2S7jpyDmbOHl7d+6HiM=", 656 "secureNote": { 657 "type": 0 658 } 659 }`)). 660 Expect().Status(200). 661 JSON().Object() 662 663 assertUpdatedCipherResponse(t, obj, cipherID, folderID) 664 obj.ValueEqual("OrganizationId", orgaID) 665 }) 666 667 t.Run("SetKeyPair", func(t *testing.T) { 668 e := testutils.CreateTestClient(t, ts.URL) 669 670 // Needs to be marshaled in order to avoid encoding issues 671 body, _ := json.Marshal(map[string]string{ 672 "encryptedPrivateKey": "2.demXNYbv8o47sG+fhYYvhg==|jXpxet7AApeIzrC3Yr752LwmjBdCZn6HJl6SjEOVP3rrOpGu5qV2rN0dBH5yXXWHusfxM7IvXkdC/fzBUAmFFOU5ubTp9kHFBqIn51tiJG6BRs5aTm7kF6TYSHVDIP5kUdX4O7DcmD23dqtq/8211DSAFR/DK1QDm5Da77Clh7NHxQE9Z9RTW1PBGV56DfzrY3N06H6vI+V6fTZ6HJRD2pdPczR2ZNC0ziQP7qCUYNlSjEv70O4VoYMSUsdb4UUE1YetcSdZ+dIAy+V2KHfoHmTFYI4DtMCW6WpDzp0ufPvszFjt1EwaMr78hujMrQr1gFWxgN8kOLJyYCrd1F5aIxWXHghBH/t+QU31gyQOxCdj18f10ssfuY/y7vocSJQ9pTRRPNh4beGAijV1AETaXWLK1L6oMnkbdhr9ZA2I6cZaHNCaHIynHQH7NUqKKQUJL/FyZ8rBv4YNnxCMRi9p88IoTb0oPsUCoNCaIZ2cvzXz+0VpU6zxj4ke7H6Bu7H46MSB1P+YHzGLtFNzZJVsUBEkz7dotUDeTeqlYKnq7oldWJ4HlqODevzCev+FRnYgrYpoXmYC/dxa1R5IlKCu6rEmP05A7Nw4h9cymnTwRMEoZRSppJ2O5FlSx/Go9Jz12g2Tfiaf+RvO7nkIb2qKiz7Jo2aJgakL5lMOlEdBA2+dsYSyX4Tvu8Ua4p0GcYaGOgSjXH27lQ73ZpHSicf4Q1kAooVl+6zTOPAqgMXOnyyVSRqBPse28HuDwGtmD8BAeVDIfkMW+a+PlWa+yoEWKfDHRduoxNod7Pc9xlNFt6eOeGoBQTEIiF7ccBDtNiSU1yfvqBZEgI8QF0QiGUo9eP7+59so5eu9/DuzjdqFMmGPtG3zHifMxuMzO5+E9UxTyHuCwvxuH93F4vmPC8zzXXn8/ErhEeqmYl1lxZbfJDm1qcjTkJibNKJ9+CXUeP0hq8yi07SEN1xJSZpupf90EUjrdFd3impz3gZKummEjTvzr3J1JX4gC/wD0mGkROHQwb0jCTDJNC18cX4usPYtNr3FxLZmxCGgPmZhkzFjF0qppN1aXTxQskdorEejQUwLL5EnWJySd9/W2P6PmjkJTAwKYUNHsmVUAfbMA7y7QBIjVFFWS4xYy0GJcc8NaLKkFMkGv/oluw552prWAJZ4aM2asoNgyv/JARrAF+JbOPSpax+CtUMO+LCFlBITHopbkHz0TwI1UMj/vIOh9wxDiMqe3YBiviymudX+B7awCaUPTLubWW1jwC4kBnXmRGAKyyIvzgOvwkdcKfQRxoTxq7JFTL/hWk7x4HlQqviSWGY166CLIp6SydCT+cqHMf3MHhe8AQZVC+nIDVNQZWfpFbOFb3nNDwlT+laWrtsiuX7hHiL0VLaCU4xzup5m4zvi59/Qxj0+d8n6M/3GP3/Tvp/bKY9m7CHoeimtGF9Ai2QFJFMOEQw3S1SUBL62ZsezKgBap6y1RqmMzdz/h3f5mhHxRMoQ0kgzZwMNWJvi2acGoIttcmBU7Cn6fqxYNi11dg17M7cFJAQCMicvd4pEwl8IBrm7uFrzbLvuLeolyiDx8GX3jfIo//Ceqa6P/RIqN8jKzH3nTSePuVqkXYiIdxhlAeF//EYW0CwOjd3GEoc=|aUt6NKqrLW4HeprkbwjuBzSQbR84imTujhUPxK17eX4=", 673 "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmAYrTtY4FBJL/TeTGqr1uHCoMCzUDgwvgq7gBGiNrk24gPbb3xreM+HxubBvkzTlgoS6m1KKKKtD4tWrLU33Xc+PevbKSZDLvBfUe+golGU1XKFxUcIkgINtB0i8LmCVCShiCrlhn2VorcAbekR/1RXtoJqpqq1urhI+RdGVXy8HBBoULA7BoV7wC8dBdkRtnQMNuvGyHclV7yjgealKGqgxz4aNcgsfybquKvYg6PUj8dAxUy7KlmMR7klPyO8nahYqyhpQ/t0xle0WyCkdx5YuYhRSA67Tok+E8fCW5WXOPfIdPZDXS+6/wW1NhcQEa5j6EW11PF/Xq0awBUFwnwIDAQAB", 674 }) 675 676 e.POST("/bitwarden/api/accounts/keys"). 677 WithHeader("Content-Type", "application/json"). 678 WithHeader("Authorization", "Bearer "+token). 679 WithBytes(body). 680 Expect().Status(200) 681 682 setting, err := settings.Get(inst) 683 assert.NoError(t, err) 684 orgKey, err := setting.OrganizationKey() 685 assert.NoError(t, err) 686 assert.NotEmpty(t, orgKey) 687 }) 688 689 t.Run("SettingsDomains", func(t *testing.T) { 690 e := testutils.CreateTestClient(t, ts.URL) 691 692 obj := e.POST("/bitwarden/api/settings/domains"). 693 WithHeader("Content-Type", "application/json"). 694 WithHeader("Authorization", "Bearer "+token). 695 WithBytes([]byte(`{ 696 "equivalentDomains": [ ["stackoverflow.com", "serverfault.com", "superuser.com"] ], 697 "globalEquivalentDomains": [42, 69] 698 }`)). 699 Expect().Status(200). 700 JSON().Object() 701 702 assertDomainsReponse(t, obj) 703 704 obj = e.GET("/bitwarden/api/settings/domains"). 705 WithHeader("Authorization", "Bearer "+token). 706 Expect().Status(200). 707 JSON().Object() 708 709 assertDomainsReponse(t, obj) 710 }) 711 712 t.Run("ImportCiphers", func(t *testing.T) { 713 e := testutils.CreateTestClient(t, ts.URL) 714 715 nbCiphers, err := couchdb.CountAllDocs(inst, consts.BitwardenCiphers) 716 assert.NoError(t, err) 717 718 nbFolders, err := couchdb.CountAllDocs(inst, consts.BitwardenFolders) 719 assert.NoError(t, err) 720 721 e.POST("/bitwarden/api/ciphers/import"). 722 WithHeader("Content-Type", "application/json"). 723 WithHeader("Authorization", "Bearer "+token). 724 WithBytes([]byte(`{ 725 "ciphers": [{ 726 "type": 2, 727 "favorite": true, 728 "name": "2.G38TIU3t1pGOfkzjCQE7OQ==|Xa1RupttU7zrWdzIT6oK+w==|J3C6qU1xDrfTgyJD+OrDri1GjgGhU2nmRK75FbZHXoI=", 729 "folderId": null, 730 "organizationId": null, 731 "notes": "2.rSw0uVQEFgUCEmOQx0JnDg==|MKqHLD25aqaXYHeYJPH/mor7l3EeSQKsI7A/R+0bFTI=|ODcUScISzKaZWHlUe4MRGuTT2S7jpyDmbOHl7d+6HiM=", 732 "secureNote": { 733 "type": 0 734 } 735 }, { 736 "type": 1, 737 "favorite": false, 738 "name": "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=", 739 "folderId": null, 740 "organizationId": null, 741 "notes": null, 742 "login": { 743 "uri": "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=", 744 "username": "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=", 745 "password": "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=", 746 "totp": null 747 } 748 }], 749 "folders": [{ 750 "name": "2.FQAwIBaDbczEGnEJw4g4hw==|7KreXaC0duAj0ulzZJ8ncA==|nu2sEvotjd4zusvGF8YZJPnS9SiJPDqc1VIfCrfve/o=" 751 }], 752 "folderRelationships": [ 753 {"key": 1, "value": 0} 754 ] 755 }`)). 756 Expect().Status(200) 757 758 nb, err := couchdb.CountAllDocs(inst, consts.BitwardenCiphers) 759 assert.NoError(t, err) 760 assert.Equal(t, nbCiphers+2, nb) 761 762 nb, err = couchdb.CountAllDocs(inst, consts.BitwardenFolders) 763 assert.NoError(t, err) 764 assert.Equal(t, nbFolders+1, nb) 765 }) 766 767 t.Run("Organization", func(t *testing.T) { 768 var orgaID string 769 770 t.Run("Create", func(t *testing.T) { 771 e := testutils.CreateTestClient(t, ts.URL) 772 773 obj := e.POST("/bitwarden/api/organizations"). 774 WithHeader("Content-Type", "application/json"). 775 WithHeader("Authorization", "Bearer "+token). 776 WithBytes([]byte(`{ 777 "name": "Family Organization", 778 "key": "bmFjbF53D9mrdGbVqQzMB54uIg678EIpU/uHFYjynSPSA6vIv5/6nUy4Uk22SjIuDB3pZ679wLE3o7R/Imzn47OjfT6IrJ8HaysEhsZA25Dn8zwEtTMtgNepUtH084wAMgNeIcElW24U/MfRscjAk8cDUIm5xnzyi2vtJfe9PcHTmzRXyng=", 779 "collectionName": "2.rrpSDDODsWZqL7EhLVsu/Q==|OSuh+MmmR89ppdb/A7KxBg==|kofpAocL2G4a3P1C2R1U+i9hWbhfKfsPKM6kfoyCg/M=" 780 }`)). 781 Expect().Status(200). 782 JSON().Object() 783 784 obj.ValueEqual("Name", "Family Organization") 785 obj.ValueEqual("Object", "profileOrganization") 786 obj.ValueEqual("Enabled", true) 787 obj.ValueEqual("Status", 2) 788 obj.ValueEqual("Type", 0) 789 790 orgaID = obj.Value("Id").String().NotEmpty().Raw() 791 792 obj.Value("Key").String().NotEmpty() 793 }) 794 795 t.Run("Get", func(t *testing.T) { 796 e := testutils.CreateTestClient(t, ts.URL) 797 798 obj := e.GET("/bitwarden/api/organizations/"+orgaID). 799 WithHeader("Authorization", "Bearer "+token). 800 Expect().Status(200). 801 JSON().Object() 802 803 obj.ValueEqual("Name", "Family Organization") 804 obj.ValueEqual("Object", "profileOrganization") 805 obj.ValueEqual("Enabled", true) 806 obj.ValueEqual("Status", 2) 807 obj.ValueEqual("Type", 0) 808 obj.ValueEqual("Id", orgaID) 809 obj.Value("Key").String().NotEmpty() 810 }) 811 812 t.Run("ListCollections", func(t *testing.T) { 813 e := testutils.CreateTestClient(t, ts.URL) 814 815 obj := e.GET("/bitwarden/api/organizations/"+orgaID+"/collections"). 816 WithHeader("Authorization", "Bearer "+token). 817 Expect().Status(200). 818 JSON().Object() 819 820 obj.ValueEqual("Object", "list") 821 data := obj.Value("Data").Array() 822 data.Length().Equal(1) 823 824 coll := data.First().Object() 825 coll.Value("Id").String().NotEmpty() 826 coll.ValueEqual("Name", "2.rrpSDDODsWZqL7EhLVsu/Q==|OSuh+MmmR89ppdb/A7KxBg==|kofpAocL2G4a3P1C2R1U+i9hWbhfKfsPKM6kfoyCg/M=") 827 coll.ValueEqual("Object", "collection") 828 coll.ValueEqual("OrganizationId", orgaID) 829 coll.ValueEqual("ReadOnly", false) 830 }) 831 832 t.Run("SyncOrganizationAndCollection", func(t *testing.T) { 833 e := testutils.CreateTestClient(t, ts.URL) 834 835 obj := e.GET("/bitwarden/api/sync"). 836 WithHeader("Authorization", "Bearer "+token). 837 Expect().Status(200). 838 JSON().Object() 839 840 obj.ValueEqual("Object", "sync") 841 profile := obj.Value("Profile").Object() 842 orgs := profile.Value("Organizations").Array() 843 orgs.Length().Equal(2) 844 845 for _, item := range orgs.Iter() { 846 org := item.Object() 847 848 if org.Value("Id").String().Raw() == orgaID { 849 org.ValueEqual("Name", "Family Organization") 850 } else { 851 org.ValueEqual("Name", "Cozy") 852 } 853 854 org.Value("Key").String().NotEmpty() 855 org.ValueEqual("Object", "profileOrganization") 856 } 857 858 colls := obj.Value("Collections").Array() 859 colls.Length().Equal(2) 860 for _, item := range colls.Iter() { 861 coll := item.Object() 862 863 if coll.Value("Id").String().Raw() != collID { 864 coll.ValueEqual("OrganizationId", orgaID) 865 coll.ValueEqual("Name", "2.rrpSDDODsWZqL7EhLVsu/Q==|OSuh+MmmR89ppdb/A7KxBg==|kofpAocL2G4a3P1C2R1U+i9hWbhfKfsPKM6kfoyCg/M=") 866 } 867 868 coll.ValueEqual("Object", "collection") 869 } 870 }) 871 872 t.Run("DeleteOrganization", func(t *testing.T) { 873 e := testutils.CreateTestClient(t, ts.URL) 874 875 email := inst.PassphraseSalt() 876 iter := crypto.DefaultPBKDF2Iterations 877 pass, _ := crypto.HashPassWithPBKDF2([]byte("cozy"), email, iter) 878 879 e.DELETE("/bitwarden/api/organizations/"+orgaID). 880 WithHeader("Content-Type", "application/json"). 881 WithHeader("Authorization", "Bearer "+token). 882 WithBytes([]byte(fmt.Sprintf(`{"masterPasswordHash": "%s"}`, pass))). 883 Expect().Status(200) 884 }) 885 }) 886 887 t.Run("ChangeSecurityStamp", func(t *testing.T) { 888 e := testutils.CreateTestClient(t, ts.URL) 889 890 email := inst.PassphraseSalt() 891 iter := crypto.DefaultPBKDF2Iterations 892 pass, _ := crypto.HashPassWithPBKDF2([]byte("cozy"), email, iter) 893 894 e.POST("/bitwarden/api/accounts/security-stamp"). 895 WithHeader("Authorization", "Bearer "+token). 896 WithBytes([]byte(fmt.Sprintf(`{"masterPasswordHash": %q}`, pass))).Expect().Status(204) 897 898 // Check that token is no longer valid 899 e.GET("/bitwarden/api/folders"). 900 WithHeader("Authorization", "Bearer "+token). 901 Expect().Status(401) 902 }) 903 904 t.Run("SendHint", func(t *testing.T) { 905 e := testutils.CreateTestClient(t, ts.URL) 906 907 e.POST("/bitwarden/api/accounts/password-hint"). 908 WithHeader("Content-Type", "application/json"). 909 WithBytes([]byte(`{ "email": "me@bitwarden.example.net" }`)). 910 Expect().Status(200) 911 }) 912 } 913 914 func assertCipherResponse(t *testing.T, obj *httpexpect.Object) { 915 t.Helper() 916 917 obj.ValueEqual("Object", "cipher") 918 obj.Value("Id").String().NotEmpty() 919 obj.ValueEqual("Type", 1.0) 920 obj.ValueEqual("Favorite", false) 921 obj.ValueEqual("Name", "2.d7MttWzJTSSKx1qXjHUxlQ==|01Ath5UqFZHk7csk5DVtkQ==|EMLoLREgCUP5Cu4HqIhcLqhiZHn+NsUDp8dAg1Xu0Io=") 922 obj.Value("Notes").Null() 923 obj.Value("FolderId").Null() 924 925 loginObj := obj.Value("Login").Object().NotEmpty() 926 loginObj.ValueEqual("PasswordRevisionDate", "2019-09-13T12:26:42+02:00") 927 loginObj.Value("Totp").Null() 928 loginObj.ValueEqual("Username", "2.JbFkAEZPnuMm70cdP44wtA==|fsN6nbT+udGmOWv8K4otgw==|JbtwmNQa7/48KszT2hAdxpmJ6DRPZst0EDEZx5GzesI=") 929 loginObj.ValueEqual("Password", "2.e83hIsk6IRevSr/H1lvZhg==|48KNkSCoTacopXRmIZsbWg==|CIcWgNbaIN2ix2Fx1Gar6rWQeVeboehp4bioAwngr0o=") 930 931 loginObj.Value("Uris").Array().Length().Equal(1) 932 uriObj := loginObj.Value("Uris").Array().First().Object() 933 uriObj.ValueEqual("Uri", "2.T57BwAuV8ubIn/sZPbQC+A==|EhUSSpJWSzSYOdJ/AQzfXuUXxwzcs/6C4tOXqhWAqcM=|OWV2VIqLfoWPs9DiouXGUOtTEkVeklbtJQHkQFIXkC8=") 934 uriObj.Value("Match").Null() 935 936 obj.Value("Fields").Null() 937 obj.Value("Attachments").Null() 938 obj.Value("RevisionDate").String().DateTime(time.RFC3339) 939 obj.ValueEqual("Edit", true) 940 obj.ValueEqual("OrganizationUseTotp", false) 941 } 942 943 func assertUpdatedCipherResponse(t *testing.T, obj *httpexpect.Object, cipherID, folderID string) { 944 t.Helper() 945 946 obj.ValueEqual("Object", "cipher") 947 obj.ValueEqual("Id", cipherID) 948 obj.ValueEqual("Type", 2.0) 949 obj.ValueEqual("Favorite", true) 950 obj.ValueEqual("Name", "2.G38TIU3t1pGOfkzjCQE7OQ==|Xa1RupttU7zrWdzIT6oK+w==|J3C6qU1xDrfTgyJD+OrDri1GjgGhU2nmRK75FbZHXoI=") 951 obj.ValueEqual("FolderId", folderID) 952 obj.ValueEqual("Notes", "2.rSw0uVQEFgUCEmOQx0JnDg==|MKqHLD25aqaXYHeYJPH/mor7l3EeSQKsI7A/R+0bFTI=|ODcUScISzKaZWHlUe4MRGuTT2S7jpyDmbOHl7d+6HiM=") 953 obj.Value("SecureNote").Object().NotEmpty().ValueEqual("Type", 0.0) 954 obj.NotContainsKey("Login") 955 obj.Value("Fields").Null() 956 obj.Value("Attachments").Null() 957 obj.Value("RevisionDate").String().DateTime(time.RFC3339) 958 obj.ValueEqual("Edit", true) 959 obj.ValueEqual("OrganizationUseTotp", false) 960 } 961 962 func assertDomainsReponse(t *testing.T, obj *httpexpect.Object) { 963 obj.ValueEqual("Object", "domains") 964 equivalent := obj.Value("EquivalentDomains").Array() 965 equivalent.Length().Equal(1) 966 domains := equivalent.First().Array() 967 domains.Length().Equal(3) 968 domains.Element(0).Equal("stackoverflow.com") 969 domains.Element(1).Equal("serverfault.com") 970 domains.Element(2).Equal("superuser.com") 971 972 global := obj.Value("GlobalEquivalentDomains").Array() 973 global.Length().Equal(len(bitwarden.GlobalDomains)) 974 975 for _, item := range global.Iter() { 976 k := int(item.Object().Value("Type").Number().Raw()) 977 excluded := (k == 42) || (k == 69) 978 item.Object().ValueEqual("Excluded", excluded) 979 item.Object().Value("Domains").Array().Length().Gt(0) 980 } 981 }