summaryrefslogtreecommitdiffstats
path: root/accounts/backend/user/__init__.py
blob: e72302a26507bfa1809cd7e21d6059f071b591e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# -*- coding: utf-8 -*-


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
    """

    def __init__(self, app):
        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, password):
        """
        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):
        """
        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):
        """
        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, wildcard=False):
        return self.find({'uid': uid}, wildcard)

    def find_by_mail(self, mail, wildcard=False):
        return self.find({'mail': mail}, wildcard)

    def find(self, filters=None, wildcard=False):
        """
        Find accounts by a given filter.
        """
        raise NotImplementedError()

    def register(self, 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, 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):
        """
        Persists an account in the backend.
        """
        raise NotImplementedError()

    def _get_next_uidNumber(self):
        """
        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()