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 }