github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/profile/store.go (about)

     1  package profile
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/gofrs/flock"
    14  	"github.com/libp2p/go-libp2p-core/peer"
    15  	"github.com/qri-io/qri/auth/key"
    16  	"github.com/qri-io/qri/config"
    17  	qerr "github.com/qri-io/qri/errors"
    18  )
    19  
    20  var (
    21  	// ErrNotFound is the not found err for the profile package
    22  	ErrNotFound = fmt.Errorf("profile: not found")
    23  	// ErrAmbiguousUsername occurs when more than one username is the same in a
    24  	// context that requires exactly one user. More information is needed to
    25  	// disambiguate which username is correct
    26  	ErrAmbiguousUsername = fmt.Errorf("ambiguous username")
    27  )
    28  
    29  // Store is a store of profile information. Stores are owned by a single profile
    30  // that must have an associated private key
    31  type Store interface {
    32  	// Owner is a single profile that represents the current user
    33  	Owner(ctx context.Context) *Profile
    34  	// SetOwner handles updates to the current user profile at runtime
    35  	SetOwner(ctx context.Context, own *Profile) error
    36  	// Active is the active profile that represents the current user
    37  	Active(ctx context.Context) *Profile
    38  
    39  	// put a profile in the store
    40  	PutProfile(ctx context.Context, profile *Profile) error
    41  	// get a profile by ID
    42  	GetProfile(ctx context.Context, id ID) (*Profile, error)
    43  	// remove a profile from the store
    44  	DeleteProfile(ctx context.Context, id ID) error
    45  
    46  	// get all profiles who's .Peername field matches a given username. It's
    47  	// possible to have multiple profiles with the same username
    48  	ProfilesForUsername(ctx context.Context, username string) ([]*Profile, error)
    49  	// list all profiles in the store, keyed by ID
    50  	// Deprecated - don't add new callers to this. We should replace this with
    51  	// a better batch accessor
    52  	List(ctx context.Context) (map[ID]*Profile, error)
    53  	// get a set of peer ids for a given profile ID
    54  	PeerIDs(ctx context.Context, id ID) ([]peer.ID, error)
    55  	// get a profile for a given peer Identifier
    56  	PeerProfile(ctx context.Context, id peer.ID) (*Profile, error)
    57  	// get the profile ID for a given peername
    58  	// Depcreated - use GetProfile instead
    59  	PeernameID(ctx context.Context, peername string) (ID, error)
    60  }
    61  
    62  // NewStore creates a profile store from configuration
    63  func NewStore(ctx context.Context, cfg *config.Config, keyStore key.Store) (Store, error) {
    64  	pro, err := NewProfile(cfg.Profile)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	// Don't create a localstore with the empty path, this will use the current directory
    70  	if cfg.Repo.Type == "fs" && cfg.Path() == "" {
    71  		return nil, fmt.Errorf("new Profile.FilesystemStore requires non-empty path")
    72  	}
    73  
    74  	if cfg.Repo == nil {
    75  		return NewMemStore(ctx, pro, keyStore)
    76  	}
    77  
    78  	switch cfg.Repo.Type {
    79  	case "fs":
    80  		return NewLocalStore(ctx, filepath.Join(filepath.Dir(cfg.Path()), "peers.json"), pro, keyStore)
    81  	case "mem":
    82  		return NewMemStore(ctx, pro, keyStore)
    83  	default:
    84  		return nil, fmt.Errorf("unknown repo type: %s", cfg.Repo.Type)
    85  	}
    86  }
    87  
    88  // ResolveUsername finds a single profile for a given username from a store of
    89  // usernames. Errors if the store contains more than one user with the given
    90  // username
    91  func ResolveUsername(ctx context.Context, s Store, username string) (*Profile, error) {
    92  	pros, err := s.ProfilesForUsername(ctx, username)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	if len(pros) > 1 {
    98  		return nil, newAmbiguousUsernamesError(pros)
    99  	} else if len(pros) == 0 {
   100  		return nil, ErrNotFound
   101  	}
   102  
   103  	return pros[0], nil
   104  }
   105  
   106  // NewAmbiguousUsernamesError creates a qri error that describes how to choose
   107  // the right user
   108  // TODO(b5): this message doesn't describe a fix... because we don't have a good
   109  // one yet. We need to modify dsref parsing to deal with username disambiguation
   110  func newAmbiguousUsernamesError(pros []*Profile) error {
   111  	msg := ""
   112  	if len(pros) > 0 {
   113  		descriptions := make([]string, len(pros), len(pros))
   114  		for i, p := range pros {
   115  			descriptions[i] = fmt.Sprintf("%s\t%s", p.ID, p.Email)
   116  		}
   117  		msg = fmt.Sprintf("multiple profiles exist for the username %q.\nprofileID\temail\n%s", pros[0].Peername, strings.Join(descriptions, "\n"))
   118  	}
   119  	return qerr.New(ErrAmbiguousUsername, msg)
   120  }
   121  
   122  // MemStore is an in-memory implementation of the profile Store interface
   123  type MemStore struct {
   124  	sync.Mutex
   125  	owner    *Profile
   126  	store    map[ID]*Profile
   127  	keyStore key.Store
   128  }
   129  
   130  // NewMemStore allocates a MemStore
   131  func NewMemStore(ctx context.Context, owner *Profile, ks key.Store) (Store, error) {
   132  	if err := owner.ValidOwnerProfile(); err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	if err := ks.AddPrivKey(ctx, owner.GetKeyID(), owner.PrivKey); err != nil {
   137  		return nil, err
   138  	}
   139  	if err := ks.AddPubKey(ctx, owner.GetKeyID(), owner.PrivKey.GetPublic()); err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	return &MemStore{
   144  		owner: owner,
   145  		store: map[ID]*Profile{
   146  			owner.ID: owner,
   147  		},
   148  		keyStore: ks,
   149  	}, nil
   150  }
   151  
   152  // Owner accesses the current owner user profile
   153  func (m *MemStore) Owner(ctx context.Context) *Profile {
   154  	// TODO(b5): this should return a copy
   155  	return m.owner
   156  }
   157  
   158  // SetOwner updates the owner profile
   159  func (m *MemStore) SetOwner(ctx context.Context, own *Profile) error {
   160  	m.owner = own
   161  	return nil
   162  }
   163  
   164  // Active is the curernt active profile
   165  func (m *MemStore) Active(ctx context.Context) *Profile {
   166  	return m.Owner(ctx)
   167  }
   168  
   169  // PutProfile adds a peer to this store
   170  func (m *MemStore) PutProfile(ctx context.Context, p *Profile) error {
   171  	if p.ID.Empty() {
   172  		return fmt.Errorf("profile.ID is required")
   173  	}
   174  
   175  	m.Lock()
   176  	m.store[p.ID] = p
   177  	m.Unlock()
   178  
   179  	if p.PubKey != nil {
   180  		if err := m.keyStore.AddPubKey(ctx, p.GetKeyID(), p.PubKey); err != nil {
   181  			return err
   182  		}
   183  	}
   184  	if p.PrivKey != nil {
   185  		if err := m.keyStore.AddPrivKey(ctx, p.GetKeyID(), p.PrivKey); err != nil {
   186  			return err
   187  		}
   188  	}
   189  	return nil
   190  }
   191  
   192  // PeernameID gives the ID for a given peername
   193  func (m *MemStore) PeernameID(ctx context.Context, peername string) (ID, error) {
   194  	m.Lock()
   195  	defer m.Unlock()
   196  
   197  	for id, profile := range m.store {
   198  		if profile.Peername == peername {
   199  			return id, nil
   200  		}
   201  	}
   202  	return "", ErrNotFound
   203  }
   204  
   205  // PeerProfile returns profile data for a given peer.ID
   206  // TODO - this func implies that peer.ID's are only ever connected to the same
   207  // profile. That could cause trouble.
   208  func (m *MemStore) PeerProfile(ctx context.Context, id peer.ID) (*Profile, error) {
   209  	m.Lock()
   210  	defer m.Unlock()
   211  
   212  	for _, profile := range m.store {
   213  		for _, pid := range profile.PeerIDs {
   214  			if id == pid {
   215  				return profile, nil
   216  			}
   217  		}
   218  	}
   219  
   220  	return nil, ErrNotFound
   221  }
   222  
   223  // PeerIDs gives the peer.IDs list for a given peername
   224  func (m *MemStore) PeerIDs(ctx context.Context, id ID) ([]peer.ID, error) {
   225  	m.Lock()
   226  	defer m.Unlock()
   227  
   228  	for proid, profile := range m.store {
   229  		if id == proid {
   230  			return profile.PeerIDs, nil
   231  		}
   232  	}
   233  
   234  	return nil, ErrNotFound
   235  }
   236  
   237  // List hands the full list of peers back
   238  func (m *MemStore) List(ctx context.Context) (map[ID]*Profile, error) {
   239  	m.Lock()
   240  	defer m.Unlock()
   241  
   242  	res := map[ID]*Profile{}
   243  	for id, p := range m.store {
   244  		res[id] = p
   245  	}
   246  	return res, nil
   247  }
   248  
   249  // GetProfile give's peer info from the store for a given peer.ID
   250  func (m *MemStore) GetProfile(ctx context.Context, id ID) (*Profile, error) {
   251  	m.Lock()
   252  	defer m.Unlock()
   253  
   254  	if m.store[id] == nil {
   255  		return nil, ErrNotFound
   256  	}
   257  
   258  	pro := m.store[id]
   259  	pro.KeyID = pro.GetKeyID()
   260  	pro.PubKey = m.keyStore.PubKey(ctx, pro.GetKeyID())
   261  	pro.PrivKey = m.keyStore.PrivKey(ctx, pro.GetKeyID())
   262  
   263  	return pro, nil
   264  }
   265  
   266  // ProfilesForUsername fetches all profile that match a username (Peername)
   267  func (m *MemStore) ProfilesForUsername(ctx context.Context, username string) ([]*Profile, error) {
   268  	m.Lock()
   269  	defer m.Unlock()
   270  
   271  	var res []*Profile
   272  	for _, pro := range m.store {
   273  		if pro.Peername == username {
   274  			res = append(res, pro)
   275  		}
   276  	}
   277  
   278  	return res, nil
   279  }
   280  
   281  // DeleteProfile removes a peer from this store
   282  func (m *MemStore) DeleteProfile(ctx context.Context, id ID) error {
   283  	m.Lock()
   284  	delete(m.store, id)
   285  	m.Unlock()
   286  
   287  	return nil
   288  }
   289  
   290  // LocalStore is an on-disk json file implementation of the
   291  // repo.Peers interface
   292  type LocalStore struct {
   293  	sync.Mutex
   294  	owner    *Profile
   295  	keyStore key.Store
   296  	filename string
   297  	flock    *flock.Flock
   298  }
   299  
   300  // NewLocalStore allocates a LocalStore
   301  func NewLocalStore(ctx context.Context, filename string, owner *Profile, ks key.Store) (Store, error) {
   302  	if err := owner.ValidOwnerProfile(); err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	if err := ks.AddPrivKey(ctx, owner.GetKeyID(), owner.PrivKey); err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	s := &LocalStore{
   311  		owner:    owner,
   312  		keyStore: ks,
   313  		filename: filename,
   314  		flock:    flock.New(lockPath(filename)),
   315  	}
   316  
   317  	err := s.PutProfile(ctx, owner)
   318  	return s, err
   319  }
   320  
   321  func lockPath(filename string) string {
   322  	return fmt.Sprintf("%s.lock", filename)
   323  }
   324  
   325  // Owner accesses the current owner user profile
   326  func (r *LocalStore) Owner(ctx context.Context) *Profile {
   327  	// TODO(b5): this should return a copy
   328  	return r.owner
   329  }
   330  
   331  // SetOwner updates the owner profile
   332  func (r *LocalStore) SetOwner(ctx context.Context, own *Profile) error {
   333  	r.owner = own
   334  	return r.PutProfile(ctx, own)
   335  }
   336  
   337  // Active is the curernt active profile
   338  func (r *LocalStore) Active(ctx context.Context) *Profile {
   339  	return r.Owner(ctx)
   340  }
   341  
   342  // PutProfile adds a peer to the store
   343  func (r *LocalStore) PutProfile(ctx context.Context, p *Profile) error {
   344  	log.Debugf("put profile: %s", p.ID.Encode())
   345  	if p.ID.Empty() {
   346  		return fmt.Errorf("profile ID is required")
   347  	}
   348  
   349  	enc, err := p.Encode()
   350  	if err != nil {
   351  		return fmt.Errorf("error encoding profile: %s", err.Error())
   352  	}
   353  
   354  	// explicitly remove Online flag
   355  	enc.Online = false
   356  
   357  	if p.PubKey != nil {
   358  		if err := r.keyStore.AddPubKey(ctx, p.GetKeyID(), p.PubKey); err != nil {
   359  			return err
   360  		}
   361  	}
   362  	if p.PrivKey != nil {
   363  		if err := r.keyStore.AddPrivKey(ctx, p.GetKeyID(), p.PrivKey); err != nil {
   364  			return err
   365  		}
   366  	}
   367  
   368  	r.Lock()
   369  	defer r.Unlock()
   370  
   371  	ps, err := r.profiles()
   372  	if err != nil {
   373  		return err
   374  	}
   375  	ps[p.ID.Encode()] = enc
   376  	return r.saveFile(ps)
   377  }
   378  
   379  // PeerIDs gives the peer.IDs list for a given peername
   380  func (r *LocalStore) PeerIDs(ctx context.Context, id ID) ([]peer.ID, error) {
   381  	r.Lock()
   382  	defer r.Unlock()
   383  
   384  	ps, err := r.profiles()
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	ids := id.Encode()
   390  
   391  	for proid, cp := range ps {
   392  		if ids == proid {
   393  			pro := &Profile{}
   394  			if err := pro.Decode(cp); err != nil {
   395  				return nil, err
   396  			}
   397  			return pro.PeerIDs, err
   398  		}
   399  	}
   400  
   401  	return nil, ErrNotFound
   402  }
   403  
   404  // List hands back the list of peers
   405  func (r *LocalStore) List(ctx context.Context) (map[ID]*Profile, error) {
   406  	r.Lock()
   407  	defer r.Unlock()
   408  
   409  	ps, err := r.profiles()
   410  	if err != nil && err.Error() == "EOF" {
   411  		return map[ID]*Profile{}, nil
   412  	} else if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	profiles := map[ID]*Profile{}
   417  	for _, cp := range ps {
   418  		pro := &Profile{}
   419  		if err := pro.Decode(cp); err != nil {
   420  			return nil, err
   421  		}
   422  		profiles[pro.ID] = pro
   423  	}
   424  
   425  	return profiles, nil
   426  }
   427  
   428  // PeernameID gives the ID for a given peername
   429  func (r *LocalStore) PeernameID(ctx context.Context, peername string) (ID, error) {
   430  	r.Lock()
   431  	defer r.Unlock()
   432  
   433  	ps, err := r.profiles()
   434  	if err != nil {
   435  		return "", err
   436  	}
   437  
   438  	for id, cp := range ps {
   439  		if cp.Peername == peername {
   440  			return IDB58Decode(id)
   441  		}
   442  	}
   443  	return "", ErrNotFound
   444  }
   445  
   446  // GetProfile fetches a profile from the store
   447  func (r *LocalStore) GetProfile(ctx context.Context, id ID) (*Profile, error) {
   448  	log.Debugf("get profile: %s", id.Encode())
   449  
   450  	r.Lock()
   451  	defer r.Unlock()
   452  
   453  	ps, err := r.profiles()
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  
   458  	ids := id.Encode()
   459  
   460  	for proid, p := range ps {
   461  		if ids == proid {
   462  			pro := &Profile{}
   463  			err := pro.Decode(p)
   464  			pro.KeyID = pro.GetKeyID()
   465  			pro.PubKey = r.keyStore.PubKey(ctx, pro.GetKeyID())
   466  			pro.PrivKey = r.keyStore.PrivKey(ctx, pro.GetKeyID())
   467  			return pro, err
   468  		}
   469  	}
   470  
   471  	return nil, ErrNotFound
   472  }
   473  
   474  // ProfilesForUsername fetches all profile that match a username (Peername)
   475  func (r *LocalStore) ProfilesForUsername(ctx context.Context, username string) ([]*Profile, error) {
   476  	r.Lock()
   477  	defer r.Unlock()
   478  
   479  	ps, err := r.profiles()
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  
   484  	var res []*Profile
   485  
   486  	for id, p := range ps {
   487  		if p.Peername == username {
   488  			pro := &Profile{}
   489  			if err := pro.Decode(p); err != nil {
   490  				log.Debugw("decoding LocalStore profile", "id", id, "err", err)
   491  				continue
   492  			}
   493  			pro.KeyID = pro.GetKeyID()
   494  			pro.PubKey = r.keyStore.PubKey(ctx, pro.GetKeyID())
   495  			pro.PrivKey = r.keyStore.PrivKey(ctx, pro.GetKeyID())
   496  			res = append(res, pro)
   497  		}
   498  	}
   499  
   500  	return res, nil
   501  }
   502  
   503  // PeerProfile gives the profile that corresponds with a given peer.ID
   504  func (r *LocalStore) PeerProfile(ctx context.Context, id peer.ID) (*Profile, error) {
   505  	log.Debugf("peerProfile: %s", id.Pretty())
   506  
   507  	r.Lock()
   508  	defer r.Unlock()
   509  
   510  	ps, err := r.profiles()
   511  	if err != nil {
   512  		return nil, err
   513  	}
   514  
   515  	str := fmt.Sprintf("/ipfs/%s", id.Pretty())
   516  	for _, p := range ps {
   517  		for _, id := range p.PeerIDs {
   518  			if id == str {
   519  				pro := &Profile{}
   520  				err := pro.Decode(p)
   521  				return pro, err
   522  			}
   523  		}
   524  	}
   525  
   526  	return nil, ErrNotFound
   527  }
   528  
   529  // DeleteProfile removes a profile from the store
   530  func (r *LocalStore) DeleteProfile(ctx context.Context, id ID) error {
   531  	r.Lock()
   532  	defer r.Unlock()
   533  
   534  	ps, err := r.profiles()
   535  	if err != nil {
   536  		return err
   537  	}
   538  	delete(ps, id.Encode())
   539  	return r.saveFile(ps)
   540  }
   541  
   542  func (r *LocalStore) saveFile(ps map[string]*config.ProfilePod) error {
   543  
   544  	data, err := json.Marshal(ps)
   545  	if err != nil {
   546  		log.Debug(err.Error())
   547  		return err
   548  	}
   549  
   550  	log.Debugf("writing profiles: %s", r.filename)
   551  	if err := r.flock.Lock(); err != nil {
   552  		return err
   553  	}
   554  	defer func() {
   555  		r.flock.Unlock()
   556  		log.Debugf("profiles written")
   557  	}()
   558  	return ioutil.WriteFile(r.filename, data, 0644)
   559  }
   560  
   561  func (r *LocalStore) profiles() (map[string]*config.ProfilePod, error) {
   562  	log.Debug("reading profiles")
   563  
   564  	if err := r.flock.Lock(); err != nil {
   565  		return nil, err
   566  	}
   567  	defer func() {
   568  		log.Debug("profiles read")
   569  		r.flock.Unlock()
   570  	}()
   571  
   572  	pp := map[string]*config.ProfilePod{}
   573  	data, err := ioutil.ReadFile(r.filename)
   574  	if err != nil {
   575  		if os.IsNotExist(err) {
   576  			return pp, nil
   577  		}
   578  		log.Debug(err.Error())
   579  		return pp, fmt.Errorf("error loading peers: %s", err.Error())
   580  	}
   581  
   582  	if err := json.Unmarshal(data, &pp); err != nil {
   583  		log.Error(err.Error())
   584  		// TODO - this is totally screwed for some reason, so for now when things fail,
   585  		// let's just return an empty list of peers
   586  		return pp, nil
   587  	}
   588  	return pp, nil
   589  }