Users

Available users in a Plone site can be created, queried, updated and deleted by interacting with the /@users endpoint on portal root (requires an authenticated user):

List Users

To retrieve a list of all current users in the portal, call the /@users endpoint with a GET request:

http

GET /plone/@users HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i http://nohost/plone/@users -H 'Accept: application/json' --user admin:secret

httpie

http http://nohost/plone/@users Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/@users', headers={
    'Accept': 'application/json',
}, auth=('admin', 'secret'))

The server will respond with a list of all users in the portal:

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "@id": "http://localhost:55001/plone/@users/admin", 
    "description": "This is an admin user", 
    "email": "admin@example.com", 
    "fullname": "Administrator", 
    "home_page": "http://www.example.com", 
    "id": "admin", 
    "location": "Berlin", 
    "portrait": null, 
    "roles": [
      "Manager"
    ], 
    "username": "admin"
  }, 
  {
    "@id": "http://localhost:55001/plone/@users/test_user_1_", 
    "description": "This is a test user", 
    "email": "test@example.com", 
    "fullname": "Test User", 
    "home_page": "http://www.example.com", 
    "id": "test_user_1_", 
    "location": "Bonn", 
    "portrait": null, 
    "roles": [
      "Manager"
    ], 
    "username": "test-user"
  }
]

This only works for Manager users, anonymous users or logged-in users without Manager rights are now allowed to list users. This is the example as an anonymous user:

http

GET /plone/@users HTTP/1.1
Accept: application/json

curl

curl -i http://nohost/plone/@users -H 'Accept: application/json'

httpie

http http://nohost/plone/@users Accept:application/json

python-requests

requests.get('http://nohost/plone/@users', headers={
    'Accept': 'application/json',
})

The server will return a 401 Unauthorized status code

HTTP/1.1 401 Unauthorized
Content-Type: application/json

null

And this one as an user without the proper rights:

http

GET /plone/@users HTTP/1.1
Accept: application/json
Authorization: Basic bm9hbTpwYXNzd29yZA==

curl

curl -i http://nohost/plone/@users -H 'Accept: application/json' --user noam:password

httpie

http http://nohost/plone/@users Accept:application/json -a noam:password

python-requests

requests.get('http://nohost/plone/@users', headers={
    'Accept': 'application/json',
}, auth=('noam', 'password'))

The server will return a 401 Unauthorized status code

HTTP/1.1 401 Unauthorized
Content-Type: application/json

null

The endpoint supports some basic filtering:

http

GET /plone/@users?query=noa HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i 'http://nohost/plone/@users?query=noa' -H 'Accept: application/json' --user admin:secret

httpie

http 'http://nohost/plone/@users?query=noa' Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/@users?query=noa', headers={
    'Accept': 'application/json',
}, auth=('admin', 'secret'))

The server will respond with a list the filtered users in the portal with username starts with the query.

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "@id": "http://localhost:55001/plone/@users/noam", 
    "description": "Professor of Linguistics", 
    "email": "noam.chomsky@example.com", 
    "fullname": "Noam Avram Chomsky", 
    "home_page": "web.mit.edu/chomsky", 
    "id": "noam", 
    "location": "Cambridge, MA", 
    "portrait": null, 
    "roles": [
      "Member"
    ], 
    "username": "noam"
  }
]

The endpoint also takes a limit parameter that defaults to a maximum of 25 users at a time for performance reasons.

Create User

To create a new user, send a POST request to the global /@users endpoint with a JSON representation of the user you want to create in the body:

http

POST /plone/@users HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "description": "Professor of Linguistics",
    "email": "noam.chomsky@example.com",
    "fullname": "Noam Avram Chomsky",
    "home_page": "web.mit.edu/chomsky",
    "location": "Cambridge, MA",
    "password": "colorlessgreenideas",
    "roles": [
        "Contributor"
    ],
    "username": "noamchomsky"
}

curl

curl -i -X POST http://nohost/plone/@users -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"description": "Professor of Linguistics", "email": "noam.chomsky@example.com", "fullname": "Noam Avram Chomsky", "home_page": "web.mit.edu/chomsky", "location": "Cambridge, MA", "password": "colorlessgreenideas", "roles": ["Contributor"], "username": "noamchomsky"}' --user admin:secret

httpie

