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 }