github.com/status-im/status-go@v1.1.0/protocol/messenger_identity_image_test.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"errors"
     7  	"fmt"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/cenkalti/backoff/v3"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/stretchr/testify/suite"
    14  	"go.uber.org/zap"
    15  
    16  	gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
    17  	"github.com/status-im/status-go/eth-node/crypto"
    18  	"github.com/status-im/status-go/eth-node/types"
    19  	"github.com/status-im/status-go/images"
    20  	"github.com/status-im/status-go/multiaccounts"
    21  	"github.com/status-im/status-go/multiaccounts/settings"
    22  	"github.com/status-im/status-go/params"
    23  	"github.com/status-im/status-go/protocol/protobuf"
    24  	"github.com/status-im/status-go/protocol/requests"
    25  	"github.com/status-im/status-go/protocol/tt"
    26  	"github.com/status-im/status-go/waku"
    27  )
    28  
    29  func TestMessengerProfilePictureHandlerSuite(t *testing.T) {
    30  	suite.Run(t, new(MessengerProfilePictureHandlerSuite))
    31  }
    32  
    33  type MessengerProfilePictureHandlerSuite struct {
    34  	suite.Suite
    35  	alice *Messenger // client instance of Messenger
    36  	bob   *Messenger // server instance of Messenger
    37  
    38  	// If one wants to send messages between different instances of Messenger,
    39  	// a single Waku service should be shared.
    40  	shh    types.Waku
    41  	logger *zap.Logger
    42  }
    43  
    44  func (s *MessengerProfilePictureHandlerSuite) SetupSuite() {
    45  	s.logger = tt.MustCreateTestLogger()
    46  
    47  	// Setup Waku things
    48  	config := waku.DefaultConfig
    49  	config.MinimumAcceptedPoW = 0
    50  	wakuLogger := s.logger.Named("Waku")
    51  	shh := waku.New(&config, wakuLogger)
    52  	s.shh = gethbridge.NewGethWakuWrapper(shh)
    53  	s.Require().NoError(shh.Start())
    54  }
    55  
    56  func (s *MessengerProfilePictureHandlerSuite) TearDownSuite() {
    57  	_ = gethbridge.GetGethWakuFrom(s.shh).Stop()
    58  	_ = s.logger.Sync()
    59  }
    60  
    61  func (s *MessengerProfilePictureHandlerSuite) newMessenger(name string) *Messenger {
    62  	m, err := newTestMessenger(s.shh, testMessengerConfig{
    63  		logger: s.logger.Named(fmt.Sprintf("messenger-%s", name)),
    64  		name:   name,
    65  		extraOptions: []Option{
    66  			WithAppSettings(newTestSettings(), params.NodeConfig{}),
    67  		},
    68  	})
    69  	s.Require().NoError(err)
    70  
    71  	_, err = m.Start()
    72  	s.Require().NoError(err)
    73  
    74  	return m
    75  }
    76  
    77  func (s *MessengerProfilePictureHandlerSuite) SetupTest() {
    78  	// Generate Alice Messenger
    79  	s.alice = s.newMessenger("Alice")
    80  	s.bob = s.newMessenger("Bobby")
    81  
    82  	// Setup MultiAccount for Alice Messenger
    83  	s.setupMultiAccount(s.alice)
    84  }
    85  
    86  func (s *MessengerProfilePictureHandlerSuite) TearDownTest() {
    87  	// Shutdown messengers
    88  	TearDownMessenger(&s.Suite, s.alice)
    89  	s.alice = nil
    90  	TearDownMessenger(&s.Suite, s.bob)
    91  	s.bob = nil
    92  	_ = s.logger.Sync()
    93  }
    94  
    95  func (s *MessengerProfilePictureHandlerSuite) setupMultiAccount(m *Messenger) {
    96  	name, err := m.settings.DisplayName()
    97  	s.Require().NoError(err)
    98  
    99  	keyUID := m.IdentityPublicKeyString()
   100  	m.account = &multiaccounts.Account{
   101  		Name:   name,
   102  		KeyUID: keyUID,
   103  	}
   104  
   105  	err = m.multiAccounts.SaveAccount(*m.account)
   106  	s.NoError(err)
   107  }
   108  
   109  func (s *MessengerProfilePictureHandlerSuite) generateAndStoreIdentityImages(m *Messenger) map[string]images.IdentityImage {
   110  	keyUID := m.IdentityPublicKeyString()
   111  	iis := images.SampleIdentityImages()
   112  
   113  	err := m.multiAccounts.StoreIdentityImages(keyUID, iis, false)
   114  	s.Require().NoError(err)
   115  
   116  	out := make(map[string]images.IdentityImage)
   117  
   118  	for _, ii := range iis {
   119  		out[ii.Name] = ii
   120  	}
   121  
   122  	s.Require().Contains(out, images.SmallDimName)
   123  	s.Require().Contains(out, images.LargeDimName)
   124  
   125  	return out
   126  }
   127  
   128  func (s *MessengerProfilePictureHandlerSuite) TestChatIdentity() {
   129  	iis := s.generateAndStoreIdentityImages(s.alice)
   130  	ci, err := s.alice.createChatIdentity(privateChat)
   131  	s.Require().NoError(err)
   132  	s.Require().Exactly(len(iis), len(ci.Images))
   133  }
   134  
   135  func (s *MessengerProfilePictureHandlerSuite) TestEncryptDecryptIdentityImagesWithContactPubKeys() {
   136  	smPayload := "hello small image"
   137  	lgPayload := "hello large image"
   138  
   139  	ci := protobuf.ChatIdentity{
   140  		Clock: uint64(time.Now().Unix()),
   141  		Images: map[string]*protobuf.IdentityImage{
   142  			"small": {
   143  				Payload: []byte(smPayload),
   144  			},
   145  			"large": {
   146  				Payload: []byte(lgPayload),
   147  			},
   148  		},
   149  	}
   150  
   151  	// Make contact keys and Contacts, set the Contacts to added
   152  	contactKeys := make([]*ecdsa.PrivateKey, 10)
   153  	for i := range contactKeys {
   154  		contactKey, err := crypto.GenerateKey()
   155  		s.Require().NoError(err)
   156  		contactKeys[i] = contactKey
   157  
   158  		contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
   159  		s.Require().NoError(err)
   160  
   161  		contact.ContactRequestLocalState = ContactRequestStateSent
   162  
   163  		s.alice.allContacts.Store(contact.ID, contact)
   164  	}
   165  
   166  	// Test EncryptIdentityImagesWithContactPubKeys
   167  	err := EncryptIdentityImagesWithContactPubKeys(ci.Images, s.alice)
   168  	s.Require().NoError(err)
   169  
   170  	for _, ii := range ci.Images {
   171  		s.Require().Equal(s.alice.allContacts.Len(), len(ii.EncryptionKeys))
   172  	}
   173  	s.Require().NotEqual([]byte(smPayload), ci.Images["small"].Payload)
   174  	s.Require().NotEqual([]byte(lgPayload), ci.Images["large"].Payload)
   175  	s.Require().True(ci.Images["small"].Encrypted)
   176  	s.Require().True(ci.Images["large"].Encrypted)
   177  
   178  	// Test DecryptIdentityImagesWithIdentityPrivateKey
   179  	err = DecryptIdentityImagesWithIdentityPrivateKey(ci.Images, contactKeys[2], &s.alice.identity.PublicKey)
   180  	s.Require().NoError(err)
   181  
   182  	s.Require().Equal(smPayload, string(ci.Images["small"].Payload))
   183  	s.Require().Equal(lgPayload, string(ci.Images["large"].Payload))
   184  	s.Require().False(ci.Images["small"].Encrypted)
   185  	s.Require().False(ci.Images["large"].Encrypted)
   186  
   187  	// RESET Messenger identity, Contacts and IdentityImage.EncryptionKeys
   188  	s.alice.allContacts = new(contactMap)
   189  	ci.Images["small"].EncryptionKeys = nil
   190  	ci.Images["large"].EncryptionKeys = nil
   191  
   192  	// Test EncryptIdentityImagesWithContactPubKeys with no contacts
   193  	err = EncryptIdentityImagesWithContactPubKeys(ci.Images, s.alice)
   194  	s.Require().NoError(err)
   195  
   196  	for _, ii := range ci.Images {
   197  		s.Require().Equal(0, len(ii.EncryptionKeys))
   198  	}
   199  	s.Require().NotEqual([]byte(smPayload), ci.Images["small"].Payload)
   200  	s.Require().NotEqual([]byte(lgPayload), ci.Images["large"].Payload)
   201  	s.Require().True(ci.Images["small"].Encrypted)
   202  	s.Require().True(ci.Images["large"].Encrypted)
   203  
   204  	// Test DecryptIdentityImagesWithIdentityPrivateKey with no valid identity
   205  	err = DecryptIdentityImagesWithIdentityPrivateKey(ci.Images, contactKeys[2], &s.alice.identity.PublicKey)
   206  	s.Require().NoError(err)
   207  
   208  	s.Require().NotEqual([]byte(smPayload), ci.Images["small"].Payload)
   209  	s.Require().NotEqual([]byte(lgPayload), ci.Images["large"].Payload)
   210  	s.Require().True(ci.Images["small"].Encrypted)
   211  	s.Require().True(ci.Images["large"].Encrypted)
   212  }
   213  
   214  func (s *MessengerProfilePictureHandlerSuite) TestPictureInPrivateChatOneSided() {
   215  	err := s.bob.settings.SaveSettingField(settings.ProfilePicturesVisibility, settings.ProfilePicturesShowToEveryone)
   216  	s.Require().NoError(err)
   217  
   218  	err = s.alice.settings.SaveSettingField(settings.ProfilePicturesVisibility, settings.ProfilePicturesShowToEveryone)
   219  	s.Require().NoError(err)
   220  
   221  	bChat := CreateOneToOneChat(s.alice.IdentityPublicKeyString(), s.alice.IdentityPublicKey(), s.alice.transport)
   222  	err = s.bob.SaveChat(bChat)
   223  	s.Require().NoError(err)
   224  
   225  	_, err = s.bob.Join(bChat)
   226  	s.Require().NoError(err)
   227  
   228  	// Alice sends a message to the public chat
   229  	message := buildTestMessage(*bChat)
   230  	response, err := s.bob.SendChatMessage(context.Background(), message)
   231  	s.Require().NoError(err)
   232  	s.Require().NotNil(response)
   233  
   234  	options := func(b *backoff.ExponentialBackOff) {
   235  		b.MaxElapsedTime = 2 * time.Second
   236  	}
   237  
   238  	err = tt.RetryWithBackOff(func() error {
   239  
   240  		response, err = s.alice.RetrieveAll()
   241  		if err != nil {
   242  			return err
   243  		}
   244  		s.Require().NotNil(response)
   245  
   246  		contacts := response.Contacts
   247  		s.logger.Debug("RetryWithBackOff contact data", zap.Any("contacts", contacts))
   248  
   249  		if len(contacts) > 0 && len(contacts[0].Images) > 0 {
   250  			s.logger.Debug("", zap.Any("contacts", contacts))
   251  			return nil
   252  		}
   253  
   254  		return errors.New("no new contacts with images received")
   255  	}, options)
   256  }
   257  
   258  func (s *MessengerProfilePictureHandlerSuite) TestE2eSendingReceivingProfilePicture() {
   259  	profilePicShowSettings := []settings.ProfilePicturesShowToType{
   260  		settings.ProfilePicturesShowToContactsOnly,
   261  		settings.ProfilePicturesShowToEveryone,
   262  		settings.ProfilePicturesShowToNone,
   263  	}
   264  
   265  	profilePicViewSettings := []settings.ProfilePicturesVisibilityType{
   266  		settings.ProfilePicturesVisibilityContactsOnly,
   267  		settings.ProfilePicturesVisibilityEveryone,
   268  		settings.ProfilePicturesVisibilityNone,
   269  	}
   270  
   271  	isContactFor := map[string][]bool{
   272  		"alice": {true, false},
   273  		"bob":   {true, false},
   274  	}
   275  
   276  	chatContexts := []ChatContext{
   277  		publicChat,
   278  		privateChat,
   279  	}
   280  
   281  	// TODO see if possible to push each test scenario into a go routine
   282  	for _, cc := range chatContexts {
   283  		for _, ss := range profilePicShowSettings {
   284  			for _, vs := range profilePicViewSettings {
   285  				for _, ac := range isContactFor["alice"] {
   286  					for _, bc := range isContactFor["bob"] {
   287  						args := &e2eArgs{
   288  							chatContext:    cc,
   289  							showToType:     ss,
   290  							visibilityType: vs,
   291  							aliceContact:   ac,
   292  							bobContact:     bc,
   293  						}
   294  						s.Run(args.TestCaseName(s.T()), func() {
   295  							s.testE2eSendingReceivingProfilePicture(args)
   296  						})
   297  					}
   298  				}
   299  			}
   300  		}
   301  	}
   302  
   303  	s.SetupTest()
   304  }
   305  
   306  func (s *MessengerProfilePictureHandlerSuite) testE2eSendingReceivingProfilePicture(args *e2eArgs) {
   307  	// Generate Alice Messenger
   308  	alice := s.newMessenger("Alice")
   309  	bob := s.newMessenger("Bobby")
   310  
   311  	// Setup MultiAccount for Alice Messenger
   312  	s.setupMultiAccount(alice)
   313  
   314  	defer func() {
   315  		TearDownMessenger(&s.Suite, alice)
   316  		alice = nil
   317  		TearDownMessenger(&s.Suite, bob)
   318  		bob = nil
   319  		_ = s.logger.Sync()
   320  	}()
   321  
   322  	s.logger.Info("testing with criteria:", zap.Any("args", args))
   323  	defer s.logger.Info("Completed testing with criteria:", zap.Any("args", args))
   324  
   325  	expectPicture, err := args.resultExpected()
   326  	s.Require().NoError(err)
   327  
   328  	s.logger.Debug("expect to receive a profile pic?",
   329  		zap.Bool("result", expectPicture),
   330  		zap.Error(err))
   331  
   332  	// Setting up Bob
   333  	err = bob.settings.SaveSettingField(settings.ProfilePicturesVisibility, args.visibilityType)
   334  	s.Require().NoError(err)
   335  
   336  	if args.bobContact {
   337  		_, err = bob.AddContact(context.Background(), &requests.AddContact{ID: alice.IdentityPublicKeyString()})
   338  		s.Require().NoError(err)
   339  	}
   340  
   341  	// Create Bob's chats
   342  	switch args.chatContext {
   343  	case publicChat:
   344  		// Bob opens up the public chat and joins it
   345  		bChat := CreatePublicChat("status", alice.transport)
   346  		err = bob.SaveChat(bChat)
   347  		s.Require().NoError(err)
   348  
   349  		_, err = bob.Join(bChat)
   350  		s.Require().NoError(err)
   351  	case privateChat:
   352  		bChat := CreateOneToOneChat(alice.IdentityPublicKeyString(), alice.IdentityPublicKey(), alice.transport)
   353  		err = bob.SaveChat(bChat)
   354  		s.Require().NoError(err)
   355  
   356  		_, err = bob.Join(bChat)
   357  		s.Require().NoError(err)
   358  	default:
   359  		s.Failf("unexpected chat context type", "%s", string(args.chatContext))
   360  	}
   361  
   362  	// Setting up Alice
   363  	err = alice.settings.SaveSettingField(settings.ProfilePicturesShowTo, args.showToType)
   364  	s.Require().NoError(err)
   365  
   366  	if args.aliceContact {
   367  		_, err = alice.AddContact(context.Background(), &requests.AddContact{ID: bob.IdentityPublicKeyString()})
   368  		s.Require().NoError(err)
   369  	}
   370  
   371  	iis := s.generateAndStoreIdentityImages(alice)
   372  
   373  	// Create chats
   374  	var aChat *Chat
   375  	switch args.chatContext {
   376  	case publicChat:
   377  		// Alice opens creates a public chat
   378  		aChat = CreatePublicChat("status", alice.transport)
   379  		err = alice.SaveChat(aChat)
   380  		s.Require().NoError(err)
   381  
   382  		// Alice sends a message to the public chat
   383  		message := buildTestMessage(*aChat)
   384  		response, err := alice.SendChatMessage(context.Background(), message)
   385  		s.Require().NoError(err)
   386  		s.Require().NotNil(response)
   387  		s.Require().Len(response.messages, 1)
   388  
   389  	case privateChat:
   390  		aChat = CreateOneToOneChat(bob.IdentityPublicKeyString(), bob.IdentityPublicKey(), bob.transport)
   391  		err = alice.SaveChat(aChat)
   392  		s.Require().NoError(err)
   393  
   394  		_, err = alice.Join(aChat)
   395  		s.Require().NoError(err)
   396  
   397  		err = alice.publishContactCode()
   398  		s.Require().NoError(err)
   399  
   400  	default:
   401  		s.Failf("unexpected chat context type", "%s", string(args.chatContext))
   402  	}
   403  
   404  	// Poll bob to see if he got the chatIdentity
   405  	// Retrieve ChatIdentity
   406  	var contacts []*Contact
   407  
   408  	options := func(b *backoff.ExponentialBackOff) {
   409  		b.MaxElapsedTime = 2 * time.Second
   410  	}
   411  
   412  	err = tt.RetryWithBackOff(func() error {
   413  		response, err := bob.RetrieveAll()
   414  		if err != nil {
   415  			return err
   416  		}
   417  
   418  		contacts = response.Contacts
   419  		if len(contacts) > 0 && len(contacts[0].Images) > 0 {
   420  			return nil
   421  		}
   422  
   423  		return errors.New("no new contacts with images received")
   424  	}, options)
   425  
   426  	if !expectPicture {
   427  		s.Require().EqualError(err, "no new contacts with images received")
   428  		return
   429  	}
   430  
   431  	s.Require().NoError(err)
   432  	s.Require().NotNil(contacts)
   433  
   434  	// Check if alice's contact data with profile picture is there
   435  	var contact *Contact
   436  	for _, c := range contacts {
   437  		if c.ID == alice.IdentityPublicKeyString() {
   438  			contact = c
   439  		}
   440  	}
   441  	s.Require().NotNil(contact)
   442  
   443  	// Check that Bob now has Alice's profile picture(s)
   444  	switch args.chatContext {
   445  	case publicChat:
   446  		// In public chat context we only need the images.SmallDimName, but also may have the large
   447  		s.Require().GreaterOrEqual(len(contact.Images), 1)
   448  		s.Require().Contains(contact.Images, images.SmallDimName)
   449  		s.Require().Equal(iis[images.SmallDimName].Payload, contact.Images[images.SmallDimName].Payload)
   450  
   451  	case privateChat:
   452  		s.Require().Equal(len(contact.Images), 2)
   453  		s.Require().Contains(contact.Images, images.SmallDimName)
   454  		s.Require().Contains(contact.Images, images.LargeDimName)
   455  		s.Require().Equal(iis[images.SmallDimName].Payload, contact.Images[images.SmallDimName].Payload)
   456  		s.Require().Equal(iis[images.LargeDimName].Payload, contact.Images[images.LargeDimName].Payload)
   457  	}
   458  }
   459  
   460  type e2eArgs struct {
   461  	chatContext    ChatContext
   462  	showToType     settings.ProfilePicturesShowToType
   463  	visibilityType settings.ProfilePicturesVisibilityType
   464  	aliceContact   bool
   465  	bobContact     bool
   466  }
   467  
   468  func (args *e2eArgs) String() string {
   469  	return fmt.Sprintf("ChatContext: %s, ShowTo: %s, Visibility: %s, AliceContact: %t, BobContact: %t",
   470  		string(args.chatContext),
   471  		profilePicShowSettingsMap[args.showToType],
   472  		profilePicViewSettingsMap[args.visibilityType],
   473  		args.aliceContact,
   474  		args.bobContact,
   475  	)
   476  }
   477  
   478  func (args *e2eArgs) TestCaseName(t *testing.T) string {
   479  	expected, err := args.resultExpected()
   480  	require.NoError(t, err)
   481  
   482  	return fmt.Sprintf("%s-%s-%s-ac.%t-bc.%t-exp.%t",
   483  		string(args.chatContext),
   484  		profilePicShowSettingsMap[args.showToType],
   485  		profilePicViewSettingsMap[args.visibilityType],
   486  		args.aliceContact,
   487  		args.bobContact,
   488  		expected,
   489  	)
   490  }
   491  
   492  func (args *e2eArgs) resultExpected() (bool, error) {
   493  	switch args.showToType {
   494  	case settings.ProfilePicturesShowToContactsOnly:
   495  		if args.aliceContact {
   496  			return args.resultExpectedVS()
   497  		}
   498  		return false, nil
   499  	case settings.ProfilePicturesShowToEveryone:
   500  		return args.resultExpectedVS()
   501  	case settings.ProfilePicturesShowToNone:
   502  		return false, nil
   503  	default:
   504  		return false, errors.New("unknown ProfilePicturesShowToType")
   505  	}
   506  }
   507  
   508  func (args *e2eArgs) resultExpectedVS() (bool, error) {
   509  	switch args.visibilityType {
   510  	case settings.ProfilePicturesVisibilityContactsOnly:
   511  		return true, nil
   512  	case settings.ProfilePicturesVisibilityEveryone:
   513  		return true, nil
   514  	case settings.ProfilePicturesVisibilityNone:
   515  		// If we are contacts, we save the image regardless
   516  		return args.bobContact, nil
   517  	default:
   518  		return false, errors.New("unknown ProfilePicturesVisibilityType")
   519  	}
   520  }
   521  
   522  var profilePicShowSettingsMap = map[settings.ProfilePicturesShowToType]string{
   523  	settings.ProfilePicturesShowToContactsOnly: "ShowToContactsOnly",
   524  	settings.ProfilePicturesShowToEveryone:     "ShowToEveryone",
   525  	settings.ProfilePicturesShowToNone:         "ShowToNone",
   526  }
   527  
   528  var profilePicViewSettingsMap = map[settings.ProfilePicturesVisibilityType]string{
   529  	settings.ProfilePicturesVisibilityContactsOnly: "ViewFromContactsOnly",
   530  	settings.ProfilePicturesVisibilityEveryone:     "ViewFromEveryone",
   531  	settings.ProfilePicturesVisibilityNone:         "ViewFromNone",
   532  }