echo '{
  "description": "Professor of Linguistics",
  "email": "noam.chomsky@example.com",
  "fullname": "Noam Avram Chomsky",
  "home_page": "web.mit.edu/chomsky",
  "location": "Cambridge, MA",
  "password": "colorlessgreenideas",
  "roles": [
    "Contributor"
  ],
  "username": "noamchomsky"
}' | http POST http://nohost/plone/@users Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.post('http://nohost/plone/@users', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'description': 'Professor of Linguistics',
    'email': 'noam.chomsky@example.com',
    'fullname': 'Noam Avram Chomsky',
    'home_page': 'web.mit.edu/chomsky',
    'location': 'Cambridge, MA',
    'password': 'colorlessgreenideas',
    'roles': ['Contributor'],
    'username': 'noamchomsky',
}, auth=('admin', 'secret'))

Note

By default, “username”, and “password” are required fields. If email login is enabled, “email” and “password” are required fields. All other fields in the example are optional.

The field “username” is not allowed when email login is enabled.

If the user has been created successfully, the server will respond with a status 201 (Created). The Location header contains the URL of the newly created user and the resource representation in the payload:

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:55001/plone/@users/noamchomsky

{
  "@id": "http://localhost:55001/plone/@users/noamchomsky", 
  "description": "Professor of Linguistics", 
  "email": "noam.chomsky@example.com", 
  "fullname": "Noam Avram Chomsky", 
  "home_page": "web.mit.edu/chomsky", 
  "id": "noamchomsky", 
  "location": "Cambridge, MA", 
  "portrait": null, 
  "roles": [
    "Contributor"
  ], 
  "username": "noamchomsky"
}

If no roles has been specified, then a default Member role is added as a sensible default.

Read User

To retrieve all details for a particular user, send a GET request to the /@users endpoint and append the user id to the URL:

http

GET /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i http://nohost/plone/@users/noam -H 'Accept: application/json' --user admin:secret

httpie

http http://nohost/plone/@users/noam Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
}, auth=('admin', 'secret'))

The server will respond with a 200 OK status code and the JSON representation of the user in the body:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "@id": "http://localhost:55001/plone/@users/noam", 
  "description": "Professor of Linguistics", 
  "email": "noam.chomsky@example.com", 
  "fullname": "Noam Avram Chomsky", 
  "home_page": "web.mit.edu/chomsky", 
  "id": "noam", 
  "location": "Cambridge, MA", 
  "portrait": null, 
  "roles": [
    "Member"
  ], 
  "username": "noam"
}

The key ‘roles’ lists the globally defined roles for the user.

Only users with Manager rights are allowed to get other users’ information:

http

GET /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic bm9hbS1mYWtlOnNlY3JldA==

curl

curl -i http://nohost/plone/@users/noam -H 'Accept: application/json' --user noam-fake:secret

httpie

http http://nohost/plone/@users/noam Accept:application/json -a noam-fake:secret

python-requests

requests.get('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
}, auth=('noam-fake', 'secret'))

If the user lacks this rights, the server will respond with a 401 Unauthorized status code:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

null

Also anonymous users are not allowed to get users’ information:

http

GET /plone/@users/noam HTTP/1.1
Accept: application/json

curl

curl -i http://nohost/plone/@users/noam -H 'Accept: application/json'

httpie

http http://nohost/plone/@users/noam Accept:application/json

python-requests

requests.get('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
})

If the user is an anonymous one, the server will respond with a 401 Unauthorized status code:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

null

But each user is allowed to get its own information.

http

GET /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic bm9hbTpzZWNyZXQ=

curl

curl -i http://nohost/plone/@users/noam -H 'Accept: application/json' --user noam:secret

httpie

http http://nohost/plone/@users/noam Accept:application/json -a noam:secret

python-requests

requests.get('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
}, auth=('noam', 'secret'))

In this case, the server will respond with a 200 OK status code and the JSON respresentation of the user in the body:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "@id": "http://localhost:55001/plone/@users/noam", 
  "description": "Professor of Linguistics", 
  "email": "noam.chomsky@example.com", 
  "fullname": "Noam Avram Chomsky", 
  "home_page": "web.mit.edu/chomsky", 
  "id": "noam", 
  "location": "Cambridge, MA", 
  "portrait": null, 
  "roles": [
    "Member"
  ], 
  "username": "noam"
}

