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  }