# -*- coding: utf-8 -*- from accounts.app import AccountsFlask from accounts.models import Account class NoSuchUserError(ValueError): pass class InvalidPasswordError(ValueError): pass class ShouldNotHappen(RuntimeError): pass class Backend(object): """ This is the backend class for the account management. It is stateless, so every request needs the authentication data again. * register a new user >> backend = Backend(app) >> foo = Account('foo','foo@bar.de', password='bar') >> backend.register(foo) * authenticate a new user >> backend = Backend(app) >> foo = backend.auth('foo', 'bar') * updates an account >> foo.change_mail('a@b.de') >> foo.change_password('newpw','oldpw') # changes root password >> foo.change_password('newpw','oldpw', 'gitlab') # changes password for gitlab >> backend.update(foo) # save changes in the backend # save changes in the backend as admin user (no need for old password) >> backend.update(foo, as_admin=True) * delete an account >> backend = Backend(app) >> backend.delete(Account) * find accounts >> backend = Backend(app) >> all_accounts = backend.find() # find all accounts >> print([x.uid for x in all_accounts]) >> backend.find_by_uid('test') # find users by uid >> backend.get_by_uid('test') # same, raise NoSuchUserError if no match >> backend.find_by_mail('test@test.de') # find users by mail >> backend.find_by_uid('test*', wildcard=True) # find with wildcards """ app: AccountsFlask def __init__(self, app: AccountsFlask) -> None: self.app = app #: Exception type, that is raised if no matching user was found. self.NoSuchUserError = NoSuchUserError #: Exception type, that is raised if you try to authenticate with #: wrong password. Because this backend is stateless, this exception #: could also be raised, if you want to change user information. self.InvalidPasswordError = InvalidPasswordError def auth(self, username: str, password: str): """ Tries to authenticate a user with a given password. If the authentication is successful an Account object will be returned. """ raise NotImplementedError() def get_by_uid(self, uid: str) -> Account: """ Find a single user by uid. Unlike find_by_uid, don't return a list but raise NoSuchUserError if there is no such user. """ users = self.find_by_uid(uid) if len(users) == 0: raise NoSuchUserError("No such user") if len(users) > 1: raise ShouldNotHappen("Several users for one uid returned.") return users[0] def get_by_mail(self, mail: str) -> Account: """ Find a single user by mail. Unlike find_by_mail, don't return a list but raise NoSuchUserError if there is no such user. """ users = self.find_by_mail(mail) if len(users) == 0: raise NoSuchUserError("No such user") if len(users) > 1: raise ShouldNotHappen("Several users for one mail returned.") return users[0] def find_by_uid(self, uid: str, wildcard=False) -> list[Account]: return self.find({"uid": uid}, wildcard) def find_by_mail(self, mail: str, wildcard=False) -> list[Account]: return self.find({"mail": mail}, wildcard) def find(self, filters=None, wildcard=False) -> list[Account]: """ Find accounts by a given filter. """ raise NotImplementedError() def register(self, account: Account): """ Register a new user account. This message checks the given account for plausibility, get a new uidNumber and store the account into the backend. """ if account.password is None: raise ValueError("Password required for register") account.uidNumber = self._get_next_uidNumber() self._store(account) def update(self, account: Account, as_admin=False): """ Updates account information like passwords or email. """ raise NotImplementedError() def delete(self, account, as_admin=False): """ Deletes an account permanently. """ raise NotImplementedError() def _store(self, account: Account) -> None: """ Persists an account in the backend. """ raise NotImplementedError() def _get_next_uidNumber(self) -> int: """ Get the next free uid number. During registration the backend need to assign a unique uidNumber for each user. Most backends are unable to automaticall increment a value. So this method is used by register() to get the next free uid number in a safe way. """ raise NotImplementedError()