github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/sharings/sharings_test.go (about) 1 package sharings_test 2 3 import ( 4 "encoding/json" 5 "net/url" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/cozy/cozy-stack/model/contact" 11 "github.com/cozy/cozy-stack/model/instance" 12 "github.com/cozy/cozy-stack/model/instance/lifecycle" 13 "github.com/cozy/cozy-stack/model/job" 14 "github.com/cozy/cozy-stack/model/permission" 15 "github.com/cozy/cozy-stack/model/sharing" 16 "github.com/cozy/cozy-stack/pkg/assets/dynamic" 17 build "github.com/cozy/cozy-stack/pkg/config" 18 "github.com/cozy/cozy-stack/pkg/config/config" 19 "github.com/cozy/cozy-stack/pkg/consts" 20 "github.com/cozy/cozy-stack/pkg/couchdb" 21 "github.com/cozy/cozy-stack/pkg/couchdb/mango" 22 "github.com/cozy/cozy-stack/tests/testutils" 23 "github.com/cozy/cozy-stack/web" 24 "github.com/cozy/cozy-stack/web/auth" 25 "github.com/cozy/cozy-stack/web/errors" 26 "github.com/cozy/cozy-stack/web/middlewares" 27 "github.com/cozy/cozy-stack/web/permissions" 28 "github.com/cozy/cozy-stack/web/sharings" 29 "github.com/cozy/cozy-stack/web/statik" 30 "github.com/gavv/httpexpect/v2" 31 "github.com/labstack/echo/v4" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 const iocozytests = "io.cozy.tests" 37 const iocozytestswildcard = "io.cozy.tests.*" 38 39 // Things that live on Alice's Cozy 40 var charlieContact, daveContact, edwardContact *contact.Contact 41 var sharingID, state, aliceAccessToken string 42 43 // Bob's browser 44 var discoveryLink, authorizeLink string 45 46 func TestSharings(t *testing.T) { 47 if testing.Short() { 48 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 49 } 50 51 config.UseTestFile(t) 52 build.BuildMode = build.ModeDev 53 config.GetConfig().Assets = "../../assets" 54 _ = web.LoadSupportedLocales() 55 testutils.NeedCouchdb(t) 56 render, _ := statik.NewDirRenderer("../../assets") 57 middlewares.BuildTemplates() 58 59 // Prepare Alice's instance 60 setup := testutils.NewSetup(t, t.Name()+"_alice") 61 aliceInstance := setup.GetTestInstance(&lifecycle.Options{ 62 Email: "alice@example.net", 63 PublicName: "Alice", 64 }) 65 aliceAppToken := generateAppToken(aliceInstance, "testapp", iocozytests) 66 aliceAppTokenWildcard := generateAppToken(aliceInstance, "testapp2", iocozytestswildcard) 67 charlieContact = createContact(t, aliceInstance, "Charlie", "charlie@example.net") 68 daveContact = createContact(t, aliceInstance, "Dave", "dave@example.net") 69 tsA := setup.GetTestServerMultipleRoutes(map[string]func(*echo.Group){ 70 "/sharings": sharings.Routes, 71 "/permissions": permissions.Routes, 72 }) 73 tsA.Config.Handler.(*echo.Echo).Renderer = render 74 tsA.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler 75 t.Cleanup(tsA.Close) 76 77 // Prepare Bob's instance 78 bobSetup := testutils.NewSetup(t, t.Name()+"_bob") 79 bobInstance := bobSetup.GetTestInstance(&lifecycle.Options{ 80 Email: "bob@example.net", 81 PublicName: "Bob", 82 Passphrase: "MyPassphrase", 83 KdfIterations: 5000, 84 Key: "xxx", 85 }) 86 bobAppToken := generateAppToken(bobInstance, "testapp", iocozytests) 87 tsB := bobSetup.GetTestServerMultipleRoutes(map[string]func(*echo.Group){ 88 "/auth": func(g *echo.Group) { 89 g.Use(middlewares.LoadSession) 90 auth.Routes(g) 91 }, 92 "/sharings": sharings.Routes, 93 }) 94 tsB.Config.Handler.(*echo.Echo).Renderer = render 95 tsB.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler 96 t.Cleanup(tsB.Close) 97 98 require.NoError(t, dynamic.InitDynamicAssetFS(config.FsURL().String()), "Could not init dynamic FS") 99 100 t.Run("CreateSharingSuccess", func(t *testing.T) { 101 eA := httpexpect.Default(t, tsA.URL) 102 103 bobContact := createBobContact(t, aliceInstance) 104 assert.NotEmpty(t, aliceAppToken) 105 assert.NotNil(t, bobContact) 106 107 obj := eA.POST("/sharings/"). 108 WithHeader("Authorization", "Bearer "+aliceAppToken). 109 WithHeader("Content-Type", "application/vnd.api+json"). 110 WithBytes([]byte(`{ 111 "data": { 112 "type": "` + consts.Sharings + `", 113 "attributes": { 114 "description": "this is a test", 115 "open_sharing": true, 116 "rules": [{ 117 "title": "test one", 118 "doctype": "` + iocozytests + `", 119 "values": ["000000"], 120 "add": "sync" 121 }] 122 }, 123 "relationships": { 124 "recipients": { 125 "data": [{"id": "` + bobContact.ID() + `", "type": "` + bobContact.DocType() + `"}] 126 }, 127 "read_only_recipients": { 128 "data": [{"id": "` + daveContact.ID() + `", "type": "` + daveContact.DocType() + `"}] 129 } 130 } 131 } 132 }`)). 133 Expect().Status(201). 134 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 135 Object() 136 137 sharingID = obj.Value("data").Object().Value("id").String().NotEmpty().Raw() 138 139 assertSharingIsCorrectOnSharer(t, obj, sharingID, aliceInstance) 140 description := assertInvitationMailWasSent(t, aliceInstance) 141 assert.Equal(t, description, "this is a test") 142 assert.Contains(t, discoveryLink, "/discovery?state=") 143 }) 144 145 t.Run("GetSharing", func(t *testing.T) { 146 eA := httpexpect.Default(t, tsA.URL) 147 148 obj := eA.GET("/sharings/"+sharingID). 149 WithHeader("Authorization", "Bearer "+aliceAppToken). 150 WithHeader("Content-Type", "application/vnd.api+json"). 151 Expect().Status(200). 152 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 153 Object() 154 155 assertSharingIsCorrectOnSharer(t, obj, sharingID, aliceInstance) 156 }) 157 158 t.Run("Discovery", func(t *testing.T) { 159 u, err := url.Parse(discoveryLink) 160 assert.NoError(t, err) 161 state = u.Query()["state"][0] 162 163 // Take only the path and query from the `disoveryLink` and redirect 164 // to the tsA host. 165 eA := httpexpect.Default(t, tsA.URL) 166 167 eA.GET(u.Path). 168 WithQuery("state", state). 169 Expect().Status(200). 170 HasContentType("text/html", "utf-8"). 171 Body(). 172 Contains("Connect to your Cozy"). 173 Contains(`<input type="hidden" name="state" value="` + state) 174 175 redirectHeader := eA.POST(u.Path). 176 WithFormField("state", state). 177 WithFormField("slug", tsB.URL). 178 WithRedirectPolicy(httpexpect.DontFollowRedirects). 179 Expect().Status(302).Header("Location") 180 181 authorizeLink = redirectHeader.NotEmpty().Raw() 182 redirectHeader.Contains(tsB.URL) 183 redirectHeader.Contains("/auth/authorize/sharing") 184 185 assertSharingRequestHasBeenCreated(t, aliceInstance, bobInstance, tsB.URL) 186 }) 187 188 t.Run("AuthorizeSharing", func(t *testing.T) { 189 u, err := url.Parse(authorizeLink) 190 assert.NoError(t, err) 191 sharingID = u.Query()["sharing_id"][0] 192 state := u.Query()["state"][0] 193 194 eB := httpexpect.Default(t, tsB.URL) 195 196 // Bob login 197 token := eB.GET("/auth/login"). 198 Expect().Status(200). 199 Cookie("_csrf").Value().NotEmpty().Raw() 200 201 eB.POST("/auth/login"). 202 WithCookie("_csrf", token). 203 WithFormField("passphrase", "MyPassphrase"). 204 WithFormField("csrf_token", token). 205 WithRedirectPolicy(httpexpect.DontFollowRedirects). 206 Expect().Status(303). 207 Header("Location").Contains("home") 208 // End bob login 209 210 fakeAliceInstance(t, bobInstance, tsA.URL) 211 212 t.Logf("redirect header: %q\n\n", authorizeLink) 213 214 body := eB.GET(u.Path). 215 WithQuery("sharing_id", sharingID). 216 WithQuery("state", state). 217 Expect().Status(200). 218 HasContentType("text/html", "utf-8"). 219 Body() 220 221 body.Contains("and you can collaborate by editing the document") 222 body.Contains(`<input type="hidden" name="sharing_id" value="` + sharingID) 223 body.Contains(`<input type="hidden" name="state" value="` + state) 224 body.Contains(`<span class="filetype-other filetype">`) 225 226 matches := body.Match(`<input type="hidden" name="csrf_token" value="(\w+)"`) 227 matches.Length().IsEqual(2) 228 229 eB.POST("/auth/authorize/sharing"). 230 WithFormField("state", state). 231 WithFormField("sharing_id", sharingID). 232 WithFormField("csrf_token", token). 233 WithFormField("synchronize", true). 234 WithRedirectPolicy(httpexpect.DontFollowRedirects). 235 Expect().Status(303). 236 Header("Location").Contains("testapp." + bobInstance.Domain) 237 238 assertCredentialsHasBeenExchanged(t, aliceInstance, bobInstance, tsA.URL, tsB.URL) 239 }) 240 241 t.Run("DelegateAddRecipientByCozyURL", func(t *testing.T) { 242 assert.NotEmpty(t, bobAppToken) 243 edwardContact = createContact(t, bobInstance, "Edward", "edward@example.net") 244 245 eB := httpexpect.Default(t, tsB.URL) 246 247 obj := eB.POST("/sharings/"+sharingID+"/recipients"). 248 WithHeader("Authorization", "Bearer "+bobAppToken). 249 WithHeader("Content-Type", "application/vnd.api+json"). 250 WithBytes([]byte(`{ 251 "data": { 252 "type": "` + consts.Sharings + `", 253 "relationships": { 254 "recipients": { 255 "data": [{"id": "` + edwardContact.ID() + `", "type": "` + edwardContact.DocType() + `"}] 256 } 257 } 258 } 259 }`)). 260 Expect().Status(200). 261 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 262 Object() 263 264 data := obj.Value("data").Object() 265 attrs := data.Value("attributes").Object() 266 267 members := attrs.Value("members").Array() 268 members.Length().IsEqual(4) 269 270 owner := members.Value(0).Object() 271 owner.HasValue("status", "owner") 272 owner.HasValue("public_name", "Alice") 273 owner.HasValue("email", "alice@example.net") 274 275 bob := members.Value(1).Object() 276 bob.HasValue("status", "ready") 277 bob.HasValue("email", "bob@example.net") 278 279 dave := members.Value(2).Object() 280 dave.HasValue("status", "pending") 281 dave.HasValue("email", "dave@example.net") 282 dave.HasValue("read_only", true) 283 284 edward := members.Value(3).Object() 285 edward.HasValue("name", "Edward") 286 edward.HasValue("email", "edward@example.net") 287 }) 288 289 t.Run("CreateSharingWithGroup", func(t *testing.T) { 290 eA := httpexpect.Default(t, tsA.URL) 291 require.NotEmpty(t, aliceAppToken) 292 293 group := createGroup(t, aliceInstance, "friends") 294 require.NotNil(t, group) 295 fionaContact := addContactToGroup(t, aliceInstance, group, "Fiona") 296 require.NotNil(t, fionaContact) 297 gabyContact := addContactToGroup(t, aliceInstance, group, "Gaby") 298 require.NotNil(t, gabyContact) 299 300 obj := eA.POST("/sharings/"). 301 WithHeader("Authorization", "Bearer "+aliceAppToken). 302 WithHeader("Content-Type", "application/vnd.api+json"). 303 WithBytes([]byte(`{ 304 "data": { 305 "type": "` + consts.Sharings + `", 306 "attributes": { 307 "description": "this is a test with a group", 308 "open_sharing": true, 309 "rules": [{ 310 "title": "test group", 311 "doctype": "` + iocozytests + `", 312 "values": ["000001"], 313 "add": "sync" 314 }] 315 }, 316 "relationships": { 317 "recipients": { 318 "data": [{"id": "` + group.ID() + `", "type": "` + consts.Groups + `"}] 319 } 320 } 321 } 322 }`)). 323 Expect().Status(201). 324 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 325 Object() 326 327 data := obj.Value("data").Object() 328 attrs := data.Value("attributes").Object() 329 attrs.HasValue("description", "this is a test with a group") 330 members := attrs.Value("members").Array() 331 members.Length().IsEqual(3) 332 333 owner := members.Value(0).Object() 334 owner.HasValue("status", "owner") 335 owner.HasValue("public_name", "Alice") 336 owner.HasValue("email", "alice@example.net") 337 owner.HasValue("instance", "http://"+aliceInstance.Domain) 338 339 recipient := members.Value(1).Object() 340 recipient.HasValue("status", "pending") 341 recipient.HasValue("name", "Fiona") 342 recipient.HasValue("email", "fiona@example.net") 343 recipient.HasValue("groups", []int{0}) 344 recipient.NotContainsKey("read_only") 345 recipient.HasValue("only_in_groups", true) 346 347 recipient = members.Value(2).Object() 348 recipient.HasValue("status", "pending") 349 recipient.HasValue("name", "Gaby") 350 recipient.HasValue("email", "gaby@example.net") 351 recipient.HasValue("groups", []int{0}) 352 recipient.NotContainsKey("read_only") 353 recipient.HasValue("only_in_groups", true) 354 355 groups := attrs.Value("groups").Array() 356 groups.Length().IsEqual(1) 357 g := groups.Value(0).Object() 358 g.HasValue("id", group.ID()) 359 g.HasValue("name", "friends") 360 g.HasValue("addedBy", 0) 361 }) 362 363 t.Run("CreateSharingWithPreview", func(t *testing.T) { 364 bobContact := createBobContact(t, aliceInstance) 365 require.NotEmpty(t, aliceAppToken) 366 require.NotNil(t, bobContact) 367 368 eA := httpexpect.Default(t, tsA.URL) 369 370 obj := eA.POST("/sharings/"). 371 WithHeader("Authorization", "Bearer "+aliceAppToken). 372 WithHeader("Content-Type", "application/vnd.api+json"). 373 WithBytes([]byte(`{ 374 "data": { 375 "type": "` + consts.Sharings + `", 376 "attributes": { 377 "description": "this is a test with preview", 378 "preview_path": "/preview", 379 "rules": [{ 380 "title": "test two", 381 "doctype": "` + iocozytests + `", 382 "values": ["foobaz"] 383 }] 384 }, 385 "relationships": { 386 "recipients": { 387 "data": [{"id": "` + bobContact.ID() + `", "type": "` + bobContact.DocType() + `"}] 388 }, 389 "read_only_recipients": { 390 "data": [{"id": "` + daveContact.ID() + `", "type": "` + daveContact.DocType() + `"}] 391 } 392 } 393 } 394 }`)). 395 Expect().Status(201). 396 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 397 Object() 398 399 data := obj.Value("data").Object() 400 data.HasValue("type", consts.Sharings) 401 sharingID = data.Value("id").String().NotEmpty().Raw() 402 data.Value("meta").Object().Value("rev").String().NotEmpty() 403 data.Value("links").Object().HasValue("self", "/sharings/"+sharingID) 404 405 attrs := data.Value("attributes").Object() 406 attrs.HasValue("description", "this is a test with preview") 407 attrs.HasValue("app_slug", "testapp") 408 attrs.HasValue("preview_path", "/preview") 409 attrs.HasValue("owner", true) 410 attrs.Value("created_at").String().AsDateTime(time.RFC3339) 411 attrs.Value("updated_at").String().AsDateTime(time.RFC3339) 412 attrs.NotContainsKey("credentials") 413 414 members := attrs.Value("members").Array() 415 assertSharingByAliceToBobAndDave(t, members, aliceInstance) 416 417 rules := attrs.Value("rules").Array() 418 rules.Length().IsEqual(1) 419 rule := rules.Value(0).Object() 420 rule.HasValue("title", "test two") 421 rule.HasValue("doctype", iocozytests) 422 rule.HasValue("values", []string{"foobaz"}) 423 424 description := assertInvitationMailWasSent(t, aliceInstance) 425 assert.Equal(t, description, "this is a test with preview") 426 assert.Contains(t, discoveryLink, aliceInstance.Domain) 427 assert.Contains(t, discoveryLink, "/preview?sharecode=") 428 }) 429 430 t.Run("DiscoveryWithPreview", func(t *testing.T) { 431 u, err := url.Parse(discoveryLink) 432 assert.NoError(t, err) 433 sharecode := u.Query()["sharecode"][0] 434 435 eA := httpexpect.Default(t, tsA.URL) 436 437 t.Logf("sharcode: %q\n\n", sharecode) 438 439 obj := eA.POST("/sharings/"+sharingID+"/discovery"). 440 WithHeader("Accept", "application/json"). 441 WithFormField("sharecode", sharecode). 442 WithFormField("url", tsB.URL). 443 Expect().Status(200). 444 JSON().Object() 445 446 redirectURI := obj.Value("redirect").String().Contains(tsB.URL).Raw() 447 448 res, err := url.Parse(redirectURI) 449 assert.NoError(t, err) 450 assert.Equal(t, res.Path, "/auth/authorize/sharing") 451 assert.Equal(t, res.Query()["sharing_id"][0], sharingID) 452 assert.NotEmpty(t, res.Query()["state"][0]) 453 }) 454 455 t.Run("AddRecipient", func(t *testing.T) { 456 require.NotEmpty(t, aliceAppToken) 457 require.NotNil(t, charlieContact) 458 459 eA := httpexpect.Default(t, tsA.URL) 460 461 brothers := createGroup(t, aliceInstance, "brothers") 462 require.NotNil(t, brothers) 463 hugoContact := addContactToGroup(t, aliceInstance, brothers, "Hugo") 464 require.NotNil(t, hugoContact) 465 idrisContact := addContactToGroup(t, aliceInstance, brothers, "Idris") 466 require.NotNil(t, idrisContact) 467 468 obj := eA.POST("/sharings/"+sharingID+"/recipients"). 469 WithHeader("Authorization", "Bearer "+aliceAppToken). 470 WithHeader("Content-Type", "application/vnd.api+json"). 471 WithBytes([]byte(`{ 472 "data": { 473 "type": "` + consts.Sharings + `", 474 "relationships": { 475 "recipients": { 476 "data": [ 477 {"id": "` + charlieContact.ID() + `", "type": "` + consts.Contacts + `"}, 478 {"id": "` + brothers.ID() + `", "type": "` + consts.Groups + `"} 479 ] 480 } 481 } 482 } 483 }`)). 484 Expect().Status(200). 485 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 486 Object() 487 488 data := obj.Value("data").Object() 489 attrs := data.Value("attributes").Object() 490 members := attrs.Value("members").Array() 491 492 members.Length().IsEqual(6) 493 owner := members.Value(0).Object() 494 owner.HasValue("status", "owner") 495 owner.HasValue("public_name", "Alice") 496 owner.HasValue("email", "alice@example.net") 497 owner.HasValue("instance", "http://"+aliceInstance.Domain) 498 499 bob := members.Value(1).Object() 500 bob.HasValue("status", "pending") 501 bob.HasValue("name", "Bob") 502 bob.HasValue("email", "bob@example.net") 503 bob.NotContainsKey("only_in_groups") 504 505 dave := members.Value(2).Object() 506 dave.HasValue("status", "pending") 507 dave.HasValue("name", "Dave") 508 dave.HasValue("email", "dave@example.net") 509 dave.HasValue("read_only", true) 510 dave.NotContainsKey("only_in_groups") 511 512 charlie := members.Value(3).Object() 513 charlie.HasValue("status", "pending") 514 charlie.HasValue("name", "Charlie") 515 charlie.HasValue("email", "charlie@example.net") 516 charlie.NotContainsKey("only_in_groups") 517 518 hugo := members.Value(4).Object() 519 hugo.HasValue("status", "pending") 520 hugo.HasValue("name", "Hugo") 521 hugo.HasValue("email", "hugo@example.net") 522 hugo.HasValue("groups", []int{0}) 523 hugo.HasValue("only_in_groups", true) 524 525 idris := members.Value(5).Object() 526 idris.HasValue("status", "pending") 527 idris.HasValue("name", "Idris") 528 idris.HasValue("email", "idris@example.net") 529 idris.HasValue("groups", []int{0}) 530 idris.HasValue("only_in_groups", true) 531 532 groups := attrs.Value("groups").Array() 533 groups.Length().IsEqual(1) 534 g := groups.Value(0).Object() 535 g.HasValue("id", brothers.ID()) 536 g.HasValue("name", "brothers") 537 g.HasValue("addedBy", 0) 538 }) 539 540 t.Run("RevokedSharingWithPreview", func(t *testing.T) { 541 sharecode := strings.Split(discoveryLink, "=")[1] 542 543 eA := httpexpect.Default(t, tsA.URL) 544 545 obj := eA.GET("/permissions/self"). 546 WithHeader("Authorization", "Bearer "+sharecode). 547 WithHeader("Content-Type", "application/vnd.api+json"). 548 Expect().Status(200). 549 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 550 Object() 551 552 sourceID := obj.Value("data").Object(). 553 Value("attributes").Object(). 554 Value("source_id").String().NotEmpty().Raw() 555 sharingID = strings.Split(sourceID, "/")[1] 556 557 // Adding a new member to the sharing 558 newMemberMail := "foo@bar.com" 559 sharingDoc, err := sharing.FindSharing(aliceInstance, sharingID) 560 require.NoError(t, err) 561 562 m := sharing.Member{Email: newMemberMail, ReadOnly: true} 563 _, err = sharingDoc.AddDelegatedContact(aliceInstance, m) 564 require.NoError(t, err) 565 perms, err := permission.GetForSharePreview(aliceInstance, sharingID) 566 require.NoError(t, err) 567 fooShareCode, err := aliceInstance.CreateShareCode(newMemberMail) 568 require.NoError(t, err) 569 570 // Adding its sharecode 571 codes := perms.Codes 572 codes[newMemberMail] = fooShareCode 573 perms.PatchCodes(codes) 574 assert.NoError(t, couchdb.UpdateDoc(aliceInstance, perms)) 575 assert.NoError(t, couchdb.UpdateDoc(aliceInstance, sharingDoc)) 576 577 // Assert he has access to the sharing preview 578 eA.GET("/permissions/self"). 579 WithHeader("Authorization", "Bearer "+fooShareCode). 580 WithHeader("Content-Type", "application/vnd.api+json"). 581 Expect().Status(200) 582 583 // Check the member status has been updated to "seen" 584 sharingDoc, err = sharing.FindSharing(aliceInstance, sharingID) 585 assert.NoError(t, err) 586 member, err := sharingDoc.FindMemberBySharecode(aliceInstance, fooShareCode) 587 assert.NoError(t, err) 588 assert.Equal(t, "seen", member.Status) 589 590 // Now, revoking the fresh user from the sharing 591 member, err = sharingDoc.FindMemberBySharecode(aliceInstance, fooShareCode) 592 assert.NoError(t, err) 593 index := 0 594 for i := range sharingDoc.Members { 595 if member == &sharingDoc.Members[i] { 596 index = i 597 break 598 } 599 } 600 err = sharingDoc.RevokeMember(aliceInstance, index) 601 assert.NoError(t, err) 602 assert.Equal(t, "revoked", member.Status) 603 604 // Try to get permissions/self, we should get a 400 605 eA.GET("/permissions/self"). 606 WithHeader("Authorization", "Bearer "+fooShareCode). 607 WithHeader("Content-Type", "application/vnd.api+json"). 608 Expect().Status(400). 609 Body().Contains("Invalid JWT") 610 }) 611 612 t.Run("CheckPermissions", func(t *testing.T) { 613 bobContact := createBobContact(t, aliceInstance) 614 assert.NotNil(t, bobContact) 615 616 eA := httpexpect.Default(t, tsA.URL) 617 618 eA.POST("/sharings"). 619 WithHeader("Authorization", "Bearer "+aliceAppToken). 620 WithHeader("Content-Type", "application/vnd.api+json"). 621 WithBytes([]byte(`{ 622 "data": { 623 "type": "` + consts.Sharings + `", 624 "attributes": { 625 "description": "this is a test", 626 "preview_path": "/preview", 627 "rules": [ 628 { 629 "title": "test one", 630 "doctype": "` + iocozytests + `", 631 "values": ["000000"], 632 "add": "sync" 633 },{ 634 "title": "test two", 635 "doctype": "` + consts.Contacts + `", 636 "values": ["000000"], 637 "add": "sync" 638 }] 639 }, 640 "relationships": { 641 "recipients": { 642 "data": [{"id": "` + bobContact.ID() + `", "type": "` + bobContact.DocType() + `"}] 643 } 644 } 645 } 646 }`)). 647 Expect().Status(403) 648 649 other := &sharing.Sharing{ 650 Description: "Another sharing", 651 Rules: []sharing.Rule{ 652 { 653 Title: "a directory", 654 DocType: consts.Files, 655 Values: []string{"6836cc06-33e9-11e8-8157-dfc1aca099b6"}, 656 }, 657 }, 658 } 659 assert.NoError(t, other.BeOwner(aliceInstance, "home")) 660 assert.NoError(t, other.AddContact(aliceInstance, bobContact.ID(), false)) 661 _, err := other.Create(aliceInstance) 662 assert.NoError(t, err) 663 664 eA.GET("/sharings/"+other.ID()). 665 WithHeader("Authorization", "Bearer "+aliceAppToken). 666 WithHeader("Content-Type", "application/vnd.api+json"). 667 Expect().Status(403) 668 }) 669 670 t.Run("CheckSharingInfoByDocType", func(t *testing.T) { 671 sharedDocs1 := []string{"fakeid1", "fakeid2", "fakeid3"} 672 sharedDocs2 := []string{"fakeid4", "fakeid5"} 673 s1 := createSharing(t, aliceInstance, sharedDocs1, tsB.URL) 674 s2 := createSharing(t, aliceInstance, sharedDocs2, tsB.URL) 675 676 for _, id := range sharedDocs1 { 677 sid := iocozytests + "/" + id 678 sd, errs := createSharedDoc(aliceInstance, sid, s1.ID()) 679 assert.NoError(t, errs) 680 assert.NotNil(t, sd) 681 } 682 for _, id := range sharedDocs2 { 683 sid := iocozytests + "/" + id 684 sd, errs := createSharedDoc(aliceInstance, sid, s2.ID()) 685 assert.NoError(t, errs) 686 assert.NotNil(t, sd) 687 } 688 689 eA := httpexpect.Default(t, tsA.URL) 690 691 obj := eA.GET("/sharings/doctype/"+iocozytests). 692 WithHeader("Authorization", "Bearer "+aliceAppToken). 693 WithHeader("Content-Type", "application/vnd.api+json"). 694 Expect().Status(200). 695 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 696 Object() 697 698 s1Found := false 699 s2Found := false 700 701 for _, itm := range obj.Value("data").Array().Iter() { 702 data := itm.Object() 703 data.HasValue("type", consts.Sharings) 704 sharingID = data.Value("id").String().NotEmpty().Raw() 705 rel := data.Value("relationships").Object() 706 sharedDocs := rel.Value("shared_docs").Object() 707 708 if sharingID == s1.ID() { 709 sharedDocsData := sharedDocs.Value("data").Array() 710 sharedDocsData.Value(0).Object().Value("id").IsEqual("fakeid1") 711 sharedDocsData.Value(1).Object().Value("id").IsEqual("fakeid2") 712 sharedDocsData.Value(2).Object().Value("id").IsEqual("fakeid3") 713 s1Found = true 714 } 715 716 if sharingID == s2.ID() { 717 sharedDocsData := sharedDocs.Value("data").Array() 718 sharedDocsData.Value(0).Object().Value("id").IsEqual("fakeid4") 719 sharedDocsData.Value(1).Object().Value("id").IsEqual("fakeid5") 720 s2Found = true 721 } 722 } 723 724 assert.Equal(t, true, s1Found) 725 assert.Equal(t, true, s2Found) 726 727 eA.GET("/sharings/doctype/io.cozy.tests.notyet"). 728 WithHeader("Authorization", "Bearer "+aliceAppTokenWildcard). 729 WithHeader("Content-Type", "application/vnd.api+json"). 730 Expect().Status(200) 731 732 eA.GET("/sharings/doctype/"+iocozytests). 733 WithHeader("Authorization", "Bearer "+aliceAppTokenWildcard). 734 WithHeader("Content-Type", "application/vnd.api+json"). 735 Expect().Status(200) 736 737 eA.GET("/sharings/doctype/io.cozy.things"). 738 WithHeader("Content-Type", "application/vnd.api+json"). 739 Expect().Status(401) 740 }) 741 742 t.Run("RevokeSharing", func(t *testing.T) { 743 sharedDocs := []string{"mygreatid1", "mygreatid2"} 744 sharedRefs := []*sharing.SharedRef{} 745 s := createSharing(t, aliceInstance, sharedDocs, tsB.URL) 746 747 for _, id := range sharedDocs { 748 sid := iocozytests + "/" + id 749 sd, errs := createSharedDoc(aliceInstance, sid, s.SID) 750 sharedRefs = append(sharedRefs, sd) 751 assert.NoError(t, errs) 752 assert.NotNil(t, sd) 753 } 754 755 cli, err := sharing.CreateOAuthClient(aliceInstance, &s.Members[1]) 756 assert.NoError(t, err) 757 s.Credentials[0].Client = sharing.ConvertOAuthClient(cli) 758 token, err := sharing.CreateAccessToken(aliceInstance, cli, s.SID, permission.ALL) 759 assert.NoError(t, err) 760 s.Credentials[0].AccessToken = token 761 s.Members[1].Status = sharing.MemberStatusReady 762 763 err = couchdb.UpdateDoc(aliceInstance, s) 764 assert.NoError(t, err) 765 766 err = s.AddTrackTriggers(aliceInstance) 767 assert.NoError(t, err) 768 err = s.AddReplicateTrigger(aliceInstance) 769 assert.NoError(t, err) 770 771 eA := httpexpect.Default(t, tsA.URL) 772 773 eA.DELETE("/sharings/"+s.ID()+"/recipients"). 774 WithHeader("Authorization", "Bearer "+aliceAppToken). 775 WithHeader("Content-Type", "application/vnd.api+json"). 776 Expect().Status(204) 777 778 var sRevoke sharing.Sharing 779 err = couchdb.GetDoc(aliceInstance, s.DocType(), s.SID, &sRevoke) 780 assert.NoError(t, err) 781 782 assert.Equal(t, "", sRevoke.Triggers.TrackID) 783 assert.Empty(t, sRevoke.Triggers.TrackIDs) 784 assert.Equal(t, "", sRevoke.Triggers.ReplicateID) 785 assert.Equal(t, "", sRevoke.Triggers.UploadID) 786 assert.Equal(t, false, sRevoke.Active) 787 788 var sdoc sharing.SharedRef 789 err = couchdb.GetDoc(aliceInstance, sharedRefs[0].DocType(), sharedRefs[0].ID(), &sdoc) 790 assert.EqualError(t, err, "CouchDB(not_found): deleted") 791 err = couchdb.GetDoc(aliceInstance, sharedRefs[1].DocType(), sharedRefs[1].ID(), &sdoc) 792 assert.EqualError(t, err, "CouchDB(not_found): deleted") 793 }) 794 795 t.Run("RevokeRecipient", func(t *testing.T) { 796 sharedDocs := []string{"mygreatid3", "mygreatid4"} 797 sharedRefs := []*sharing.SharedRef{} 798 s := createSharing(t, aliceInstance, sharedDocs, tsB.URL) 799 800 for _, id := range sharedDocs { 801 sid := iocozytests + "/" + id 802 sd, errs := createSharedDoc(aliceInstance, sid, s.SID) 803 sharedRefs = append(sharedRefs, sd) 804 assert.NoError(t, errs) 805 assert.NotNil(t, sd) 806 } 807 808 cli, err := sharing.CreateOAuthClient(aliceInstance, &s.Members[1]) 809 assert.NoError(t, err) 810 s.Credentials[0].Client = sharing.ConvertOAuthClient(cli) 811 token, err := sharing.CreateAccessToken(aliceInstance, cli, s.SID, permission.ALL) 812 assert.NoError(t, err) 813 s.Credentials[0].AccessToken = token 814 s.Members[1].Status = sharing.MemberStatusReady 815 816 s.Members = append(s.Members, sharing.Member{ 817 Status: sharing.MemberStatusReady, 818 Name: "Charlie", 819 Email: "charlie@cozy.local", 820 Instance: tsB.URL, 821 }) 822 823 clientC, err := sharing.CreateOAuthClient(aliceInstance, &s.Members[2]) 824 assert.NoError(t, err) 825 tokenC, err := sharing.CreateAccessToken(aliceInstance, clientC, s.SID, permission.ALL) 826 assert.NoError(t, err) 827 s.Credentials = append(s.Credentials, sharing.Credentials{ 828 Client: sharing.ConvertOAuthClient(clientC), 829 AccessToken: tokenC, 830 }) 831 832 err = couchdb.UpdateDoc(aliceInstance, s) 833 assert.NoError(t, err) 834 835 err = s.AddTrackTriggers(aliceInstance) 836 assert.NoError(t, err) 837 err = s.AddReplicateTrigger(aliceInstance) 838 assert.NoError(t, err) 839 840 eA := httpexpect.Default(t, tsA.URL) 841 842 eA.DELETE("/sharings/"+s.ID()+"/recipients/1"). 843 WithHeader("Authorization", "Bearer "+aliceAppToken). 844 WithHeader("Content-Type", "application/vnd.api+json"). 845 Expect().Status(204) 846 847 assertOneRecipientIsRevoked(t, s, aliceInstance) 848 849 eA.DELETE("/sharings/"+s.ID()+"/recipients/2"). 850 WithHeader("Authorization", "Bearer "+aliceAppToken). 851 WithHeader("Content-Type", "application/vnd.api+json"). 852 Expect().Status(204) 853 854 assertLastRecipientIsRevoked(t, s, sharedRefs, aliceInstance) 855 }) 856 857 t.Run("RevokeGroup", func(t *testing.T) { 858 sharedDocs := []string{"forgroup1"} 859 s := createSharing(t, aliceInstance, sharedDocs, tsB.URL) 860 861 group := createGroup(t, aliceInstance, "friends") 862 require.NotNil(t, group) 863 fionaContact := addContactToGroup(t, aliceInstance, group, "Fiona") 864 require.NotNil(t, fionaContact) 865 gabyContact := addContactToGroup(t, aliceInstance, group, "Gaby") 866 require.NotNil(t, gabyContact) 867 868 eA := httpexpect.Default(t, tsA.URL) 869 870 obj := eA.POST("/sharings/"+s.ID()+"/recipients"). 871 WithHeader("Authorization", "Bearer "+aliceAppToken). 872 WithHeader("Content-Type", "application/vnd.api+json"). 873 WithBytes([]byte(`{ 874 "data": { 875 "type": "` + consts.Sharings + `", 876 "relationships": { 877 "recipients": { 878 "data": [ 879 {"id": "` + group.ID() + `", "type": "` + consts.Groups + `"} 880 ] 881 } 882 } 883 } 884 }`)). 885 Expect().Status(200). 886 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 887 Object() 888 889 data := obj.Value("data").Object() 890 attrs := data.Value("attributes").Object() 891 members := attrs.Value("members").Array() 892 members.Length().IsEqual(4) 893 894 owner := members.Value(0).Object() 895 owner.HasValue("status", "owner") 896 owner.HasValue("public_name", "Alice") 897 898 bob := members.Value(1).Object() 899 bob.HasValue("name", "Bob") 900 901 fiona := members.Value(2).Object() 902 fiona.HasValue("status", "pending") 903 fiona.HasValue("name", "Fiona") 904 fiona.HasValue("groups", []int{0}) 905 fiona.HasValue("only_in_groups", true) 906 907 gaby := members.Value(3).Object() 908 gaby.HasValue("status", "pending") 909 gaby.HasValue("name", "Gaby") 910 gaby.HasValue("groups", []int{0}) 911 gaby.HasValue("only_in_groups", true) 912 913 eA.DELETE("/sharings/"+s.ID()+"/groups/0"). 914 WithHeader("Authorization", "Bearer "+aliceAppToken). 915 WithHeader("Content-Type", "application/vnd.api+json"). 916 Expect().Status(204) 917 918 obj = eA.GET("/sharings/"+s.ID()). 919 WithHeader("Authorization", "Bearer "+aliceAppToken). 920 WithHeader("Content-Type", "application/vnd.api+json"). 921 Expect().Status(200). 922 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 923 Object() 924 925 data = obj.Value("data").Object() 926 attrs = data.Value("attributes").Object() 927 members = attrs.Value("members").Array() 928 members.Length().IsEqual(4) 929 930 owner = members.Value(0).Object() 931 owner.HasValue("status", "owner") 932 owner.HasValue("public_name", "Alice") 933 934 bob = members.Value(1).Object() 935 bob.HasValue("name", "Bob") 936 937 fiona = members.Value(2).Object() 938 fiona.HasValue("status", "revoked") 939 fiona.HasValue("name", "Fiona") 940 941 gaby = members.Value(3).Object() 942 gaby.HasValue("status", "revoked") 943 gaby.HasValue("name", "Gaby") 944 }) 945 946 t.Run("RevocationFromRecipient", func(t *testing.T) { 947 sharedDocs := []string{"mygreatid5", "mygreatid6"} 948 sharedRefs := []*sharing.SharedRef{} 949 s := createSharing(t, aliceInstance, sharedDocs, tsB.URL) 950 for _, id := range sharedDocs { 951 sid := iocozytests + "/" + id 952 sd, errs := createSharedDoc(aliceInstance, sid, s.SID) 953 sharedRefs = append(sharedRefs, sd) 954 assert.NoError(t, errs) 955 assert.NotNil(t, sd) 956 } 957 958 cli, err := sharing.CreateOAuthClient(aliceInstance, &s.Members[1]) 959 assert.NoError(t, err) 960 s.Credentials[0].InboundClientID = cli.ClientID 961 s.Credentials[0].Client = sharing.ConvertOAuthClient(cli) 962 token, err := sharing.CreateAccessToken(aliceInstance, cli, s.SID, permission.ALL) 963 assert.NoError(t, err) 964 s.Credentials[0].AccessToken = token 965 s.Members[1].Status = sharing.MemberStatusReady 966 967 s.Members = append(s.Members, sharing.Member{ 968 Status: sharing.MemberStatusReady, 969 Name: "Charlie", 970 Email: "charlie@cozy.local", 971 Instance: tsB.URL, 972 }) 973 clientC, err := sharing.CreateOAuthClient(aliceInstance, &s.Members[2]) 974 assert.NoError(t, err) 975 tokenC, err := sharing.CreateAccessToken(aliceInstance, clientC, s.SID, permission.ALL) 976 assert.NoError(t, err) 977 s.Credentials = append(s.Credentials, sharing.Credentials{ 978 Client: sharing.ConvertOAuthClient(clientC), 979 AccessToken: tokenC, 980 InboundClientID: clientC.ClientID, 981 }) 982 983 err = couchdb.UpdateDoc(aliceInstance, s) 984 assert.NoError(t, err) 985 986 err = s.AddTrackTriggers(aliceInstance) 987 assert.NoError(t, err) 988 err = s.AddReplicateTrigger(aliceInstance) 989 assert.NoError(t, err) 990 991 eA := httpexpect.Default(t, tsA.URL) 992 993 eA.DELETE("/sharings/"+s.ID()+"/answer"). 994 WithHeader("Authorization", "Bearer "+s.Credentials[0].AccessToken.AccessToken). 995 WithHeader("Content-Type", "application/vnd.api+json"). 996 Expect().Status(204) 997 998 assertOneRecipientIsRevoked(t, s, aliceInstance) 999 1000 eA.DELETE("/sharings/"+s.ID()+"/answer"). 1001 WithHeader("Authorization", "Bearer "+s.Credentials[1].AccessToken.AccessToken). 1002 WithHeader("Content-Type", "application/vnd.api+json"). 1003 Expect().Status(204) 1004 1005 assertLastRecipientIsRevoked(t, s, sharedRefs, aliceInstance) 1006 }) 1007 1008 t.Run("ClearAppInURL", func(t *testing.T) { 1009 host := sharings.ClearAppInURL("https://example.mycozy.cloud/") 1010 assert.Equal(t, "https://example.mycozy.cloud/", host) 1011 host = sharings.ClearAppInURL("https://example-drive.mycozy.cloud/") 1012 assert.Equal(t, "https://example.mycozy.cloud/", host) 1013 host = sharings.ClearAppInURL("https://my-cozy.example.net/") 1014 assert.Equal(t, "https://my-cozy.example.net/", host) 1015 }) 1016 } 1017 1018 func assertSharingByAliceToBobAndDave(t *testing.T, obj *httpexpect.Array, instance *instance.Instance) { 1019 t.Helper() 1020 1021 obj.Length().IsEqual(3) 1022 1023 owner := obj.Value(0).Object() 1024 owner.HasValue("status", "owner") 1025 owner.HasValue("public_name", "Alice") 1026 owner.HasValue("email", "alice@example.net") 1027 owner.HasValue("instance", "http://"+instance.Domain) 1028 1029 recipient := obj.Value(1).Object() 1030 recipient.HasValue("status", "pending") 1031 recipient.HasValue("name", "Bob") 1032 recipient.HasValue("email", "bob@example.net") 1033 recipient.NotContainsKey("read_only") 1034 1035 recipient2 := obj.Value(2).Object() 1036 recipient2.HasValue("status", "pending") 1037 recipient2.HasValue("name", "Dave") 1038 recipient2.HasValue("email", "dave@example.net") 1039 recipient2.HasValue("read_only", true) 1040 } 1041 1042 func assertSharingIsCorrectOnSharer(t *testing.T, obj *httpexpect.Object, sharingID string, instance *instance.Instance) { 1043 t.Helper() 1044 1045 data := obj.Value("data").Object() 1046 1047 data.HasValue("type", consts.Sharings) 1048 data.Value("meta").Object().Value("rev").String().NotEmpty() 1049 data.Value("links").Object().HasValue("self", "/sharings/"+sharingID) 1050 1051 attrs := data.Value("attributes").Object() 1052 attrs.HasValue("description", "this is a test") 1053 attrs.HasValue("app_slug", "testapp") 1054 attrs.HasValue("owner", true) 1055 attrs.Value("created_at").String().AsDateTime(time.RFC3339) 1056 attrs.Value("updated_at").String().AsDateTime(time.RFC3339) 1057 attrs.NotContainsKey("credentials") 1058 1059 assertSharingByAliceToBobAndDave(t, attrs.Value("members").Array(), instance) 1060 1061 rules := attrs.Value("rules").Array() 1062 rules.Length().IsEqual(1) 1063 rule := rules.Value(0).Object() 1064 rule.HasValue("title", "test one") 1065 rule.HasValue("doctype", iocozytests) 1066 rule.HasValue("values", []interface{}{"000000"}) 1067 } 1068 1069 func assertInvitationMailWasSent(t *testing.T, instance *instance.Instance) string { 1070 var jobs []job.Job 1071 couchReq := &couchdb.FindRequest{ 1072 UseIndex: "by-worker-and-state", 1073 Selector: mango.And( 1074 mango.Equal("worker", "sendmail"), 1075 mango.Exists("state"), 1076 ), 1077 Sort: mango.SortBy{ 1078 mango.SortByField{Field: "worker", Direction: "desc"}, 1079 }, 1080 Limit: 2, 1081 } 1082 err := couchdb.FindDocs(instance, consts.Jobs, couchReq, &jobs) 1083 assert.NoError(t, err) 1084 assert.Len(t, jobs, 2) 1085 var msg map[string]interface{} 1086 // Ignore the mail sent to Dave 1087 err = json.Unmarshal(jobs[0].Message, &msg) 1088 assert.NoError(t, err) 1089 if msg["recipient_name"] == "Dave" { 1090 err = json.Unmarshal(jobs[1].Message, &msg) 1091 assert.NoError(t, err) 1092 } 1093 assert.Equal(t, msg["mode"], "from") 1094 assert.Equal(t, msg["template_name"], "sharing_request") 1095 values := msg["template_values"].(map[string]interface{}) 1096 assert.Equal(t, values["SharerPublicName"], "Alice") 1097 discoveryLink = values["SharingLink"].(string) 1098 return values["Description"].(string) 1099 } 1100 1101 func assertSharingRequestHasBeenCreated(t *testing.T, instanceA, instanceB *instance.Instance, serverURL string) { 1102 var results []*sharing.Sharing 1103 req := couchdb.AllDocsRequest{} 1104 err := couchdb.GetAllDocs(instanceB, consts.Sharings, &req, &results) 1105 assert.NoError(t, err) 1106 assert.Len(t, results, 1) 1107 s := results[0] 1108 assert.Equal(t, s.SID, sharingID) 1109 assert.False(t, s.Active) 1110 assert.False(t, s.Owner) 1111 assert.Equal(t, s.Description, "this is a test") 1112 assert.Equal(t, s.AppSlug, "testapp") 1113 1114 assert.Len(t, s.Members, 3) 1115 owner := s.Members[0] 1116 assert.Equal(t, owner.Status, "owner") 1117 assert.Equal(t, owner.PublicName, "Alice") 1118 assert.Equal(t, owner.Email, "alice@example.net") 1119 assert.Equal(t, owner.Instance, "http://"+instanceA.Domain) 1120 recipient := s.Members[1] 1121 assert.Equal(t, recipient.Status, "pending") 1122 assert.Equal(t, recipient.Email, "bob@example.net") 1123 assert.Equal(t, recipient.Instance, serverURL) 1124 recipient = s.Members[2] 1125 assert.Equal(t, recipient.Status, "pending") 1126 assert.Equal(t, recipient.Email, "dave@example.net") 1127 assert.Equal(t, recipient.ReadOnly, true) 1128 1129 assert.Len(t, s.Rules, 1) 1130 rule := s.Rules[0] 1131 assert.Equal(t, rule.Title, "test one") 1132 assert.Equal(t, rule.DocType, iocozytests) 1133 assert.NotEmpty(t, rule.Values) 1134 } 1135 1136 func fakeAliceInstance(t *testing.T, instance *instance.Instance, serverURL string) { 1137 var results []*sharing.Sharing 1138 req := couchdb.AllDocsRequest{} 1139 err := couchdb.GetAllDocs(instance, consts.Sharings, &req, &results) 1140 assert.NoError(t, err) 1141 assert.Len(t, results, 1) 1142 s := results[0] 1143 assert.Len(t, s.Members, 3) 1144 s.Members[0].Instance = serverURL 1145 err = couchdb.UpdateDoc(instance, s) 1146 assert.NoError(t, err) 1147 } 1148 1149 func assertCredentialsHasBeenExchanged(t *testing.T, instanceA, instanceB *instance.Instance, urlA, urlB string) { 1150 var resultsA []map[string]interface{} 1151 req := couchdb.AllDocsRequest{} 1152 err := couchdb.GetAllDocs(instanceB, consts.OAuthClients, &req, &resultsA) 1153 assert.NoError(t, err) 1154 assert.True(t, len(resultsA) > 0) 1155 clientA := resultsA[len(resultsA)-1] 1156 assert.Equal(t, clientA["client_kind"], "sharing") 1157 assert.Equal(t, clientA["client_uri"], urlA+"/") 1158 assert.Equal(t, clientA["client_name"], "Sharing Alice") 1159 1160 var resultsB []map[string]interface{} 1161 err = couchdb.GetAllDocs(instanceA, consts.OAuthClients, &req, &resultsB) 1162 assert.NoError(t, err) 1163 assert.True(t, len(resultsB) > 0) 1164 clientB := resultsB[len(resultsB)-1] 1165 assert.Equal(t, clientB["client_kind"], "sharing") 1166 assert.Equal(t, clientB["client_uri"], urlB+"/") 1167 assert.Equal(t, clientB["client_name"], "Sharing Bob") 1168 1169 var sharingsA []*sharing.Sharing 1170 err = couchdb.GetAllDocs(instanceA, consts.Sharings, &req, &sharingsA) 1171 assert.NoError(t, err) 1172 assert.True(t, len(sharingsA) > 0) 1173 assert.Len(t, sharingsA[0].Credentials, 2) 1174 credentials := sharingsA[0].Credentials[0] 1175 if assert.NotNil(t, credentials.Client) { 1176 assert.Equal(t, credentials.Client.ClientID, clientA["_id"]) 1177 } 1178 if assert.NotNil(t, credentials.AccessToken) { 1179 assert.NotEmpty(t, credentials.AccessToken.AccessToken) 1180 assert.NotEmpty(t, credentials.AccessToken.RefreshToken) 1181 aliceAccessToken = credentials.AccessToken.AccessToken 1182 } 1183 assert.Equal(t, sharingsA[0].Members[1].Status, "ready") 1184 assert.Equal(t, sharingsA[0].Members[2].Status, "pending") 1185 1186 var sharingsB []*sharing.Sharing 1187 err = couchdb.GetAllDocs(instanceB, consts.Sharings, &req, &sharingsB) 1188 assert.NoError(t, err) 1189 assert.True(t, len(sharingsB) > 0) 1190 assert.Len(t, sharingsB[0].Credentials, 1) 1191 credentials = sharingsB[0].Credentials[0] 1192 if assert.NotNil(t, credentials.Client) { 1193 assert.Equal(t, credentials.Client.ClientID, clientB["_id"]) 1194 } 1195 if assert.NotNil(t, credentials.AccessToken) { 1196 assert.NotEmpty(t, credentials.AccessToken.AccessToken) 1197 assert.NotEmpty(t, credentials.AccessToken.RefreshToken) 1198 } 1199 } 1200 1201 func assertOneRecipientIsRevoked(t *testing.T, s *sharing.Sharing, instance *instance.Instance) { 1202 var sRevoked sharing.Sharing 1203 err := couchdb.GetDoc(instance, s.DocType(), s.SID, &sRevoked) 1204 assert.NoError(t, err) 1205 1206 assert.Equal(t, sharing.MemberStatusRevoked, sRevoked.Members[1].Status) 1207 assert.Equal(t, sharing.MemberStatusReady, sRevoked.Members[2].Status) 1208 assert.NotEmpty(t, sRevoked.Triggers.TrackIDs) 1209 assert.NotEmpty(t, sRevoked.Triggers.ReplicateID) 1210 assert.True(t, sRevoked.Active) 1211 } 1212 1213 func assertLastRecipientIsRevoked(t *testing.T, s *sharing.Sharing, refs []*sharing.SharedRef, instance *instance.Instance) { 1214 var sRevoked sharing.Sharing 1215 err := couchdb.GetDoc(instance, s.DocType(), s.SID, &sRevoked) 1216 assert.NoError(t, err) 1217 1218 assert.Equal(t, sharing.MemberStatusRevoked, sRevoked.Members[1].Status) 1219 assert.Equal(t, sharing.MemberStatusRevoked, sRevoked.Members[2].Status) 1220 assert.Empty(t, sRevoked.Triggers.TrackIDs) 1221 assert.Empty(t, sRevoked.Triggers.ReplicateID) 1222 assert.False(t, sRevoked.Active) 1223 1224 var sdoc sharing.SharedRef 1225 err = couchdb.GetDoc(instance, refs[0].DocType(), refs[0].ID(), &sdoc) 1226 assert.EqualError(t, err, "CouchDB(not_found): deleted") 1227 err = couchdb.GetDoc(instance, refs[1].DocType(), refs[1].ID(), &sdoc) 1228 assert.EqualError(t, err, "CouchDB(not_found): deleted") 1229 } 1230 1231 func createBobContact(t *testing.T, instance *instance.Instance) *contact.Contact { 1232 return createContact(t, instance, "Bob", "bob@example.net") 1233 } 1234 1235 func createContact(t *testing.T, inst *instance.Instance, name, email string) *contact.Contact { 1236 t.Helper() 1237 mail := map[string]interface{}{"address": email} 1238 c := contact.New() 1239 c.M["fullname"] = name 1240 c.M["email"] = []interface{}{mail} 1241 require.NoError(t, couchdb.CreateDoc(inst, c)) 1242 return c 1243 } 1244 1245 func createGroup(t *testing.T, inst *instance.Instance, name string) *contact.Group { 1246 t.Helper() 1247 g := contact.NewGroup() 1248 g.M["name"] = name 1249 require.NoError(t, couchdb.CreateDoc(inst, g)) 1250 return g 1251 } 1252 1253 func addContactToGroup(t *testing.T, inst *instance.Instance, g *contact.Group, contactName string) *contact.Contact { 1254 t.Helper() 1255 email := strings.ToLower(contactName) + "@example.net" 1256 mail := map[string]interface{}{"address": email} 1257 c := contact.New() 1258 c.M["fullname"] = contactName 1259 c.M["email"] = []interface{}{mail} 1260 c.M["relationships"] = map[string]interface{}{ 1261 "groups": map[string]interface{}{ 1262 "data": []interface{}{ 1263 map[string]interface{}{ 1264 "_id": g.ID(), 1265 "_type": consts.Groups, 1266 }, 1267 }, 1268 }, 1269 } 1270 require.NoError(t, couchdb.CreateDoc(inst, c)) 1271 return c 1272 } 1273 1274 func createSharing(t *testing.T, inst *instance.Instance, values []string, serverURL string) *sharing.Sharing { 1275 bobContact := createBobContact(t, inst) 1276 assert.NotNil(t, bobContact) 1277 1278 r := sharing.Rule{ 1279 Title: "test", 1280 DocType: iocozytests, 1281 Values: values, 1282 Add: sharing.ActionRuleSync, 1283 } 1284 mail, err := bobContact.ToMailAddress() 1285 assert.NoError(t, err) 1286 m := sharing.Member{ 1287 Name: bobContact.Get("fullname").(string), 1288 Email: mail.Email, 1289 Instance: serverURL, 1290 } 1291 s := &sharing.Sharing{ 1292 Owner: true, 1293 Rules: []sharing.Rule{r}, 1294 } 1295 s.Credentials = append(s.Credentials, sharing.Credentials{}) 1296 err = s.BeOwner(inst, "") 1297 assert.NoError(t, err) 1298 s.Members = append(s.Members, m) 1299 1300 err = couchdb.CreateDoc(inst, s) 1301 assert.NoError(t, err) 1302 assert.NotNil(t, s) 1303 return s 1304 } 1305 1306 func createSharedDoc(inst *instance.Instance, id, sharingID string) (*sharing.SharedRef, error) { 1307 ref := &sharing.SharedRef{ 1308 SID: id, 1309 Revisions: &sharing.RevsTree{Rev: "1-aaa"}, 1310 Infos: map[string]sharing.SharedInfo{ 1311 sharingID: {Rule: 0}, 1312 }, 1313 } 1314 err := couchdb.CreateNamedDocWithDB(inst, ref) 1315 if err != nil { 1316 return nil, err 1317 } 1318 return ref, nil 1319 } 1320 1321 func generateAppToken(inst *instance.Instance, slug, doctype string) string { 1322 rules := permission.Set{ 1323 permission.Rule{ 1324 Type: doctype, 1325 Verbs: permission.ALL, 1326 }, 1327 } 1328 permReq := permission.Permission{ 1329 Permissions: rules, 1330 Type: permission.TypeWebapp, 1331 SourceID: consts.Apps + "/" + slug, 1332 } 1333 err := couchdb.CreateDoc(inst, &permReq) 1334 if err != nil { 1335 return "" 1336 } 1337 manifest := &couchdb.JSONDoc{ 1338 Type: consts.Apps, 1339 M: map[string]interface{}{ 1340 "_id": consts.Apps + "/" + slug, 1341 "slug": slug, 1342 "permissions": rules, 1343 }, 1344 } 1345 err = couchdb.CreateNamedDocWithDB(inst, manifest) 1346 if err != nil { 1347 return "" 1348 } 1349 return inst.BuildAppToken(slug, "") 1350 }