5
The REST API can be used to add and remove users, add and remove user
6
addresses, and change their preferred address, password, or name. The API can
7
also be used to verify a user's password.
9
Users are different than members; the latter represents an email address
10
subscribed to a specific mailing list. Users are just people that Mailman
13
There are no users yet.
15
>>> dump_json('http://localhost:9001/3.0/users')
20
Anne is added, with an email address. Her user record gets a `user_id`.
22
>>> from zope.component import getUtility
23
>>> from mailman.interfaces.usermanager import IUserManager
24
>>> user_manager = getUtility(IUserManager)
25
>>> anne = user_manager.create_user('anne@example.com', 'Anne Person')
26
>>> transaction.commit()
27
>>> int(anne.user_id.int)
30
Anne's user record is returned as an entry into the collection of all users.
32
>>> dump_json('http://localhost:9001/3.0/users')
34
created_on: 2005-08-01T07:49:23
35
display_name: Anne Person
37
self_link: http://localhost:9001/3.0/users/1
43
A user might not have a display name, in which case, the attribute will not be
44
returned in the REST API.
46
>>> bart = user_manager.create_user('bart@example.com')
47
>>> transaction.commit()
48
>>> dump_json('http://localhost:9001/3.0/users')
50
created_on: 2005-08-01T07:49:23
51
display_name: Anne Person
53
self_link: http://localhost:9001/3.0/users/1
56
created_on: 2005-08-01T07:49:23
58
self_link: http://localhost:9001/3.0/users/2
65
Paginating over user records
66
----------------------------
68
Instead of returning all the user records at once, it's possible to return
69
them in pages by adding the GET parameters ``count`` and ``page`` to the
70
request URI. Page 1 is the first page and ``count`` defines the size of the
74
>>> dump_json('http://localhost:9001/3.0/users?count=1&page=1')
76
created_on: 2005-08-01T07:49:23
77
display_name: Anne Person
79
self_link: http://localhost:9001/3.0/users/1
85
>>> dump_json('http://localhost:9001/3.0/users?count=1&page=2')
87
created_on: 2005-08-01T07:49:23
89
self_link: http://localhost:9001/3.0/users/2
99
New users can be created by POSTing to the users collection. At a minimum,
100
the user's email address must be provided.
102
>>> dump_json('http://localhost:9001/3.0/users', {
103
... 'email': 'cris@example.com',
107
location: http://localhost:9001/3.0/users/3
111
Cris is now a user known to the system, but he has no display name.
113
>>> user_manager.get_user('cris@example.com')
116
Cris's user record can also be accessed via the REST API, using her user id.
117
Note that because no password was given when the record was created, a random
118
one was assigned to her.
120
>>> dump_json('http://localhost:9001/3.0/users/3')
121
created_on: 2005-08-01T07:49:23
123
password: {plaintext}...
124
self_link: http://localhost:9001/3.0/users/3
127
Because email addresses just have an ``@`` sign in then, there's no confusing
128
them with user ids. Thus, Cris's record can be retrieved via her email
131
>>> dump_json('http://localhost:9001/3.0/users/cris@example.com')
132
created_on: 2005-08-01T07:49:23
134
password: {plaintext}...
135
self_link: http://localhost:9001/3.0/users/3
139
Providing a display name
140
------------------------
142
When a user is added, a display name can be provided.
144
>>> transaction.abort()
145
>>> dump_json('http://localhost:9001/3.0/users', {
146
... 'email': 'dave@example.com',
147
... 'display_name': 'Dave Person',
151
location: http://localhost:9001/3.0/users/4
155
Dave's user record includes his display name.
157
>>> dump_json('http://localhost:9001/3.0/users/4')
158
created_on: 2005-08-01T07:49:23
159
display_name: Dave Person
161
password: {plaintext}...
162
self_link: http://localhost:9001/3.0/users/4
169
To avoid getting assigned a random, and irretrievable password (but one which
170
can be reset), you can provide a password when the user is created. By
171
default, the password is provided in plain text, and it is hashed by Mailman
174
>>> transaction.abort()
175
>>> dump_json('http://localhost:9001/3.0/users', {
176
... 'email': 'elly@example.com',
177
... 'display_name': 'Elly Person',
178
... 'password': 'supersekrit',
182
location: http://localhost:9001/3.0/users/5
186
When we view Elly's user record, we can tell that her password has been hashed
187
because it has the hash algorithm prefix (i.e. the *{plaintext}* marker).
189
>>> dump_json('http://localhost:9001/3.0/users/5')
190
created_on: 2005-08-01T07:49:23
191
display_name: Elly Person
193
password: {plaintext}supersekrit
194
self_link: http://localhost:9001/3.0/users/5
201
Dave's display name can be changed through the REST API.
203
>>> dump_json('http://localhost:9001/3.0/users/4', {
204
... 'display_name': 'David Person',
205
... }, method='PATCH')
211
Dave's display name has been updated.
213
>>> dump_json('http://localhost:9001/3.0/users/dave@example.com')
214
created_on: 2005-08-01T07:49:23
215
display_name: David Person
217
password: {plaintext}...
218
self_link: http://localhost:9001/3.0/users/4
221
Dave can also be assigned a new password by providing in the new cleartext
222
password. Mailman will hash this before it is stored internally.
224
>>> dump_json('http://localhost:9001/3.0/users/4', {
225
... 'cleartext_password': 'clockwork angels',
226
... }, method='PATCH')
232
As described above, even though you see *{plaintext}clockwork angels* below,
233
it has still been hashed before storage. The default hashing algorithm for
234
the test suite is a plain text hash, but you can see that it works by the
235
addition of the algorithm prefix.
237
>>> dump_json('http://localhost:9001/3.0/users/4')
238
created_on: 2005-08-01T07:49:23
239
display_name: David Person
241
password: {plaintext}clockwork angels
242
self_link: http://localhost:9001/3.0/users/4
245
You can change both the display name and the password by PUTing the full
248
>>> dump_json('http://localhost:9001/3.0/users/4', {
249
... 'display_name': 'David Personhood',
250
... 'cleartext_password': 'the garden',
257
Dave's user record has been updated.
259
>>> dump_json('http://localhost:9001/3.0/users/dave@example.com')
260
created_on: 2005-08-01T07:49:23
261
display_name: David Personhood
263
password: {plaintext}the garden
264
self_link: http://localhost:9001/3.0/users/4
268
Deleting users via the API
269
==========================
271
Users can also be deleted via the API.
273
>>> dump_json('http://localhost:9001/3.0/users/cris@example.com',
284
Fred may have any number of email addresses associated with his user account,
285
and we can find them all through the API.
287
Through some other means, Fred registers a bunch of email addresses and
288
associates them with his user account.
290
>>> fred = user_manager.create_user('fred@example.com', 'Fred Person')
291
>>> fred.register('fperson@example.com')
292
<Address: fperson@example.com [not verified] at ...>
293
>>> fred.register('fred.person@example.com')
294
<Address: fred.person@example.com [not verified] at ...>
295
>>> fred.register('Fred.Q.Person@example.com')
296
<Address: Fred.Q.Person@example.com [not verified]
297
key: fred.q.person@example.com at ...>
298
>>> transaction.commit()
300
When we access Fred's addresses via the REST API, they are sorted in lexical
301
order by original (i.e. case-preserved) email address.
303
>>> dump_json('http://localhost:9001/3.0/users/fred@example.com/addresses')
305
email: fred.q.person@example.com
307
original_email: Fred.Q.Person@example.com
308
registered_on: 2005-08-01T07:49:23
310
http://localhost:9001/3.0/addresses/fred.q.person@example.com
311
user: http://localhost:9001/3.0/users/6
313
email: fperson@example.com
315
original_email: fperson@example.com
316
registered_on: 2005-08-01T07:49:23
317
self_link: http://localhost:9001/3.0/addresses/fperson@example.com
318
user: http://localhost:9001/3.0/users/6
320
email: fred.person@example.com
322
original_email: fred.person@example.com
323
registered_on: 2005-08-01T07:49:23
324
self_link: http://localhost:9001/3.0/addresses/fred.person@example.com
325
user: http://localhost:9001/3.0/users/6
327
display_name: Fred Person
328
email: fred@example.com
330
original_email: fred@example.com
331
registered_on: 2005-08-01T07:49:23
332
self_link: http://localhost:9001/3.0/addresses/fred@example.com
333
user: http://localhost:9001/3.0/users/6
338
In fact, since these are all associated with Fred's user account, any of the
339
addresses can be used to look up Fred's user record.
342
>>> dump_json('http://localhost:9001/3.0/users/fred@example.com')
343
created_on: 2005-08-01T07:49:23
344
display_name: Fred Person
346
self_link: http://localhost:9001/3.0/users/6
349
>>> dump_json('http://localhost:9001/3.0/users/fred.person@example.com')
350
created_on: 2005-08-01T07:49:23
351
display_name: Fred Person
353
self_link: http://localhost:9001/3.0/users/6
356
>>> dump_json('http://localhost:9001/3.0/users/fperson@example.com')
357
created_on: 2005-08-01T07:49:23
358
display_name: Fred Person
360
self_link: http://localhost:9001/3.0/users/6
363
>>> dump_json('http://localhost:9001/3.0/users/Fred.Q.Person@example.com')
364
created_on: 2005-08-01T07:49:23
365
display_name: Fred Person
367
self_link: http://localhost:9001/3.0/users/6
374
A user's password is stored internally in hashed form. Logging in a user is
375
the process of verifying a provided clear text password against the hashed
378
When Elly was added as a user, she provided a password in the clear. Now the
379
password is hashed and getting her user record returns the hashed password.
381
>>> dump_json('http://localhost:9001/3.0/users/5')
382
created_on: 2005-08-01T07:49:23
383
display_name: Elly Person
385
password: {plaintext}supersekrit
386
self_link: http://localhost:9001/3.0/users/5
389
Unless the client can run the hashing algorithm on the login text that Elly
390
provided, and do its own comparison, the client should let the REST API handle
391
password verification.
393
This time, Elly successfully logs into Mailman.
395
>>> dump_json('http://localhost:9001/3.0/users/5/login', {
396
... 'cleartext_password': 'supersekrit',
397
... }, method='POST')