Update User

To update the settings of a user, send a PATCH request with the user details you want to amend to the URL of that particular user, e.g. if you want to update the email address of the admin user to:

http

PATCH /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "email": "avram.chomsky@example.com",
    "roles": {
        "Contributor": false
    }
}

curl

curl -i -X PATCH http://nohost/plone/@users/noam -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"email": "avram.chomsky@example.com", "roles": {"Contributor": false}}' --user admin:secret

httpie

echo '{
  "email": "avram.chomsky@example.com",
  "roles": {
    "Contributor": false
  }
}' | http PATCH http://nohost/plone/@users/noam Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.patch('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'email': 'avram.chomsky@example.com',
    'roles': {
        'Contributor': False,
    },
}, auth=('admin', 'secret'))

A successful response to a PATCH request will be indicated by a 204 No Content response:

HTTP/1.1 204 No Content

Note

The ‘roles’ object is a mapping of a role and a boolean indicating adding or removing.

Any user is able to update their own properties and password (if allowed) by using the same request.

The user portrait/avatar can also be updated using the same serialization as the file one:

http

PATCH /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "portrait": {
        "content-type": "image/png",
        "data": "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=",
        "encoding": "base64",
        "filename": "image.png"
    }
}

curl

curl -i -X PATCH http://nohost/plone/@users/noam -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"portrait": {"content-type": "image/png", "data": "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=", "encoding": "base64", "filename": "image.png"}}' --user admin:secret

httpie

echo '{
  "portrait": {
    "content-type": "image/png",
    "data": "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=",
    "encoding": "base64",
    "filename": "image.png"
  }
}' | http PATCH http://nohost/plone/@users/noam Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.patch('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'portrait': {
        'content-type': 'image/png',
        'data': 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=',
        'encoding': 'base64',
        'filename': 'image.png',
    },
}, auth=('admin', 'secret'))

A successful response to a PATCH request will be indicated by a 204 No Content response. Then asking for the user the portrait URL should be on the response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "@id": "http://localhost:55001/plone/@users/noam", 
  "description": null, 
  "email": "noam.chomsky@example.com", 
  "fullname": null, 
  "home_page": null, 
  "id": "noam", 
  "location": null, 
  "portrait": "http://localhost:55001/plone/portal_memberdata/portraits/noam", 
  "roles": [
    "Member"
  ], 
  "username": "noam"
}

Adding the portrait via the @user endpoint does not scale it since it’s assumed that the frontend will take care of it (resizing/cropping). If you still want that Plone to take care of the scaling using the default Plone behavior for portraits, you have to add the scale attribute to the request:

http

PATCH /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "portrait": {
        "content-type": "image/png",
        "data": "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=",
        "encoding": "base64",
        "filename": "image.png",
        "scale": true
    }
}

curl

curl -i -X PATCH http://nohost/plone/@users/noam -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"portrait": {"content-type": "image/png", "data": "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=", "encoding": "base64", "filename": "image.png", "scale": true}}' --user admin:secret

httpie

echo '{
  "portrait": {
    "content-type": "image/png",
    "data": "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=",
    "encoding": "base64",
    "filename": "image.png",
    "scale": true
  }
}' | http PATCH http://nohost/plone/@users/noam Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.patch('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'portrait': {
        'content-type': 'image/png',
        'data': 'R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=',
        'encoding': 'base64',
        'filename': 'image.png',
        'scale': True,
    },
}, auth=('admin', 'secret'))

Delete User

To delete a user send a DELETE request to the /@users endpoint and append the user id of the user you want to delete, e.g. to delete the user with the id johndoe:

http

DELETE /plone/@users/noam HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X DELETE http://nohost/plone/@users/noam -H 'Accept: application/json' --user admin:secret

httpie

http DELETE http://nohost/plone/@users/noam Accept:application/json -a admin:secret

python-requests

requests.delete('http://nohost/plone/@users/noam', headers={
    'Accept': 'application/json',
}, auth=('admin', 'secret'))

A successful response will be indicated by a 204 No Content response:

HTTP/1.1 204 No Content

User registration

Plone allows you to enable the auto registration of users. If it is enabled, then an anonymous user can register a new user using the user creation endpoint. This new user will have the role Member by default as the Plone registration process also does.

