github.com/cs3org/reva/v2@v2.27.7/pkg/siteacc/manager/accmanager.go (about)

     1  // Copyright 2018-2020 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package manager
    20  
    21  import (
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/cs3org/reva/v2/pkg/siteacc/config"
    27  	"github.com/cs3org/reva/v2/pkg/siteacc/data"
    28  	"github.com/cs3org/reva/v2/pkg/siteacc/email"
    29  	"github.com/cs3org/reva/v2/pkg/siteacc/manager/gocdb"
    30  	"github.com/cs3org/reva/v2/pkg/smtpclient"
    31  	"github.com/pkg/errors"
    32  	"github.com/rs/zerolog"
    33  	"github.com/sethvargo/go-password/password"
    34  )
    35  
    36  const (
    37  	// FindByEmail holds the string value of the corresponding search criterium.
    38  	FindByEmail = "email"
    39  )
    40  
    41  // AccountsManager is responsible for all site account related tasks.
    42  type AccountsManager struct {
    43  	conf *config.Configuration
    44  	log  *zerolog.Logger
    45  
    46  	storage data.Storage
    47  
    48  	accounts          data.Accounts
    49  	accountsListeners []AccountsListener
    50  
    51  	smtp *smtpclient.SMTPCredentials
    52  
    53  	mutex sync.RWMutex
    54  }
    55  
    56  func (mngr *AccountsManager) initialize(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) error {
    57  	if conf == nil {
    58  		return errors.Errorf("no configuration provided")
    59  	}
    60  	mngr.conf = conf
    61  
    62  	if log == nil {
    63  		return errors.Errorf("no logger provided")
    64  	}
    65  	mngr.log = log
    66  
    67  	if storage == nil {
    68  		return errors.Errorf("no storage provided")
    69  	}
    70  	mngr.storage = storage
    71  
    72  	mngr.accounts = make(data.Accounts, 0, 32) // Reserve some space for accounts
    73  	mngr.readAllAccounts()
    74  
    75  	// Register accounts listeners
    76  	if listener, err := gocdb.NewListener(mngr.conf, mngr.log); err == nil {
    77  		mngr.accountsListeners = append(mngr.accountsListeners, listener)
    78  	} else {
    79  		return errors.Wrap(err, "unable to create the GOCDB accounts listener")
    80  	}
    81  
    82  	// Create the SMTP client
    83  	if conf.Email.SMTP != nil {
    84  		mngr.smtp = smtpclient.NewSMTPCredentials(conf.Email.SMTP)
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func (mngr *AccountsManager) readAllAccounts() {
    91  	if accounts, err := mngr.storage.ReadAccounts(); err == nil {
    92  		mngr.accounts = *accounts
    93  	} else {
    94  		// Just warn when not being able to read accounts
    95  		mngr.log.Warn().Err(err).Msg("error while reading accounts")
    96  	}
    97  }
    98  
    99  func (mngr *AccountsManager) writeAllAccounts() {
   100  	if err := mngr.storage.WriteAccounts(&mngr.accounts); err != nil {
   101  		// Just warn when not being able to write accounts
   102  		mngr.log.Warn().Err(err).Msg("error while writing accounts")
   103  	}
   104  }
   105  
   106  func (mngr *AccountsManager) findAccount(by string, value string) (*data.Account, error) {
   107  	if len(value) == 0 {
   108  		return nil, errors.Errorf("no search value specified")
   109  	}
   110  
   111  	var account *data.Account
   112  	switch strings.ToLower(by) {
   113  	case FindByEmail:
   114  		account = mngr.findAccountByPredicate(func(account *data.Account) bool { return strings.EqualFold(account.Email, value) })
   115  
   116  	default:
   117  		return nil, errors.Errorf("invalid search type %v", by)
   118  	}
   119  
   120  	if account != nil {
   121  		return account, nil
   122  	}
   123  
   124  	return nil, errors.Errorf("no user found matching the specified criteria")
   125  }
   126  
   127  func (mngr *AccountsManager) findAccountByPredicate(predicate func(*data.Account) bool) *data.Account {
   128  	for _, account := range mngr.accounts {
   129  		if predicate(account) {
   130  			return account
   131  		}
   132  	}
   133  	return nil
   134  }
   135  
   136  // CreateAccount creates a new account; if an account with the same email address already exists, an error is returned.
   137  func (mngr *AccountsManager) CreateAccount(accountData *data.Account) error {
   138  	mngr.mutex.Lock()
   139  	defer mngr.mutex.Unlock()
   140  
   141  	// Accounts must be unique (identified by their email address)
   142  	if account, _ := mngr.findAccount(FindByEmail, accountData.Email); account != nil {
   143  		return errors.Errorf("an account with the specified email address already exists")
   144  	}
   145  
   146  	if account, err := data.NewAccount(accountData.Email, accountData.Title, accountData.FirstName, accountData.LastName, accountData.Site, accountData.Role, accountData.PhoneNumber, accountData.Password.Value); err == nil {
   147  		mngr.accounts = append(mngr.accounts, account)
   148  		mngr.storage.AccountAdded(account)
   149  		mngr.writeAllAccounts()
   150  
   151  		mngr.sendEmail(account, nil, email.SendAccountCreated)
   152  		mngr.callListeners(account, AccountsListener.AccountCreated)
   153  	} else {
   154  		return errors.Wrap(err, "error while creating account")
   155  	}
   156  
   157  	return nil
   158  }
   159  
   160  // UpdateAccount updates the account identified by the account email; if no such account exists, an error is returned.
   161  func (mngr *AccountsManager) UpdateAccount(accountData *data.Account, setPassword bool, copyData bool) error {
   162  	mngr.mutex.Lock()
   163  	defer mngr.mutex.Unlock()
   164  
   165  	account, err := mngr.findAccount(FindByEmail, accountData.Email)
   166  	if err != nil {
   167  		return errors.Wrap(err, "user to update not found")
   168  	}
   169  
   170  	if err := account.Update(accountData, setPassword, copyData); err == nil {
   171  		account.DateModified = time.Now()
   172  
   173  		mngr.storage.AccountUpdated(account)
   174  		mngr.writeAllAccounts()
   175  
   176  		mngr.callListeners(account, AccountsListener.AccountUpdated)
   177  	} else {
   178  		return errors.Wrap(err, "error while updating account")
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  // ConfigureAccount configures the account identified by the account email; if no such account exists, an error is returned.
   185  func (mngr *AccountsManager) ConfigureAccount(accountData *data.Account) error {
   186  	mngr.mutex.Lock()
   187  	defer mngr.mutex.Unlock()
   188  
   189  	account, err := mngr.findAccount(FindByEmail, accountData.Email)
   190  	if err != nil {
   191  		return errors.Wrap(err, "user to configure not found")
   192  	}
   193  
   194  	if err := account.Configure(accountData); err == nil {
   195  		account.DateModified = time.Now()
   196  
   197  		mngr.storage.AccountUpdated(account)
   198  		mngr.writeAllAccounts()
   199  
   200  		mngr.callListeners(account, AccountsListener.AccountUpdated)
   201  	} else {
   202  		return errors.Wrap(err, "error while configuring account")
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  // ResetPassword resets the password for the given user.
   209  func (mngr *AccountsManager) ResetPassword(name string) error {
   210  	account, err := mngr.findAccount(FindByEmail, name)
   211  	if err != nil {
   212  		return errors.Wrap(err, "user to reset password for not found")
   213  	}
   214  	accountUpd := account.Clone(true)
   215  	accountUpd.Password.Value = password.MustGenerate(defaultPasswordLength, 2, 0, false, true)
   216  
   217  	err = mngr.UpdateAccount(accountUpd, true, false)
   218  	if err == nil {
   219  		mngr.sendEmail(accountUpd, nil, email.SendPasswordReset)
   220  	}
   221  
   222  	return err
   223  }
   224  
   225  // FindAccount is used to find an account by various criteria. The account is cloned to prevent data changes.
   226  func (mngr *AccountsManager) FindAccount(by string, value string) (*data.Account, error) {
   227  	return mngr.FindAccountEx(by, value, true)
   228  }
   229  
   230  // FindAccountEx is used to find an account by various criteria and optionally clone the account.
   231  func (mngr *AccountsManager) FindAccountEx(by string, value string, cloneAccount bool) (*data.Account, error) {
   232  	mngr.mutex.RLock()
   233  	defer mngr.mutex.RUnlock()
   234  
   235  	account, err := mngr.findAccount(by, value)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	if cloneAccount {
   241  		account = account.Clone(false)
   242  	}
   243  
   244  	return account, nil
   245  }
   246  
   247  // GrantSiteAccess sets the Site access status of the account identified by the account email; if no such account exists, an error is returned.
   248  func (mngr *AccountsManager) GrantSiteAccess(accountData *data.Account, grantAccess bool) error {
   249  	mngr.mutex.Lock()
   250  	defer mngr.mutex.Unlock()
   251  
   252  	account, err := mngr.findAccount(FindByEmail, accountData.Email)
   253  	if err != nil {
   254  		return errors.Wrap(err, "no account with the specified email exists")
   255  	}
   256  
   257  	return mngr.grantAccess(account, &account.Data.SiteAccess, grantAccess, email.SendSiteAccessGranted)
   258  }
   259  
   260  // GrantGOCDBAccess sets the GOCDB access status of the account identified by the account email; if no such account exists, an error is returned.
   261  func (mngr *AccountsManager) GrantGOCDBAccess(accountData *data.Account, grantAccess bool) error {
   262  	mngr.mutex.Lock()
   263  	defer mngr.mutex.Unlock()
   264  
   265  	account, err := mngr.findAccount(FindByEmail, accountData.Email)
   266  	if err != nil {
   267  		return errors.Wrap(err, "no account with the specified email exists")
   268  	}
   269  
   270  	return mngr.grantAccess(account, &account.Data.GOCDBAccess, grantAccess, email.SendGOCDBAccessGranted)
   271  }
   272  
   273  // RemoveAccount removes the account identified by the account email; if no such account exists, an error is returned.
   274  func (mngr *AccountsManager) RemoveAccount(accountData *data.Account) error {
   275  	mngr.mutex.Lock()
   276  	defer mngr.mutex.Unlock()
   277  
   278  	for i, account := range mngr.accounts {
   279  		if strings.EqualFold(account.Email, accountData.Email) {
   280  			mngr.accounts = append(mngr.accounts[:i], mngr.accounts[i+1:]...)
   281  			mngr.storage.AccountRemoved(account)
   282  			mngr.writeAllAccounts()
   283  
   284  			mngr.callListeners(account, AccountsListener.AccountRemoved)
   285  			return nil
   286  		}
   287  	}
   288  
   289  	return errors.Errorf("no account with the specified email exists")
   290  }
   291  
   292  // SendContactForm sends a generic email to the ScienceMesh admins.
   293  func (mngr *AccountsManager) SendContactForm(account *data.Account, subject, message string) {
   294  	mngr.sendEmail(account, map[string]string{"Subject": subject, "Message": message}, email.SendContactForm)
   295  }
   296  
   297  // CloneAccounts retrieves all accounts currently stored by cloning the data, thus avoiding race conflicts and making outside modifications impossible.
   298  func (mngr *AccountsManager) CloneAccounts(erasePasswords bool) data.Accounts {
   299  	mngr.mutex.RLock()
   300  	defer mngr.mutex.RUnlock()
   301  
   302  	clones := make(data.Accounts, 0, len(mngr.accounts))
   303  	for _, acc := range mngr.accounts {
   304  		clones = append(clones, acc.Clone(erasePasswords))
   305  	}
   306  
   307  	return clones
   308  }
   309  
   310  func (mngr *AccountsManager) grantAccess(account *data.Account, accessFlag *bool, grantAccess bool, emailFunc email.SendFunction) error {
   311  	accessOld := *accessFlag
   312  	*accessFlag = grantAccess
   313  
   314  	mngr.storage.AccountUpdated(account)
   315  	mngr.writeAllAccounts()
   316  
   317  	if *accessFlag && *accessFlag != accessOld {
   318  		mngr.sendEmail(account, nil, emailFunc)
   319  	}
   320  
   321  	mngr.callListeners(account, AccountsListener.AccountUpdated)
   322  
   323  	return nil
   324  }
   325  
   326  func (mngr *AccountsManager) callListeners(account *data.Account, cb AccountsListenerCallback) {
   327  	for _, listener := range mngr.accountsListeners {
   328  		cb(listener, account)
   329  	}
   330  }
   331  
   332  func (mngr *AccountsManager) sendEmail(account *data.Account, params map[string]string, sendFunc email.SendFunction) {
   333  	_ = sendFunc(account, []string{account.Email, mngr.conf.Email.NotificationsMail}, params, *mngr.conf)
   334  }
   335  
   336  // NewAccountsManager creates a new accounts manager instance.
   337  func NewAccountsManager(storage data.Storage, conf *config.Configuration, log *zerolog.Logger) (*AccountsManager, error) {
   338  	mngr := &AccountsManager{}
   339  	if err := mngr.initialize(storage, conf, log); err != nil {
   340  		return nil, errors.Wrap(err, "unable to initialize the accounts manager")
   341  	}
   342  	return mngr, nil
   343  }