github.com/status-im/status-go@v1.1.0/protocol/communities/community_test.go (about) 1 package communities 2 3 import ( 4 "crypto/ecdsa" 5 "encoding/json" 6 "testing" 7 "time" 8 9 "github.com/golang/protobuf/proto" 10 11 "github.com/stretchr/testify/suite" 12 13 "github.com/status-im/status-go/eth-node/crypto" 14 "github.com/status-im/status-go/protocol/common" 15 "github.com/status-im/status-go/protocol/protobuf" 16 ) 17 18 func TestCommunitySuite(t *testing.T) { 19 suite.Run(t, new(CommunitySuite)) 20 } 21 22 const testChatID1 = "chat-id-1" 23 const testCategoryID1 = "category-id-1" 24 const testCategoryName1 = "category-name-1" 25 const testChatID2 = "chat-id-2" 26 27 type TimeSourceStub struct { 28 } 29 30 func (t *TimeSourceStub) GetCurrentTime() uint64 { 31 return uint64(time.Now().Unix()) 32 } 33 34 type CommunitySuite struct { 35 suite.Suite 36 37 identity *ecdsa.PrivateKey 38 communityID []byte 39 40 member1 *ecdsa.PrivateKey 41 member2 *ecdsa.PrivateKey 42 member3 *ecdsa.PrivateKey 43 44 member1Key string 45 member2Key string 46 member3Key string 47 } 48 49 func (s *CommunitySuite) SetupTest() { 50 identity, err := crypto.GenerateKey() 51 s.Require().NoError(err) 52 s.identity = identity 53 s.communityID = crypto.CompressPubkey(&identity.PublicKey) 54 55 member1, err := crypto.GenerateKey() 56 s.Require().NoError(err) 57 s.member1 = member1 58 59 member2, err := crypto.GenerateKey() 60 s.Require().NoError(err) 61 s.member2 = member2 62 63 member3, err := crypto.GenerateKey() 64 s.Require().NoError(err) 65 s.member3 = member3 66 67 s.member1Key = common.PubkeyToHex(&s.member1.PublicKey) 68 s.member2Key = common.PubkeyToHex(&s.member2.PublicKey) 69 s.member3Key = common.PubkeyToHex(&s.member3.PublicKey) 70 71 } 72 73 func (s *CommunitySuite) TestHasPermission() { 74 // returns false if empty public key is passed 75 community := &Community{} 76 ownerKey, err := crypto.GenerateKey() 77 s.Require().NoError(err) 78 79 nonMemberKey, err := crypto.GenerateKey() 80 s.Require().NoError(err) 81 82 memberKey, err := crypto.GenerateKey() 83 s.Require().NoError(err) 84 85 s.Require().False(community.hasRoles(nil, adminRole())) 86 87 // returns false if key is passed, but config is nil 88 s.Require().False(community.hasRoles(&nonMemberKey.PublicKey, adminRole())) 89 90 // returns true if the user is the owner 91 92 communityDescription := &protobuf.CommunityDescription{} 93 communityDescription.Members = make(map[string]*protobuf.CommunityMember) 94 communityDescription.Members[common.PubkeyToHex(&ownerKey.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_OWNER}} 95 communityDescription.Members[common.PubkeyToHex(&memberKey.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_ADMIN}} 96 97 community.config = &Config{ID: &ownerKey.PublicKey, CommunityDescription: communityDescription} 98 99 s.Require().True(community.hasRoles(&ownerKey.PublicKey, ownerRole())) 100 101 // return false if user is not a member 102 s.Require().False(community.hasRoles(&nonMemberKey.PublicKey, adminRole())) 103 104 // return true if user is a member and has permissions 105 s.Require().True(community.hasRoles(&memberKey.PublicKey, adminRole())) 106 107 // return false if user is a member and does not have permissions 108 s.Require().False(community.hasRoles(&memberKey.PublicKey, ownerRole())) 109 110 } 111 112 func (s *CommunitySuite) TestCreateChat() { 113 newChatID := "new-chat-id" 114 org := s.buildCommunity(&s.identity.PublicKey) 115 org.config.PrivateKey = nil 116 org.config.ID = nil 117 118 identity := &protobuf.ChatIdentity{ 119 DisplayName: "new-chat-display-name", 120 Description: "new-chat-description", 121 } 122 permissions := &protobuf.CommunityPermissions{ 123 Access: protobuf.CommunityPermissions_AUTO_ACCEPT, 124 } 125 126 _, err := org.CreateChat(newChatID, &protobuf.CommunityChat{ 127 Identity: identity, 128 Permissions: permissions, 129 }) 130 131 s.Require().Equal(ErrNotAuthorized, err) 132 133 org.config.PrivateKey = s.identity 134 org.config.ID = &s.identity.PublicKey 135 136 changes, err := org.CreateChat(newChatID, &protobuf.CommunityChat{ 137 Identity: identity, 138 Permissions: permissions, 139 }) 140 141 description := org.config.CommunityDescription 142 143 s.Require().NoError(err) 144 s.Require().NotNil(description) 145 s.Require().NotNil(description.Chats[newChatID]) 146 s.Require().NotEmpty(description.Clock) 147 s.Require().Equal(len(description.Chats)-1, int(description.Chats[newChatID].Position)) 148 s.Require().Equal(permissions, description.Chats[newChatID].Permissions) 149 s.Require().Equal(identity, description.Chats[newChatID].Identity) 150 151 s.Require().NotNil(changes) 152 s.Require().NotNil(changes.ChatsAdded[newChatID]) 153 154 // Add a community with the same name 155 156 _, err = org.CreateChat("different-chat-id", &protobuf.CommunityChat{ 157 Identity: identity, 158 Permissions: permissions, 159 }) 160 161 s.Require().Error(err) 162 } 163 164 func (s *CommunitySuite) TestEditChat() { 165 newChatID := "new-chat-id" 166 org := s.buildCommunity(&s.identity.PublicKey) 167 168 identity := &protobuf.ChatIdentity{ 169 DisplayName: "new-chat-display-name", 170 Description: "new-chat-description", 171 Emoji: "😎", 172 Color: "#000000", 173 } 174 permissions := &protobuf.CommunityPermissions{ 175 Access: protobuf.CommunityPermissions_AUTO_ACCEPT, 176 Private: false, 177 } 178 179 _, err := org.CreateChat(newChatID, &protobuf.CommunityChat{ 180 Identity: identity, 181 Permissions: permissions, 182 HideIfPermissionsNotMet: false, 183 }) 184 s.Require().NoError(err) 185 186 org.config.PrivateKey = nil 187 org.config.ID = nil 188 editedIdentity := &protobuf.ChatIdentity{ 189 DisplayName: "edited-new-chat-display-name", 190 Description: "edited-new-chat-description", 191 Emoji: "🤘", 192 Color: "#FFFFFF", 193 } 194 editedPermissions := &protobuf.CommunityPermissions{ 195 Access: protobuf.CommunityPermissions_AUTO_ACCEPT, 196 Private: true, 197 } 198 _, err = org.EditChat(newChatID, &protobuf.CommunityChat{ 199 Identity: editedIdentity, 200 Permissions: editedPermissions, 201 }) 202 s.Require().Equal(ErrNotAuthorized, err) 203 204 description := org.config.CommunityDescription 205 org.config.PrivateKey = s.identity 206 org.config.ID = &s.identity.PublicKey 207 editChanges, err := org.EditChat(newChatID, &protobuf.CommunityChat{ 208 Identity: editedIdentity, 209 Permissions: editedPermissions, 210 HideIfPermissionsNotMet: true, 211 }) 212 213 s.Require().NoError(err) 214 215 s.Require().NotNil(description.Chats[newChatID]) 216 s.Require().NotEmpty(description.Clock) 217 s.Require().Equal(editedPermissions, description.Chats[newChatID].Permissions) 218 s.Require().Equal(editedIdentity, description.Chats[newChatID].Identity) 219 220 s.Require().NotNil(editChanges) 221 s.Require().NotNil(editChanges.ChatsModified[newChatID]) 222 s.Require().Equal(editChanges.ChatsModified[newChatID].ChatModified.Identity, editedIdentity) 223 s.Require().Equal(editChanges.ChatsModified[newChatID].ChatModified.Permissions, editedPermissions) 224 s.Require().Equal(editChanges.ChatsModified[newChatID].ChatModified.HideIfPermissionsNotMet, true) 225 } 226 227 func (s *CommunitySuite) TestDeleteChat() { 228 org := s.buildCommunity(&s.identity.PublicKey) 229 org.config.PrivateKey = nil 230 org.config.ID = nil 231 232 _, err := org.DeleteChat(testChatID1) 233 s.Require().Equal(ErrNotAuthorized, err) 234 change1Clock := org.Clock() 235 236 org.config.PrivateKey = s.identity 237 org.config.ID = &s.identity.PublicKey 238 239 changes, err := org.DeleteChat(testChatID1) 240 s.Require().NoError(err) 241 s.Require().NotNil(changes) 242 change2Clock := org.Clock() 243 244 s.Require().Nil(org.Chats()[testChatID1]) 245 s.Require().Len(changes.ChatsRemoved, 1) 246 s.Require().Greater(change2Clock, change1Clock) 247 } 248 249 func (s *CommunitySuite) TestRemoveUserFromChat() { 250 org := s.buildCommunity(&s.identity.PublicKey) 251 org.config.PrivateKey = nil 252 org.config.ID = nil 253 // Not an admin 254 _, err := org.RemoveUserFromOrg(&s.member1.PublicKey) 255 s.Require().Equal(ErrNotAuthorized, err) 256 257 // Add admin to community 258 org.config.PrivateKey = s.identity 259 org.config.ID = &s.identity.PublicKey 260 261 actualCommunity, err := org.RemoveUserFromChat(&s.member1.PublicKey, testChatID1) 262 s.Require().Nil(err) 263 s.Require().NotNil(actualCommunity) 264 265 // Check member has not been removed 266 s.Require().True(org.HasMember(&s.member1.PublicKey)) 267 268 // Check member has not been removed from org 269 _, ok := actualCommunity.Members[common.PubkeyToHex(&s.member1.PublicKey)] 270 s.Require().True(ok) 271 272 // Check member has been removed from chat 273 _, ok = actualCommunity.Chats[testChatID1].Members[common.PubkeyToHex(&s.member1.PublicKey)] 274 s.Require().False(ok) 275 } 276 277 func (s *CommunitySuite) TestRemoveUserFormOrg() { 278 org := s.buildCommunity(&s.identity.PublicKey) 279 org.config.PrivateKey = nil 280 org.config.ID = nil 281 // Not an admin 282 _, err := org.RemoveUserFromOrg(&s.member1.PublicKey) 283 s.Require().Equal(ErrNotAuthorized, err) 284 285 // Add admin to community 286 org.config.PrivateKey = s.identity 287 org.config.ID = &s.identity.PublicKey 288 289 actualCommunity, err := org.RemoveUserFromOrg(&s.member1.PublicKey) 290 s.Require().Nil(err) 291 s.Require().NotNil(actualCommunity) 292 293 // Check member has been removed 294 s.Require().False(org.HasMember(&s.member1.PublicKey)) 295 296 // Check member has been removed from org 297 _, ok := actualCommunity.Members[common.PubkeyToHex(&s.member1.PublicKey)] 298 s.Require().False(ok) 299 300 // Check member has been removed from chat 301 _, ok = actualCommunity.Chats[testChatID1].Members[common.PubkeyToHex(&s.member1.PublicKey)] 302 s.Require().False(ok) 303 } 304 305 func (s *CommunitySuite) TestRemoveOurselvesFormOrg() { 306 org := s.buildCommunity(&s.identity.PublicKey) 307 308 // We don't need to be an admin to remove ourselves from community 309 org.config.PrivateKey = nil 310 311 org.RemoveOurselvesFromOrg(&s.member1.PublicKey) 312 313 // Check member has been removed from org 314 s.Require().False(org.HasMember(&s.member1.PublicKey)) 315 316 // Check member has been removed from chat 317 _, ok := org.config.CommunityDescription.Chats[testChatID1].Members[common.PubkeyToHex(&s.member1.PublicKey)] 318 s.Require().False(ok) 319 } 320 321 func (s *CommunitySuite) TestAcceptRequestToJoin() { 322 // WHAT TO DO WITH ENS 323 // TEST CASE 1: Not an admin 324 // TEST CASE 2: No request to join 325 // TEST CASE 3: Valid 326 } 327 328 func (s *CommunitySuite) TestDeclineRequestToJoin() { 329 // TEST CASE 1: Not an admin 330 // TEST CASE 2: No request to join 331 // TEST CASE 3: Valid 332 } 333 334 func (s *CommunitySuite) TestValidateRequestToJoin() { 335 description := &protobuf.CommunityDescription{} 336 337 key, err := crypto.GenerateKey() 338 s.Require().NoError(err) 339 340 signer := &key.PublicKey 341 342 revealedAccounts := []*protobuf.RevealedAccount{ 343 &protobuf.RevealedAccount{ 344 Address: "0x0100000000000000000000000000000000000000"}, 345 } 346 347 request := &protobuf.CommunityRequestToJoin{ 348 EnsName: "donvanvliet.stateofus.eth", 349 CommunityId: s.communityID, 350 Clock: uint64(time.Now().Unix()), 351 RevealedAccounts: revealedAccounts, 352 } 353 354 requestWithChatID := &protobuf.CommunityRequestToJoin{ 355 EnsName: "donvanvliet.stateofus.eth", 356 CommunityId: s.communityID, 357 ChatId: testChatID1, 358 Clock: uint64(time.Now().Unix()), 359 RevealedAccounts: revealedAccounts, 360 } 361 362 requestWithoutENS := &protobuf.CommunityRequestToJoin{ 363 CommunityId: s.communityID, 364 Clock: uint64(time.Now().Unix()), 365 RevealedAccounts: revealedAccounts, 366 } 367 368 requestWithChatWithoutENS := &protobuf.CommunityRequestToJoin{ 369 CommunityId: s.communityID, 370 ChatId: testChatID1, 371 Clock: uint64(time.Now().Unix()), 372 RevealedAccounts: revealedAccounts, 373 } 374 375 // MATRIX 376 // NO_MEMBERHSIP - NO_MEMBERSHIP -> Error -> Anyone can join org, chat is read/write for anyone 377 // NO_MEMBRISHIP - INVITATION_ONLY -> Error -> Anyone can join org, chat is invitation only 378 // NO_MEMBERSHIP - ON_REQUEST -> Success -> Anyone can join org, chat is on request and needs approval 379 // INVITATION_ONLY - NO_MEMBERSHIP -> TODO -> Org is invitation only, chat is read-write for members 380 // INVITATION_ONLY - INVITATION_ONLY -> Error -> Org is invitation only, chat is invitation only 381 // INVITATION_ONLY - ON_REQUEST -> TODO -> Error -> Org is invitation only, member of the org need to request access for chat 382 // ON_REQUEST - NO_MEMBRERSHIP -> TODO -> Error -> Org is on request, chat is read write for members 383 // ON_REQUEST - INVITATION_ONLY -> Error -> Org is on request, chat is invitation only for members 384 // ON_REQUEST - ON_REQUEST -> Fine -> Org is on request, chat is on request 385 386 testCases := []struct { 387 name string 388 config Config 389 request *protobuf.CommunityRequestToJoin 390 signer *ecdsa.PublicKey 391 err error 392 }{ 393 { 394 name: "on-request access to community", 395 config: s.configOnRequest(), 396 signer: signer, 397 request: request, 398 err: nil, 399 }, 400 { 401 name: "not admin", 402 config: Config{MemberIdentity: key, CommunityDescription: description}, 403 signer: signer, 404 request: request, 405 err: ErrNotAdmin, 406 }, 407 { 408 name: "ens-only org and missing ens", 409 config: s.configENSOnly(), 410 signer: signer, 411 request: requestWithoutENS, 412 err: ErrCantRequestAccess, 413 }, 414 { 415 name: "ens-only chat and missing ens", 416 config: s.configChatENSOnly(), 417 signer: signer, 418 request: requestWithChatWithoutENS, 419 err: ErrCantRequestAccess, 420 }, 421 { 422 name: "missing chat", 423 config: s.configOnRequest(), 424 signer: signer, 425 request: requestWithChatID, 426 err: ErrChatNotFound, 427 }, 428 // Org-Chat combinations 429 // NO_MEMBERSHIP-NO_MEMBERSHIP = error as you should not be 430 // requesting access 431 { 432 name: "no-membership org with no-membeship chat", 433 config: s.configNoMembershipOrgNoMembershipChat(), 434 signer: signer, 435 request: requestWithChatID, 436 err: ErrCantRequestAccess, 437 }, 438 // NO_MEMBERSHIP-ON_REQUEST = this is a valid case 439 { 440 name: "no-membership org with on-request chat", 441 config: s.configNoMembershipOrgOnRequestChat(), 442 signer: signer, 443 request: requestWithChatID, 444 }, 445 // ON_REQUEST-ON_REQUEST success 446 { 447 name: "on-request org with on-request chat", 448 config: s.configOnRequestOrgOnRequestChat(), 449 signer: signer, 450 request: requestWithChatID, 451 }, 452 } 453 454 for _, tc := range testCases { 455 s.Run(tc.name, func() { 456 org, err := New(tc.config, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil) 457 s.Require().NoError(err) 458 err = org.ValidateRequestToJoin(tc.signer, tc.request) 459 s.Require().Equal(tc.err, err) 460 }) 461 } 462 } 463 464 func (s *CommunitySuite) TestCanPostCanView() { 465 chatID := "chat-id" 466 memberKey := common.PubkeyToHex(&s.member1.PublicKey) 467 // Member has no channel role 468 description := &protobuf.CommunityDescription{ 469 Members: map[string]*protobuf.CommunityMember{ 470 memberKey: &protobuf.CommunityMember{}, 471 }, 472 Chats: map[string]*protobuf.CommunityChat{ 473 chatID: &protobuf.CommunityChat{ 474 Members: map[string]*protobuf.CommunityMember{ 475 memberKey: &protobuf.CommunityMember{}, 476 }, 477 }, 478 }, 479 } 480 481 community := &Community{config: &Config{ID: &s.member2.PublicKey}} 482 community.config.CommunityDescription = description 483 484 result, err := community.CanPost(&s.member1.PublicKey, chatID, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE) 485 s.Require().NoError(err) 486 s.Require().True(result) 487 488 result = community.CanView(&s.member1.PublicKey, chatID) 489 s.Require().True(result) 490 491 // member has view channel permissions 492 description.Chats[chatID].Members[memberKey].ChannelRole = protobuf.CommunityMember_CHANNEL_ROLE_VIEWER 493 494 result, err = community.CanPost(&s.member1.PublicKey, chatID, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE) 495 s.Require().NoError(err) 496 s.Require().False(result) 497 498 result = community.CanView(&s.member1.PublicKey, chatID) 499 s.Require().True(result) 500 } 501 502 func (s *CommunitySuite) TestCanPost() { 503 notMember := &s.member3.PublicKey 504 member := &s.member1.PublicKey 505 506 testCases := []struct { 507 name string 508 config Config 509 member *ecdsa.PublicKey 510 err error 511 canPost bool 512 }{ 513 { 514 name: "no-membership org with no-membership chat", 515 config: s.configNoMembershipOrgNoMembershipChat(), 516 member: notMember, 517 canPost: false, 518 }, 519 { 520 name: "membership org with no-membership chat-not-a-member", 521 config: s.configOnRequestOrgNoMembershipChat(), 522 member: notMember, 523 canPost: false, 524 }, 525 { 526 name: "membership org with no-membership chat", 527 config: s.configOnRequestOrgNoMembershipChat(), 528 member: member, 529 canPost: true, 530 }, 531 { 532 name: "creator can always post of course", 533 config: s.configOnRequestOrgNoMembershipChat(), 534 member: &s.identity.PublicKey, 535 canPost: true, 536 }, 537 } 538 539 for _, tc := range testCases { 540 s.Run(tc.name, func() { 541 var err error 542 org, err := New(tc.config, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil) 543 s.Require().NoError(err) 544 545 canPost, err := org.CanPost(tc.member, testChatID1, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE) 546 s.Require().Equal(tc.err, err) 547 s.Require().Equal(tc.canPost, canPost) 548 }) 549 } 550 } 551 552 func (s *CommunitySuite) TestHandleCommunityDescription() { 553 key, err := crypto.GenerateKey() 554 s.Require().NoError(err) 555 556 signer := &key.PublicKey 557 558 buildChanges := func(c *Community) *CommunityChanges { 559 return c.emptyCommunityChanges() 560 } 561 562 testCases := []struct { 563 name string 564 description func(*Community) *protobuf.CommunityDescription 565 changes func(*Community) *CommunityChanges 566 signer *ecdsa.PublicKey 567 err error 568 }{ 569 { 570 name: "updated version but no changes", 571 description: s.identicalCommunityDescription, 572 signer: signer, 573 changes: buildChanges, 574 err: nil, 575 }, 576 { 577 name: "updated version but lower clock", 578 description: s.oldCommunityDescription, 579 signer: signer, 580 changes: func(c *Community) *CommunityChanges { return nil }, 581 err: ErrInvalidCommunityDescriptionClockOutdated, 582 }, 583 { 584 name: "removed member from org", 585 description: s.removedMemberCommunityDescription, 586 signer: signer, 587 changes: func(org *Community) *CommunityChanges { 588 changes := org.emptyCommunityChanges() 589 changes.MembersRemoved[s.member1Key] = &protobuf.CommunityMember{} 590 changes.ChatsModified[testChatID1] = &CommunityChatChanges{ 591 MembersAdded: make(map[string]*protobuf.CommunityMember), 592 MembersRemoved: make(map[string]*protobuf.CommunityMember), 593 } 594 changes.ChatsModified[testChatID1].MembersRemoved[s.member1Key] = &protobuf.CommunityMember{} 595 596 return changes 597 }, 598 err: nil, 599 }, 600 { 601 name: "added member from org", 602 description: s.addedMemberCommunityDescription, 603 signer: signer, 604 changes: func(org *Community) *CommunityChanges { 605 changes := org.emptyCommunityChanges() 606 changes.MembersAdded[s.member3Key] = &protobuf.CommunityMember{} 607 changes.ChatsModified[testChatID1] = &CommunityChatChanges{ 608 MembersAdded: make(map[string]*protobuf.CommunityMember), 609 MembersRemoved: make(map[string]*protobuf.CommunityMember), 610 } 611 changes.ChatsModified[testChatID1].MembersAdded[s.member3Key] = &protobuf.CommunityMember{} 612 613 return changes 614 }, 615 err: nil, 616 }, 617 { 618 name: "chat added to org", 619 description: s.addedChatCommunityDescription, 620 signer: signer, 621 changes: func(org *Community) *CommunityChanges { 622 changes := org.emptyCommunityChanges() 623 changes.MembersAdded[s.member3Key] = &protobuf.CommunityMember{} 624 changes.ChatsAdded[testChatID2] = &protobuf.CommunityChat{ 625 Identity: &protobuf.ChatIdentity{DisplayName: "added-chat", Description: "description"}, 626 Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_MANUAL_ACCEPT}, 627 Members: make(map[string]*protobuf.CommunityMember)} 628 changes.ChatsAdded[testChatID2].Members[s.member3Key] = &protobuf.CommunityMember{} 629 630 return changes 631 }, 632 err: nil, 633 }, 634 { 635 name: "chat removed from the org", 636 description: s.removedChatCommunityDescription, 637 signer: signer, 638 changes: func(org *Community) *CommunityChanges { 639 changes := org.emptyCommunityChanges() 640 changes.ChatsRemoved[testChatID1] = org.config.CommunityDescription.Chats[testChatID1] 641 642 return changes 643 }, 644 err: nil, 645 }, 646 } 647 648 for _, tc := range testCases { 649 s.Run(tc.name, func() { 650 org := s.buildCommunity(signer) 651 org.Join() 652 expectedChanges := tc.changes(org) 653 actualChanges, err := org.UpdateCommunityDescription(tc.description(org), []byte{0x01}, nil) 654 s.Require().Equal(tc.err, err) 655 s.Require().Equal(expectedChanges, actualChanges) 656 }) 657 } 658 } 659 660 func (s *CommunitySuite) TestValidateCommunityDescription() { 661 662 testCases := []struct { 663 name string 664 description *protobuf.CommunityDescription 665 err error 666 }{ 667 { 668 name: "valid", 669 description: s.buildCommunityDescription(), 670 err: nil, 671 }, 672 { 673 name: "empty description", 674 err: ErrInvalidCommunityDescription, 675 }, 676 { 677 name: "empty org permissions", 678 description: s.emptyPermissionsCommunityDescription(), 679 err: ErrInvalidCommunityDescriptionNoOrgPermissions, 680 }, 681 { 682 name: "empty chat permissions", 683 description: s.emptyChatPermissionsCommunityDescription(), 684 err: ErrInvalidCommunityDescriptionNoChatPermissions, 685 }, 686 { 687 name: "unknown org permissions", 688 description: s.unknownOrgPermissionsCommunityDescription(), 689 err: ErrInvalidCommunityDescriptionUnknownOrgAccess, 690 }, 691 { 692 name: "unknown chat permissions", 693 description: s.unknownChatPermissionsCommunityDescription(), 694 err: ErrInvalidCommunityDescriptionUnknownChatAccess, 695 }, 696 { 697 name: "member in chat but not in org", 698 description: s.memberInChatNotInOrgCommunityDescription(), 699 err: ErrInvalidCommunityDescriptionMemberInChatButNotInOrg, 700 }, 701 } 702 703 for _, tc := range testCases { 704 s.Run(tc.name, func() { 705 err := ValidateCommunityDescription(tc.description) 706 s.Require().Equal(tc.err, err) 707 }) 708 } 709 } 710 711 func (s *CommunitySuite) TestChatIDs() { 712 community := s.buildCommunity(&s.identity.PublicKey) 713 chatIDs := community.ChatIDs() 714 715 s.Require().Len(chatIDs, 1) 716 } 717 718 func (s *CommunitySuite) TestChannelTokenPermissionsByType() { 719 org := s.buildCommunity(&s.identity.PublicKey) 720 721 viewOnlyPermissions := []*protobuf.CommunityTokenPermission{ 722 &protobuf.CommunityTokenPermission{ 723 Id: "some-id", 724 Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, 725 TokenCriteria: make([]*protobuf.TokenCriteria, 0), 726 ChatIds: []string{"some-chat-id"}, 727 }, 728 } 729 730 viewAndPostPermissions := []*protobuf.CommunityTokenPermission{ 731 &protobuf.CommunityTokenPermission{ 732 Id: "some-other-id", 733 Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL, 734 TokenCriteria: make([]*protobuf.TokenCriteria, 0), 735 ChatIds: []string{"some-chat-id-2"}, 736 }, 737 } 738 739 for _, viewOnlyPermission := range viewOnlyPermissions { 740 _, err := org.UpsertTokenPermission(viewOnlyPermission) 741 s.Require().NoError(err) 742 } 743 for _, viewAndPostPermission := range viewAndPostPermissions { 744 _, err := org.UpsertTokenPermission(viewAndPostPermission) 745 s.Require().NoError(err) 746 } 747 748 result := org.ChannelTokenPermissionsByType("some-chat-id", protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) 749 s.Require().Len(result, 1) 750 s.Require().Equal(result[0].Id, viewOnlyPermissions[0].Id) 751 s.Require().Equal(result[0].TokenCriteria, viewOnlyPermissions[0].TokenCriteria) 752 s.Require().Equal(result[0].ChatIds, viewOnlyPermissions[0].ChatIds) 753 754 result = org.ChannelTokenPermissionsByType("some-chat-id-2", protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) 755 s.Require().Len(result, 1) 756 s.Require().Equal(result[0].Id, viewAndPostPermissions[0].Id) 757 s.Require().Equal(result[0].TokenCriteria, viewAndPostPermissions[0].TokenCriteria) 758 s.Require().Equal(result[0].ChatIds, viewAndPostPermissions[0].ChatIds) 759 } 760 761 func (s *CommunitySuite) TestChannelEncrypted() { 762 org := s.buildCommunity(&s.identity.PublicKey) 763 someChannelID := "some-channel-id" 764 someChatID := org.ChatID(someChannelID) 765 766 s.Require().False(org.ChannelEncrypted(someChannelID)) 767 768 _, err := org.UpsertTokenPermission(&protobuf.CommunityTokenPermission{ 769 Id: "A", 770 Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL, 771 TokenCriteria: []*protobuf.TokenCriteria{}, 772 ChatIds: []string{someChatID}, 773 }) 774 s.Require().NoError(err) 775 s.Require().True(org.channelEncrypted(someChannelID)) 776 777 _, err = org.UpsertTokenPermission(&protobuf.CommunityTokenPermission{ 778 Id: "B", 779 Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, 780 TokenCriteria: []*protobuf.TokenCriteria{&protobuf.TokenCriteria{}}, 781 ChatIds: []string{someChatID}, 782 }) 783 s.Require().NoError(err) 784 s.Require().True(org.channelEncrypted(someChannelID)) 785 786 // Channels with `view` permission without token requirements shouldn't be encrypted. 787 // See: https://github.com/status-im/status-desktop/issues/14748 788 _, err = org.UpsertTokenPermission(&protobuf.CommunityTokenPermission{ 789 Id: "C", 790 Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL, 791 TokenCriteria: []*protobuf.TokenCriteria{}, 792 ChatIds: []string{someChatID}, 793 }) 794 s.Require().NoError(err) 795 s.Require().False(org.channelEncrypted(someChannelID)) 796 } 797 798 func (s *CommunitySuite) emptyCommunityDescription() *protobuf.CommunityDescription { 799 return &protobuf.CommunityDescription{ 800 Permissions: &protobuf.CommunityPermissions{}, 801 } 802 803 } 804 805 func (s *CommunitySuite) emptyCommunityDescriptionWithChat() *protobuf.CommunityDescription { 806 desc := &protobuf.CommunityDescription{ 807 Members: make(map[string]*protobuf.CommunityMember), 808 Clock: 1, 809 Chats: make(map[string]*protobuf.CommunityChat), 810 Categories: make(map[string]*protobuf.CommunityCategory), 811 Permissions: &protobuf.CommunityPermissions{}, 812 } 813 814 desc.Categories[testCategoryID1] = &protobuf.CommunityCategory{CategoryId: testCategoryID1, Name: testCategoryName1, Position: 0} 815 desc.Chats[testChatID1] = &protobuf.CommunityChat{Position: 0, Permissions: &protobuf.CommunityPermissions{}, Members: make(map[string]*protobuf.CommunityMember)} 816 desc.Members[common.PubkeyToHex(&s.member1.PublicKey)] = &protobuf.CommunityMember{} 817 desc.Chats[testChatID1].Members[common.PubkeyToHex(&s.member1.PublicKey)] = &protobuf.CommunityMember{} 818 819 return desc 820 821 } 822 823 func (s *CommunitySuite) newConfig(identity *ecdsa.PrivateKey, description *protobuf.CommunityDescription) Config { 824 return Config{ 825 MemberIdentity: identity, 826 ID: &identity.PublicKey, 827 CommunityDescription: description, 828 PrivateKey: identity, 829 ControlNode: &identity.PublicKey, 830 ControlDevice: true, 831 } 832 } 833 834 func (s *CommunitySuite) configOnRequest() Config { 835 description := s.emptyCommunityDescription() 836 description.Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 837 return s.newConfig(s.identity, description) 838 } 839 840 func (s *CommunitySuite) configNoMembershipOrgNoMembershipChat() Config { 841 description := s.emptyCommunityDescriptionWithChat() 842 description.Permissions.Access = protobuf.CommunityPermissions_AUTO_ACCEPT 843 description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_AUTO_ACCEPT 844 return s.newConfig(s.identity, description) 845 } 846 847 func (s *CommunitySuite) configNoMembershipOrgOnRequestChat() Config { 848 description := s.emptyCommunityDescriptionWithChat() 849 description.Permissions.Access = protobuf.CommunityPermissions_AUTO_ACCEPT 850 description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 851 return s.newConfig(s.identity, description) 852 } 853 854 func (s *CommunitySuite) configOnRequestOrgOnRequestChat() Config { 855 description := s.emptyCommunityDescriptionWithChat() 856 description.Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 857 description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 858 return s.newConfig(s.identity, description) 859 } 860 861 func (s *CommunitySuite) configOnRequestOrgNoMembershipChat() Config { 862 description := s.emptyCommunityDescriptionWithChat() 863 description.Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 864 description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_AUTO_ACCEPT 865 return s.newConfig(s.identity, description) 866 } 867 868 func (s *CommunitySuite) configChatENSOnly() Config { 869 description := s.emptyCommunityDescriptionWithChat() 870 description.Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 871 description.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 872 description.Chats[testChatID1].Permissions.EnsOnly = true 873 return s.newConfig(s.identity, description) 874 } 875 876 func (s *CommunitySuite) configENSOnly() Config { 877 description := s.emptyCommunityDescription() 878 description.Permissions.Access = protobuf.CommunityPermissions_MANUAL_ACCEPT 879 description.Permissions.EnsOnly = true 880 return s.newConfig(s.identity, description) 881 } 882 883 func (s *CommunitySuite) config() Config { 884 config := s.configOnRequestOrgOnRequestChat() 885 return config 886 } 887 888 func (s *CommunitySuite) buildCommunityDescription() *protobuf.CommunityDescription { 889 config := s.configOnRequestOrgOnRequestChat() 890 desc := config.CommunityDescription 891 desc.Clock = 1 892 desc.Members = make(map[string]*protobuf.CommunityMember) 893 desc.Members[s.member1Key] = &protobuf.CommunityMember{} 894 desc.Members[s.member2Key] = &protobuf.CommunityMember{} 895 desc.Chats[testChatID1].Members = make(map[string]*protobuf.CommunityMember) 896 desc.Chats[testChatID1].Members[s.member1Key] = &protobuf.CommunityMember{} 897 desc.Chats[testChatID1].Identity = &protobuf.ChatIdentity{ 898 DisplayName: "display-name", 899 Description: "description", 900 } 901 return desc 902 } 903 904 func (s *CommunitySuite) emptyPermissionsCommunityDescription() *protobuf.CommunityDescription { 905 desc := s.buildCommunityDescription() 906 desc.Permissions = nil 907 return desc 908 } 909 910 func (s *CommunitySuite) emptyChatPermissionsCommunityDescription() *protobuf.CommunityDescription { 911 desc := s.buildCommunityDescription() 912 desc.Chats[testChatID1].Permissions = nil 913 return desc 914 } 915 916 func (s *CommunitySuite) unknownOrgPermissionsCommunityDescription() *protobuf.CommunityDescription { 917 desc := s.buildCommunityDescription() 918 desc.Permissions.Access = protobuf.CommunityPermissions_UNKNOWN_ACCESS 919 return desc 920 } 921 922 func (s *CommunitySuite) unknownChatPermissionsCommunityDescription() *protobuf.CommunityDescription { 923 desc := s.buildCommunityDescription() 924 desc.Chats[testChatID1].Permissions.Access = protobuf.CommunityPermissions_UNKNOWN_ACCESS 925 return desc 926 } 927 928 func (s *CommunitySuite) memberInChatNotInOrgCommunityDescription() *protobuf.CommunityDescription { 929 desc := s.buildCommunityDescription() 930 desc.Chats[testChatID1].Members[s.member3Key] = &protobuf.CommunityMember{} 931 return desc 932 } 933 934 func (s *CommunitySuite) buildCommunity(owner *ecdsa.PublicKey) *Community { 935 config := s.config() 936 config.ID = owner 937 config.CommunityDescription = s.buildCommunityDescription() 938 939 org, err := New(config, &TimeSourceStub{}, &DescriptionEncryptorMock{}, nil) 940 s.Require().NoError(err) 941 return org 942 } 943 944 func (s *CommunitySuite) identicalCommunityDescription(org *Community) *protobuf.CommunityDescription { 945 description := proto.Clone(org.config.CommunityDescription).(*protobuf.CommunityDescription) 946 description.Clock++ 947 return description 948 } 949 950 func (s *CommunitySuite) oldCommunityDescription(org *Community) *protobuf.CommunityDescription { 951 description := proto.Clone(org.config.CommunityDescription).(*protobuf.CommunityDescription) 952 description.Clock-- 953 delete(description.Members, s.member1Key) 954 delete(description.Chats[testChatID1].Members, s.member1Key) 955 return description 956 } 957 958 func (s *CommunitySuite) removedMemberCommunityDescription(org *Community) *protobuf.CommunityDescription { 959 description := proto.Clone(org.config.CommunityDescription).(*protobuf.CommunityDescription) 960 description.Clock++ 961 delete(description.Members, s.member1Key) 962 delete(description.Chats[testChatID1].Members, s.member1Key) 963 return description 964 } 965 966 func (s *CommunitySuite) addedMemberCommunityDescription(org *Community) *protobuf.CommunityDescription { 967 description := proto.Clone(org.config.CommunityDescription).(*protobuf.CommunityDescription) 968 description.Clock++ 969 description.Members[s.member3Key] = &protobuf.CommunityMember{} 970 description.Chats[testChatID1].Members[s.member3Key] = &protobuf.CommunityMember{} 971 972 return description 973 } 974 975 func (s *CommunitySuite) addedChatCommunityDescription(org *Community) *protobuf.CommunityDescription { 976 description := proto.Clone(org.config.CommunityDescription).(*protobuf.CommunityDescription) 977 description.Clock++ 978 description.Members[s.member3Key] = &protobuf.CommunityMember{} 979 description.Chats[testChatID2] = &protobuf.CommunityChat{ 980 Identity: &protobuf.ChatIdentity{DisplayName: "added-chat", Description: "description"}, 981 Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_MANUAL_ACCEPT}, 982 Members: make(map[string]*protobuf.CommunityMember)} 983 description.Chats[testChatID2].Members[s.member3Key] = &protobuf.CommunityMember{} 984 985 return description 986 } 987 988 func (s *CommunitySuite) removedChatCommunityDescription(org *Community) *protobuf.CommunityDescription { 989 description := proto.Clone(org.config.CommunityDescription).(*protobuf.CommunityDescription) 990 description.Clock++ 991 delete(description.Chats, testChatID1) 992 993 return description 994 } 995 996 func (s *CommunitySuite) TestMarshalJSON() { 997 community := s.buildCommunity(&s.identity.PublicKey) 998 channelID := community.ChatID(testChatID1) 999 _, err := community.UpsertTokenPermission(&protobuf.CommunityTokenPermission{ 1000 Id: "A", 1001 Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL, 1002 TokenCriteria: []*protobuf.TokenCriteria{}, 1003 ChatIds: []string{channelID}, 1004 }) 1005 s.Require().NoError(err) 1006 1007 s.Require().True(community.ChannelEncrypted(testChatID1)) 1008 1009 communityDescription := community.config.CommunityDescription 1010 ownerKey := s.identity 1011 s.Require().NoError(err) 1012 1013 memberKey, err := crypto.GenerateKey() 1014 s.Require().NoError(err) 1015 1016 // returns true if the user is the owner 1017 1018 communityDescription.Members = make(map[string]*protobuf.CommunityMember) 1019 communityDescription.Members[common.PubkeyToHex(&ownerKey.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_OWNER}} 1020 communityDescription.Members[common.PubkeyToHex(&memberKey.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_ADMIN}} 1021 communityDescription.Chats[testChatID1] = &protobuf.CommunityChat{Members: make(map[string]*protobuf.CommunityMember), Identity: &protobuf.ChatIdentity{}} 1022 communityDescription.Chats[testChatID1].Members[common.PubkeyToHex(&ownerKey.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_OWNER}} 1023 1024 // Test token gated community 1025 s.Require().True(community.ChannelEncrypted(testChatID1)) 1026 communityJSON, err := json.Marshal(community) 1027 s.Require().NoError(err) 1028 1029 var communityData map[string]interface{} 1030 err = json.Unmarshal(communityJSON, &communityData) 1031 s.Require().NoError(err) 1032 s.Require().NotNil(communityData["chats"]) 1033 1034 expectedChats := map[string]interface{}{} 1035 expectedChat := map[string]interface{}{ 1036 "canPost": true, 1037 "canPostReactions": true, 1038 "categoryID": "", 1039 "canView": true, 1040 "color": "", 1041 "description": "", 1042 "emoji": "", 1043 "hideIfPermissionsNotMet": false, 1044 "members": map[string]interface{}{ 1045 common.PubkeyToHex(&ownerKey.PublicKey): map[string]interface{}{ 1046 "roles": []interface{}{float64(1)}, 1047 }, 1048 }, 1049 "id": testChatID1, 1050 "name": "", 1051 "permissions": nil, 1052 "position": float64(0), 1053 "tokenGated": true, 1054 "viewersCanPostReactions": false, 1055 "missingEncryptionKey": false, 1056 } 1057 1058 expectedChats[testChatID1] = expectedChat 1059 s.Require().Equal(expectedChats, communityData["chats"]) 1060 1061 // Test token gated community 1062 community.config.CommunityDescription.TokenPermissions = nil 1063 communityJSON, err = json.Marshal(community) 1064 s.Require().NoError(err) 1065 1066 err = json.Unmarshal(communityJSON, &communityData) 1067 s.Require().NoError(err) 1068 s.Require().NotNil(communityData["chats"]) 1069 1070 expectedChats = map[string]interface{}{} 1071 expectedChat = map[string]interface{}{ 1072 "canPost": true, 1073 "canPostReactions": true, 1074 "categoryID": "", 1075 "canView": true, 1076 "color": "", 1077 "description": "", 1078 "emoji": "", 1079 "hideIfPermissionsNotMet": false, 1080 "id": testChatID1, 1081 "members": nil, 1082 "name": "", 1083 "permissions": nil, 1084 "position": float64(0), 1085 "tokenGated": false, 1086 "viewersCanPostReactions": false, 1087 "missingEncryptionKey": false, 1088 } 1089 1090 expectedChats[testChatID1] = expectedChat 1091 s.Require().Equal(expectedChats, communityData["chats"]) 1092 }