github.com/decred/politeia@v1.4.0/politeiawww/legacy/user/localdb/localdb.go (about) 1 package localdb 2 3 import ( 4 "encoding/binary" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "path/filepath" 9 "strings" 10 "sync" 11 12 "github.com/decred/politeia/politeiawww/legacy/user" 13 "github.com/google/uuid" 14 "github.com/syndtr/goleveldb/leveldb" 15 "github.com/syndtr/goleveldb/leveldb/util" 16 ) 17 18 const ( 19 UserdbPath = "users" 20 LastPaywallAddressIndex = "lastpaywallindex" 21 22 UserVersion uint32 = 1 23 UserVersionKey = "userversion" 24 25 // The key for a user session is sessionPrefix+sessionID 26 sessionPrefix = "session:" 27 28 // The key for a user email history is emailHistoryPrefix+userID 29 emailHistoryPrefix = "emailhistory:" 30 ) 31 32 var ( 33 _ user.Database = (*localdb)(nil) 34 ) 35 36 // localdb implements the Database interface. 37 type localdb struct { 38 sync.RWMutex 39 40 shutdown bool // Backend is shutdown 41 root string // Database root 42 userdb *leveldb.DB // Database context 43 pluginSettings map[string][]user.PluginSetting // [pluginID][]PluginSettings 44 } 45 46 // Version contains the database version. 47 type Version struct { 48 Version uint32 `json:"version"` // Database version 49 Time int64 `json:"time"` // Time of record creation 50 } 51 52 // isUserRecord returns true if the given key is a user record, 53 // and false otherwise. This is helpful when iterating the user records 54 // because the DB contains some non-user records. 55 func isUserRecord(key string) bool { 56 return key != UserVersionKey && 57 key != LastPaywallAddressIndex && 58 !strings.HasPrefix(key, sessionPrefix) && 59 !strings.HasPrefix(key, cmsUserPrefix) && 60 !strings.HasPrefix(key, cmsCodeStatsPrefix) && 61 !strings.HasPrefix(key, emailHistoryPrefix) 62 } 63 64 // Store new user. 65 // 66 // UserNew satisfies the Database interface. 67 func (l *localdb) UserNew(u user.User) error { 68 l.Lock() 69 defer l.Unlock() 70 71 if l.shutdown { 72 return user.ErrShutdown 73 } 74 75 log.Debugf("UserNew: %v", u) 76 77 // Make sure user does not exist 78 ok, err := l.userdb.Has([]byte(u.Email), nil) 79 if err != nil { 80 return err 81 } else if ok { 82 return user.ErrUserExists 83 } 84 85 // Fetch the next unique paywall index for the user. 86 var lastPaywallIndex uint64 87 b, err := l.userdb.Get([]byte(LastPaywallAddressIndex), nil) 88 if err != nil { 89 if !errors.Is(err, leveldb.ErrNotFound) { 90 return err 91 } 92 } else { 93 lastPaywallIndex = binary.LittleEndian.Uint64(b) + 1 94 } 95 96 // Set the new paywall index on the user. 97 u.PaywallAddressIndex = lastPaywallIndex 98 99 // Write the new paywall index back to the db. 100 err = l.SetPaywallAddressIndex(lastPaywallIndex) 101 if err != nil { 102 return err 103 } 104 105 // Set unique uuid for the user. 106 u.ID = uuid.New() 107 108 payload, err := user.EncodeUser(u) 109 if err != nil { 110 return err 111 } 112 113 return l.userdb.Put([]byte(u.Email), payload, nil) 114 } 115 116 // SetPaywallAddressIndex updates the paywall address index. 117 func (l *localdb) SetPaywallAddressIndex(index uint64) error { 118 b := make([]byte, 8) 119 binary.LittleEndian.PutUint64(b, index) 120 if err := l.userdb.Put([]byte(LastPaywallAddressIndex), b, nil); err != nil { 121 return fmt.Errorf("error updating paywall index: %v", err) 122 } 123 return nil 124 } 125 126 // InsertUser inserts a user record into the database. The record must be a 127 // complete user record and the user must not already exist. This function is 128 // intended to be used for migrations between databases. 129 // 130 // InsertUser satisfies the Database interface. 131 func (l *localdb) InsertUser(u user.User) error { 132 l.Lock() 133 defer l.Unlock() 134 135 if l.shutdown { 136 return user.ErrShutdown 137 } 138 139 log.Debugf("InsertUser: %v", u) 140 141 // Make sure user does not exist 142 ok, err := l.userdb.Has([]byte(u.Email), nil) 143 if err != nil { 144 return err 145 } else if ok { 146 return user.ErrUserExists 147 } 148 149 payload, err := user.EncodeUser(u) 150 if err != nil { 151 return err 152 } 153 154 return l.userdb.Put([]byte(u.Email), payload, nil) 155 } 156 157 // UserGet returns a user record if found in the database. 158 // 159 // UserGet satisfies the Database interface. 160 func (l *localdb) UserGet(email string) (*user.User, error) { 161 l.RLock() 162 defer l.RUnlock() 163 164 if l.shutdown { 165 return nil, user.ErrShutdown 166 } 167 168 payload, err := l.userdb.Get([]byte(strings.ToLower(email)), nil) 169 if errors.Is(err, leveldb.ErrNotFound) { 170 return nil, user.ErrUserNotFound 171 } else if err != nil { 172 return nil, err 173 } 174 175 u, err := user.DecodeUser(payload) 176 if err != nil { 177 return nil, err 178 } 179 180 return u, nil 181 } 182 183 // UserGetByUsername returns a user record given its username, if found in the 184 // database. 185 // 186 // UserGetByUsername satisfies the Database interface. 187 func (l *localdb) UserGetByUsername(username string) (*user.User, error) { 188 l.RLock() 189 defer l.RUnlock() 190 191 if l.shutdown { 192 return nil, user.ErrShutdown 193 } 194 195 log.Debugf("UserGetByUsername") 196 197 iter := l.userdb.NewIterator(nil, nil) 198 for iter.Next() { 199 key := iter.Key() 200 value := iter.Value() 201 202 if !isUserRecord(string(key)) { 203 continue 204 } 205 206 u, err := user.DecodeUser(value) 207 if err != nil { 208 return nil, err 209 } 210 211 if strings.EqualFold(u.Username, username) { 212 return u, err 213 } 214 } 215 iter.Release() 216 217 if iter.Error() != nil { 218 return nil, iter.Error() 219 } 220 221 return nil, user.ErrUserNotFound 222 } 223 224 // UserGetByPubKey returns a user record given its public key. The public key 225 // can be any of the public keys in the user's identity history. 226 // 227 // UserGetByPubKey satisfies the Database interface. 228 func (l *localdb) UserGetByPubKey(pubKey string) (*user.User, error) { 229 log.Tracef("UserGetByPubKey: %v", pubKey) 230 231 l.RLock() 232 defer l.RUnlock() 233 234 if l.shutdown { 235 return nil, user.ErrShutdown 236 } 237 238 iter := l.userdb.NewIterator(nil, nil) 239 for iter.Next() { 240 key := iter.Key() 241 value := iter.Value() 242 if !isUserRecord(string(key)) { 243 continue 244 } 245 u, err := user.DecodeUser(value) 246 if err != nil { 247 return nil, err 248 } 249 for _, v := range u.Identities { 250 if v.String() == pubKey { 251 return u, err 252 } 253 } 254 } 255 iter.Release() 256 257 if iter.Error() != nil { 258 return nil, iter.Error() 259 } 260 261 return nil, user.ErrUserNotFound 262 } 263 264 // UsersGetByPubKey returns a [pubkey]user.User map for the provided public 265 // keys. Public keys can be any of the public keys in the user's identity 266 // history. If a user is not found, the map will not include an entry for the 267 // corresponding public key. It is the responsibility of the caller to ensure 268 // results are returned for all of the provided public keys. 269 // 270 // UsersGetByPubKey satisfies the Database interface. 271 func (l *localdb) UsersGetByPubKey(pubKeys []string) (map[string]user.User, error) { 272 log.Tracef("UsersGetByPubKey: %v", pubKeys) 273 274 l.RLock() 275 defer l.RUnlock() 276 277 if l.shutdown { 278 return nil, user.ErrShutdown 279 } 280 281 // Put provided pubkeys into a map 282 pk := make(map[string]struct{}, len(pubKeys)) 283 for _, v := range pubKeys { 284 pk[v] = struct{}{} 285 } 286 287 // Iterate through all users checking if any identities 288 // (active or old) match any of the provided pubkeys. 289 users := make(map[string]user.User, len(pubKeys)) // [pubkey]User 290 iter := l.userdb.NewIterator(nil, nil) 291 for iter.Next() { 292 key := iter.Key() 293 value := iter.Value() 294 if !isUserRecord(string(key)) { 295 continue 296 } 297 u, err := user.DecodeUser(value) 298 if err != nil { 299 return nil, err 300 } 301 for _, v := range u.Identities { 302 _, ok := pk[v.String()] 303 if ok { 304 users[v.String()] = *u 305 } 306 } 307 } 308 iter.Release() 309 310 if iter.Error() != nil { 311 return nil, iter.Error() 312 } 313 314 return users, nil 315 } 316 317 // UserGetById returns a user record given its id, if found in the database. 318 // 319 // UserGetById satisfies the Database interface. 320 func (l *localdb) UserGetById(id uuid.UUID) (*user.User, error) { 321 l.RLock() 322 defer l.RUnlock() 323 324 if l.shutdown { 325 return nil, user.ErrShutdown 326 } 327 328 log.Debugf("UserGetById") 329 330 iter := l.userdb.NewIterator(nil, nil) 331 for iter.Next() { 332 key := iter.Key() 333 value := iter.Value() 334 335 if !isUserRecord(string(key)) { 336 continue 337 } 338 339 u, err := user.DecodeUser(value) 340 if err != nil { 341 return nil, err 342 } 343 344 if u.ID == id { 345 return u, err 346 } 347 } 348 iter.Release() 349 350 if iter.Error() != nil { 351 return nil, iter.Error() 352 } 353 354 return nil, user.ErrUserNotFound 355 } 356 357 // Update existing user. 358 // 359 // UserUpdate satisfies the Database interface. 360 func (l *localdb) UserUpdate(u user.User) error { 361 l.Lock() 362 defer l.Unlock() 363 364 if l.shutdown { 365 return user.ErrShutdown 366 } 367 368 log.Debugf("UserUpdate: %v", u) 369 370 // Make sure user already exists 371 exists, err := l.userdb.Has([]byte(u.Email), nil) 372 if err != nil { 373 return err 374 } else if !exists { 375 return user.ErrUserNotFound 376 } 377 378 payload, err := user.EncodeUser(u) 379 if err != nil { 380 return err 381 } 382 383 return l.userdb.Put([]byte(u.Email), payload, nil) 384 } 385 386 // Update existing user. 387 // 388 // UserUpdate satisfies the Database interface. 389 func (l *localdb) AllUsers(callbackFn func(u *user.User)) error { 390 l.Lock() 391 defer l.Unlock() 392 393 if l.shutdown { 394 return user.ErrShutdown 395 } 396 397 log.Debugf("AllUsers") 398 399 iter := l.userdb.NewIterator(nil, nil) 400 for iter.Next() { 401 key := iter.Key() 402 value := iter.Value() 403 404 if !isUserRecord(string(key)) { 405 continue 406 } 407 408 u, err := user.DecodeUser(value) 409 if err != nil { 410 return err 411 } 412 413 callbackFn(u) 414 } 415 iter.Release() 416 417 return iter.Error() 418 } 419 420 // RotateKeys is an empty stub to satisfy the user.Database interface. 421 // Localdb implementation does not use encryption. 422 func (l *localdb) RotateKeys(_ string) error { 423 return nil 424 } 425 426 // PluginExec executes the provided plugin command. 427 func (l *localdb) PluginExec(pc user.PluginCommand) (*user.PluginCommandReply, error) { 428 log.Tracef("PluginExec: %v %v", pc.ID, pc.Command) 429 430 var payload string 431 var err error 432 switch pc.ID { 433 case user.CMSPluginID: 434 payload, err = l.cmsPluginExec(pc.Command, pc.Payload) 435 default: 436 return nil, user.ErrInvalidPlugin 437 } 438 if err != nil { 439 return nil, err 440 } 441 442 return &user.PluginCommandReply{ 443 ID: pc.ID, 444 Command: pc.Command, 445 Payload: payload, 446 }, nil 447 } 448 449 // RegisterPlugin registers a plugin with the user database. 450 func (l *localdb) RegisterPlugin(p user.Plugin) error { 451 log.Tracef("RegisterPlugin: %v %v", p.ID, p.Version) 452 453 var err error 454 switch p.ID { 455 case user.CMSPluginID: 456 // This is an acceptable plugin ID 457 default: 458 return user.ErrInvalidPlugin 459 } 460 if err != nil { 461 return err 462 } 463 464 // Save plugin settings 465 l.Lock() 466 defer l.Unlock() 467 468 l.pluginSettings[p.ID] = p.Settings 469 470 return nil 471 } 472 473 // EmailHistoriesSave saves an email history for each user passed in the map. 474 // The histories map contains map[userid]EmailHistory. 475 // 476 // EmailHistoriesSave satisfies the user MailerDB interface. 477 func (l *localdb) EmailHistoriesSave(histories map[uuid.UUID]user.EmailHistory) error { 478 l.Lock() 479 defer l.Unlock() 480 481 if l.shutdown { 482 return user.ErrShutdown 483 } 484 485 if len(histories) == 0 { 486 return nil 487 } 488 489 log.Debugf("EmailHistoriesSave: %v", histories) 490 491 for id, history := range histories { 492 payload, err := json.Marshal(history) 493 if err != nil { 494 return err 495 } 496 key := []byte(emailHistoryPrefix + id.String()) 497 err = l.userdb.Put(key, payload, nil) 498 if err != nil { 499 return err 500 } 501 } 502 503 return nil 504 } 505 506 // EmailHistoriesGet retrieves the email histories for the provided user IDs 507 // The returned map[userid]EmailHistory will contain an entry for each of the 508 // provided user ID. If a provided user ID does not correspond to a user in the 509 // database, then the entry will be skipped in the returned map. An error is not 510 // returned. 511 // 512 // EmailHistoriesGet satisfies the user MailerDB interface. 513 func (l *localdb) EmailHistoriesGet(users []uuid.UUID) (map[uuid.UUID]user.EmailHistory, error) { 514 l.RLock() 515 defer l.RUnlock() 516 517 if l.shutdown { 518 return nil, user.ErrShutdown 519 } 520 521 log.Debugf("EmailHistoriesGet: %v", users) 522 523 histories := make(map[uuid.UUID]user.EmailHistory, len(users)) 524 for _, id := range users { 525 key := []byte(emailHistoryPrefix + id.String()) 526 payload, err := l.userdb.Get(key, nil) 527 if errors.Is(err, leveldb.ErrNotFound) { 528 continue 529 } else if err != nil { 530 return nil, err 531 } 532 533 var h user.EmailHistory 534 err = json.Unmarshal(payload, &h) 535 if err != nil { 536 return nil, err 537 } 538 539 histories[id] = h 540 } 541 542 return histories, nil 543 } 544 545 // Close shuts down the database. All interface functions MUST return with 546 // errShutdown if the backend is shutting down. 547 // 548 // Close satisfies the Database interface. 549 func (l *localdb) Close() error { 550 l.Lock() 551 defer l.Unlock() 552 553 l.shutdown = true 554 return l.userdb.Close() 555 } 556 557 // SessionSave saves the given session to the database. New sessions are 558 // inserted into the database. Existing sessions are updated in the database. 559 // 560 // SessionSave satisfies the user.Database interface. 561 func (l *localdb) SessionSave(s user.Session) error { 562 log.Tracef("SessionSave: %v", s) 563 564 l.Lock() 565 defer l.Unlock() 566 567 if l.shutdown { 568 return user.ErrShutdown 569 } 570 571 payload, err := user.EncodeSession(s) 572 if err != nil { 573 return err 574 } 575 576 key := []byte(sessionPrefix + s.ID) 577 return l.userdb.Put(key, payload, nil) 578 } 579 580 // SessionGetByID returns a session given its id if present in the database. 581 // 582 // SessionGetByID satisfies the user.Database interface. 583 func (l *localdb) SessionGetByID(sid string) (*user.Session, error) { 584 log.Tracef("SessionGetByID: %v", sid) 585 586 l.RLock() 587 defer l.RUnlock() 588 589 if l.shutdown { 590 return nil, user.ErrShutdown 591 } 592 593 payload, err := l.userdb.Get([]byte(sessionPrefix+sid), nil) 594 if errors.Is(err, leveldb.ErrNotFound) { 595 return nil, user.ErrSessionNotFound 596 } else if err != nil { 597 return nil, err 598 } 599 600 us, err := user.DecodeSession(payload) 601 if err != nil { 602 return nil, err 603 } 604 605 return us, nil 606 } 607 608 // SessionDeleteByID deletes the session with the given id. 609 // 610 // SessionDeleteByID satisfies the user.Database interface. 611 func (l *localdb) SessionDeleteByID(sid string) error { 612 log.Tracef("SessionDeleteByID: %v", sid) 613 614 l.RLock() 615 defer l.RUnlock() 616 617 if l.shutdown { 618 return user.ErrShutdown 619 } 620 621 err := l.userdb.Delete([]byte(sessionPrefix+sid), nil) 622 if err != nil { 623 return err 624 } 625 626 return nil 627 } 628 629 // SessionsDeleteByUserID deletes all sessions for the given user ID, except 630 // the session IDs in exemptSessionIDs. 631 // 632 // SessionsDeleteByUserID satisfies the Database interface. 633 func (l *localdb) SessionsDeleteByUserID(uid uuid.UUID, exemptSessionIDs []string) error { 634 log.Tracef("SessionsDeleteByUserId %v", uid) 635 636 l.RLock() 637 defer l.RUnlock() 638 639 if l.shutdown { 640 return user.ErrShutdown 641 } 642 643 exempt := make(map[string]struct{}, len(exemptSessionIDs)) // [sessionID]struct{} 644 for _, v := range exemptSessionIDs { 645 exempt[v] = struct{}{} 646 } 647 648 batch := new(leveldb.Batch) 649 iter := l.userdb.NewIterator(util.BytesPrefix([]byte(sessionPrefix)), nil) 650 for iter.Next() { 651 key := iter.Key() 652 value := iter.Value() 653 654 s, err := user.DecodeSession(value) 655 if err != nil { 656 return err 657 } 658 659 _, ok := exempt[s.ID] 660 if ok { 661 continue 662 } 663 if s.UserID == uid { 664 batch.Delete(key) 665 } 666 } 667 iter.Release() 668 669 return l.userdb.Write(batch, nil) 670 } 671 672 // New creates a new localdb instance. 673 func New(root string) (*localdb, error) { 674 log.Tracef("localdb New: %v", root) 675 676 l := &localdb{ 677 root: root, 678 pluginSettings: make(map[string][]user.PluginSetting), 679 } 680 err := l.openUserDB(filepath.Join(l.root, UserdbPath)) 681 if err != nil { 682 return nil, err 683 } 684 685 return l, nil 686 }