Expand description
§User Management Extension
This is a LARS specific xAPI resource extension that allows authorized administrators to manage users.
The Extension end-point base URL is <LRS_EXTERNAL_URL>/extensions/users where LRS_EXTERNAL_URL is the configured environment variable representing the external host address of the running server instance. For brevity purposes this “base” will be assumed to prefix the segment(s) associated to a handler. So if /foo is given, the full URL to invoke the action will be <LRS_EXTERNAL_URL>/extensions/users/foo. Also if the segment is simply / it is omitted.
§Background, decisions and rationale
§Permissions
For a fine-grained access control, a system may define and require a separate Permission for each end-point or Route. However in the case of an LRS like LaRS this is IMHO an overkill. Grouping Permissions by Resource or Extension makes more practical sense.
Here’s the list of Resources LaRS services:
| Resource | URL segment suffix | Guarded? |
|---|---|---|
| statements | /statements | Yes |
| state | /activities/state | Yes |
| agents | /agents | Yes |
| activities | /activities | Yes |
| agent_profile | /agents/profile | Yes |
| activities_profile | /activities/profile | Yes |
| about | /about | No |
| verbs | /extensions/verbs | Yes |
| stats | /extensions/stats | No |
| users | /extensions/users | Yes |
We can push the envelope even further and group all xAPI Resources under a single Permission. Let’s call it the USE_XAPI permission.
In addition, we have an AUTHORIZE_STATEMENT Permission as one allowing a holder to act, under certain conditions, as the authority of a Statement.
The MANAGE_USERS Permission would govern the users Resource; e.g. creating, editing Users, assigning them Roles, etc… Note there’s no mention of the ability to create, or edit Permissions. This is because for now a fixed set of handful hard-wired enumeration variants should be enough.
Similarly a USE_VERBS Permission would govern the use of the verbs Resource.
Here’s the complete list:
| Permission | Capability |
|---|---|
USE_XAPI | Access xAPI Resources. |
AUTHORIZE_STATEMENT | Act as the authority of a Statement. Implies USE_XAPI |
USE_VERBS | Use the Verbs Extension Resource. |
MANAGE_USERS | Use the Users Extension Resource. |
§Roles
Similar to Permissions, the Roles in play here are very limited and discrete:
-
Guest- A Role that does not entitle its holder to any permission. -
User- This Role entitles its User holder to theUSE_XAPIpermission. -
AuthUser- AUserthat additionally can also act as a Statementauthority. It entitles its holder to both theAUTHORIZE_STATEMENTandUSE_XAPIpermissions. -
Admin- A team leader looking after a group of users. Its holders enjoyMANAGE_USERSandUSE_VERBSbut notUSE_XAPIorAUTHORIZE_STATEMENTpermissions. Note also that while Verbs are accessible and managed by all Users with theAdminRole, Users are tied to the concreteAdminUser that manage them. -
Root- This Role entitles its holder to all the permissions. There should be only two Users assigned this Role: a Test User to run the tests while in development, and a Root User that is materialized when the server is run in release; e.g. in production and when running against the CTS.
§One User, one Role
With such a simple and straightforward system, the authorization check which in the general case consists of answering the question…
Does the authenticated User have a Role that entitles them to the Permission(s) required for the requested Route?
is now reduced to a much simpler one…
Does the authenticated
Userhave aRolethat gives them the required Permission?
As an example, when handling a request for any xAPI Resource the authorization system needs only to check if the authenticated User has a User or AuthUser Role 1. However if when processing statements, it turns out that a Statement is lacking an authority, then if the User does not have the AuthUser Role but only a User one, then the request should fail.
§What a Role holder can and cannot do?
§Every Role except Root
- Can modify their own email and password. Note that because we only store a hashed version of the credentials, when modifying either one of those two properties, both must be provided.
§Root
- Can do everything except changing their own properties. In other words Root properties are immutable.
- Can create new users.
- Can toggle user’s enabled flag.
- Can change user’s role up to Admin.
- Can re-assign a user’s manager.
- Can use Verbs Extension.
§Admin
- Can create new users with either User or AuthUser role. Those new users will have that Admin as their manager.
- Can fetch their users.
- Can toggle their users enabled flag.
- Can toggle their user’s role between User and AuthUser.
- Can use Verbs Extension.
- Cannot use xAPI resources.
§AuthUser
- Can use xAPI resources,
- Can act as
authorityfor Statements if required.
§User
- Can use xAPI resources,
- Cannot act as Statements
authority.
§Forms
§CreateForm
A Form to use when creating new users. Its definition for the role field
relies on the Rocket built-in validation annotation ensuring that it’s between
0 (Guest) and 3 (Admin) inclusive.
#[derive(Debug, FromForm)]
struct CreateForm<'a> {
email: &'a str,
password: &'a str,
/// Even root cannot create a user w/ Root (4) role!
#[field(validate = range(0..4))]
role: u16,
}§UpdateForm
A Form to use when updating a single user. Only enabled, (email and password
pair), role or manager_id should be provided. A Rocket built-in annotation
ensures the manager_id property is recognized when its camel-case form is used.
#[derive(Debug, FromForm)]
struct UpdateForm<'a> {
enabled: Option<bool>,
email: Option<&'a str>,
password: Option<&'a str>,
role: Option<RoleUI>,
#[field(name = uncased("managerId"))]
manager_id: Option<i32>,
}
#[derive(Debug, FromForm)]
#[field(validate = range(0..4))]
struct RoleUI(u16);§BatchUpdateForm
A Form to use when updating multiple Users. Only enabled, role or
manager_id should be provided.
#[derive(Debug, FromForm)]
struct BatchUpdateForm {
/// Array of targeted user IDs.
ids: Vec<i32>,
enabled: Option<bool>,
role: Option<RoleUI>,
#[field(name = uncased("managerId"))]
manager_id: Option<i32>,
}§Handlers
§Create new User (POST /)
Allow creating new users. As explained earlier, only Root and Admin can invoke this action.
Newly created users…
- are enabled by default,
- are assigned the authenticated User that submitted the request as their manager.
Body: Valid URL-encoded CreateForm.
Status codes:
- 200 OK - User created.
- 400 Bad Request - The form is empty, or invalid.
- 401 Unauthorized - Requesting User is not authenticated.
- 403 Forbidden - Authenticated requesting User is not allowed to make this call.
- 500 Internal Server Error - An unexpected error occurred.
Response: When successful, will consist of the JSON representation of the newly created User instance. It will also include the ETag and Last-Modified headers.
§Update User (PUT /<id>)
Everybody, except Root, is allowed to modify their email, password or both. Because LaRS does not store plain user passwords, a user needs to always provide both properties even when wanting to change only one.
Only Root and Admin can toggle enabled flag. Admin can do that only for users they manager while Root can do that for everybody –except themselves.
Similarly Root and Admin are allowed to modify a user role. Admin does that only for the users they manager and only to one of User and AuthUser. Root can change the role for anybody –except themselves– to a different one, up to Admin.
Finally only Root can re-assign a user manager.
id: An existing User ID.
Body: Valid URL-encoded UpdateForm.
IMPORTANT - This call is subject to the same xAPI Concurrency Control requirements.
Status codes:
- 200 OK - User updated.
- 400 Bad Request - The form is empty, or invalid.
- 401 Unauthorized - Requesting User is not authenticated.
- 403 Forbidden - Authenticated requesting User is not allowed to make this call.
- 404 Not Found - Unknown target User.
- 409 Conflict - If-Match, or If-None-Match pre-conditions were not included in the request.
- 412 Precondition Failed - The ETag of the existing User fails the If-Match, If-None-Match pre-conditions.
- 500 Internal Server Error - An unexpected error occurred.
Response: When successful, will consist of the JSON representation of the newly modified User instance. It will also include the (updated) ETag and Last-Modified headers.
§Update multiple Users (PUT /)
It is envisaged that a somewhat sophisticated front-end would benefit from the ability to apply an action on multiple users at the same. For example, re-assigning a set of users to a different Admin, or disabling a set of users managed by the same Admin or not, etc…
This method/route allows Root and Admin to do just that. When the requesting authenticated user is an Admin only users managed by them can be targeted with the same constraints and limitations as when targeting a single user.
Body: Valid URL-encoded BatchUpdateForm.
Status codes:
- 200 OK - User(s) updated.
- 400 Bad Request - The form is empty, or invalid.
- 401 Unauthorized - Requesting User is not authenticated.
- 403 Forbidden - Authenticated requesting User is not allowed to make this call.
- 500 Internal Server Error - An unexpected error occurred.
§Fetch a User (GET /<id>)
Only Root and Admin can fetch the details of a User. Again, when the requesting authenticated user is an Admin the targeted user must be one managed by that Admin.
id: An existing User ID.
Status codes:
- 200 OK.
- 400 Bad Request - Invalid parameter(s).
- 401 Unauthorized - Requesting User is not authenticated.
- 403 Forbidden - Authenticated requesting User is not allowed to make this call.
- 404 Not Found - Unknown target User.
- 500 Internal Server Error - An unexpected error occurred.
Response: When successful, will consist of the JSON representation of the requested User instance. It will also include the ETag and Last-Modified headers.
§Get all Users (GET /)
Only Root and Admin can invoke this action. When it’s an Admin only the users managed by that Admin are selected.
Status codes:
- 200 OK.
- 400 Bad Request - Invalid parameter(s).
- 401 Unauthorized - Requesting User is not authenticated.
- 403 Forbidden - Authenticated requesting User is not allowed to make this call.
- 500 Internal Server Error - An unexpected error occurred.
Response: When successful, will consist of the JSON representation of a potentially empty array of (the selected) User IDs.
I intentionally didn’t mention the trivial case of the User having the Root Role. ↩