To create a new user send a POST request to the @users’ endpoint:

http

POST /plone/@users HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{
    "description": "Professor of Linguistics",
    "email": "noam.chomsky@example.com",
    "fullname": "Noam Avram Chomsky",
    "home_page": "web.mit.edu/chomsky",
    "location": "Cambridge, MA",
    "sendPasswordReset": true,
    "username": "noamchomsky"
}

curl

curl -i -X POST http://nohost/plone/@users -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"description": "Professor of Linguistics", "email": "noam.chomsky@example.com", "fullname": "Noam Avram Chomsky", "home_page": "web.mit.edu/chomsky", "location": "Cambridge, MA", "sendPasswordReset": true, "username": "noamchomsky"}' --user admin:secret

httpie

echo '{
  "description": "Professor of Linguistics",
  "email": "noam.chomsky@example.com",
  "fullname": "Noam Avram Chomsky",
  "home_page": "web.mit.edu/chomsky",
  "location": "Cambridge, MA",
  "sendPasswordReset": true,
  "username": "noamchomsky"
}' | http POST http://nohost/plone/@users Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.post('http://nohost/plone/@users', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'description': 'Professor of Linguistics',
    'email': 'noam.chomsky@example.com',
    'fullname': 'Noam Avram Chomsky',
    'home_page': 'web.mit.edu/chomsky',
    'location': 'Cambridge, MA',
    'sendPasswordReset': True,
    'username': 'noamchomsky',
}, auth=('admin', 'secret'))

If the user should receive an email to set her password, you should pass ‘sendPasswordReset”: true’ in the JSON body of the request. Keep in mind that Plone will send a URL that points to the URL of the Plone site, which might just be your API endpoint.

If the user has been created, the server will respond with a 201 Created response:

HTTP/1.1 201 Created
Location: http://localhost:55001/plone/@users/noamchomsky
Content-Type: application/json

{
  "@id": "http://localhost:55001/plone/@users/noamchomsky", 
  "description": "Professor of Linguistics", 
  "email": "noam.chomsky@example.com", 
  "fullname": "Noam Avram Chomsky", 
  "home_page": "web.mit.edu/chomsky", 
  "id": "noamchomsky", 
  "location": "Cambridge, MA", 
  "portrait": null, 
  "roles": [
    "Member"
  ], 
  "username": "noamchomsky"
}

Reset User Password

Plone allows to reset a password for a user by sending a POST request to the user resource and appending /reset-password to the URL:

POST /plone/@users/noam/reset-password HTTP/1.1
Host: localhost:8080
Accept: application/json

The server will respond with a 200 OK response and send an email to the user to reset her password.

The token that is part of the reset url in the email can be used to authorize setting a new password:

http

POST /plone/@users/noam/rest-password HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
Content-Type: application/json

{"reset_token": "ef3d2aabacdc2345df63d6acf2edbef4", "new_password": "verysecret"}

curl

curl -i -X POST http://nohost/plone/@users/noam/rest-password -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"new_password": "verysecret", "reset_token": "ef3d2aabacdc2345df63d6acf2edbef4"}' --user admin:secret

httpie

echo '{
  "new_password": "verysecret",
  "reset_token": "ef3d2aabacdc2345df63d6acf2edbef4"
}' | http POST http://nohost/plone/@users/noam/rest-password Accept:application/json Content-Type:application/json -a admin:secret

python-requests

requests.post('http://nohost/plone/@users/noam/rest-password', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'new_password': 'verysecret',
    'reset_token': 'ef3d2aabacdc2345df63d6acf2edbef4',
}, auth=('admin', 'secret'))

Reset Own Password

Plone also allows a user to reset her own password directly without sending an email. The endpoint and the request is the same as above, but now the user can send the old password and the new password as payload:

POST /plone/@users/noam/reset-password HTTP/1.1
Host: localhost:8080
Accept: application/json
Content-Type: application/json

{
  'old_password': 'secret',
  'new_password': 'verysecret',
}

The server will respond with a 200 OK response without sending an email.

To set the password with the old password you need either the Set own password or the plone.app.controlpanel.UsersAndGroups permission.

If an API consumer tries to send a password in the payload that is not the same as the currently logged in user, the server will respond with a 400 Bad Request response.

Return Values