Managing user role assignments

From DreamFactory
Jump to: navigation, search
DreamFactoryTutorialsManaging user role assignments

The purpose of roles is to control what services non-admin users have access to for a given app. Each user can be assigned a single role per app. This is normally done from the Users tab of the DreamFactory admin console but it can also be done via the API. If no assignment is made the user inherits the default role for the app, which can be set from the Apps tab in the admin console. To use the API to create and manage roles you must be an admin user or a user with a role that allows API access to /system/role. When operating on more than one record, the default behavior is to stop after the first error. Any successes before the error will be committed to the database. To keep going with best effort for all records set the query param continue=true. To revert all changes on any error set the query param rollback=true.

Create (POST)

Details on the relationships role_service_access_by_role_id, user_to_app_to_role_by_role_id, and role_lookup_by_role_id are included after these CRUD examples.

POST /api/v2/system/role
{
	"resource": [{
		"name": "Db Role",
		"description": "Allows full access to db service with id=5. Assign user 10 this role for app 17.",
		"is_active": true,
		"role_service_access_by_role_id": [{
			"verb_mask": 31,
			"requestor_mask": 3,
			"component": "*",
			"service_id": 5
		}],
		"user_to_app_to_role_by_role_id": [{
			"user_id": 10,
			"app_id": 17
		}],
		"role_lookup_by_role_id": [{
			"name": "group",
			"value": "sales"
		}]
	}]
}

Retrieve (GET)

GET /api/v2/system/role/27?related=role_service_access_by_role_id,user_to_app_to_role_by_role_id,role_lookup_by_role_id

OR

GET /api/v2/system/role?ids=27,28

OR

GET /api/v2/system/role
{
	"resource": [{
		"id": 27,
	},
	{
		"id": 28,
	}]
}

Update (PATCH/PUT)

PATCH /api/v2/system/role/27
{
	{
		"is_active": false,
	}
}

OR

PATCH /api/v2/system/role?ids=27,28
{
	"resource": [{
		"is_active": false
	}]
}

OR

PATCH /api/v2/system/role
{
	"resource": [{
		"id": 27,
		"is_active": false
	},
	{
		"id": 28,
		"is_active": false
	}]
}


Delete (DELETE)

DELETE /api/v2/system/role/27

OR

DELETE /api/v2/system/role?ids=27,28

OR

DELETE /api/v2/system/role
{
	"resource": [{
		"id": 27,
	},
	{
		"id": 28,
	}]
}


WARNING: It is currently not possible to use the API to delete a role's service mapping by supplementing a Role deletion call with the related=role_service_access_by_role_id parameter and an associated JSON body. Any call to DELETE /api/v2/system/role/{id} *will result* in the deletion of said role and all associated service-to-role mappings. Please use the web-based administration console to delete any undesired service mappings for a given role. Otherwise, you can issue a PUT method to modify the role's service mappings.

Role and Role Service Access Details

Let's look closer at what makes up a role.

name is the display name for the role.

description is the text description for the role.

is_active enables and disables the role. Users assigned to an inactive role can't log in.

role_service_access_by_role_id is an array of service/component pairs the role allows access to.

verb_mask defines a bit mask for which verbs are allowed on each service/component pair.

GET: 1,
POST: 2,
PUT: 4,
PATCH: 8,
DELETE: 16

For example to select GET and PATCH, set verb_mask to 1 + 8 = 9. To allow all verbs, set verb_mask to 31.

requestor_mask defines a bit mask for allowing API and/or script access on each service/component pair. Script access means that a script service or event script can use platform.api calls to access the selected service/component.

API: 1,
SCRIPT: 2

1 = API access only
2 = Script access only
3 = API and script access

To allow both API and script access to the selected service/component, set requestor_mask to 3.

service_id is the database id for the service for this role service access entry.

component is the portion of the service this entry allows access to. You can use the component to fine tune the role access.

user_to_app_to_role_by_role_id defines which user/app this role applies to. Each user can have a unique role for each app.

role_lookup_by_role_id defines name/value pairs that apply to this role.

We'll focus on role service access using a service named mysql. Here are some detailed examples showing common combinations of service and components.

No Components

Component = (empty string)
Allows access at the root service level to return a list of resources for the service. GET is the only applicable verb.

GET /mysql

{
	"resource": [
		{
			"name": "_schema"
		},
		{
			"name": "_table"
		},
		{
			"name": "_proc"
		},
		{
			"name": "_func"
		}
	]
}

All Components

Component = *
Allows access to all resources for the service including schemas, tables (records), stored procedures, and functions. This setting should be used with care.

Table Components

To access specific tables from the API there are three levels of role service access to deal with.

Component = _table/
Allow listing of all tables available to API

Component = _table/*
Make all tables available to API

Component = _table/todo
Make a single table named 'todo' available to API

The second two control which tables are available to the API, while the first one controls whether or not a list of available tables can be retrieved. You can allow API access to tables that can't be listed, but you can't list tables that have no API access.

With no role service access provisioned a list of available tables cannot be returned. A 403 error is returned.

GET /mysql/_table
{
	"error": {
		"code": 403,
		"context": null,
		"message": "Access Forbidden. You do not have enough privileges to access this resource.",
		"status_code": 403
	}
}

Similarly, none of the tables can be accessed.

GET /mysql/_table/todo
{
	"error": {
		"code": 403,
		"context": null,
		"message": "Access Forbidden. You do not have enough privileges to access this resource.",
		"status_code": 403
	}
}

To allow a listing of available tables you must allow access to mysql/_table.

Component = _table/

GET /mysql/_table
{
	"resource": []
}

It returns an empty array because you haven't made any tables accessible yet.

To allow a table to be accessible you must allow access to all tables or that specific table.

Component = _table/*
Allow access to all tables.

Component = _table/todo
Allow access to a single table.

Now the todo table is accessible.

GET mysql/_table/todo
{
	"resource": [
		{
			"id": 1,
			"name": "todo #1",
			"complete": false,
			"updated": null
		}
	]
}

With access and listing allowed you can now see todo in the list (or all tables if you specified _table/* rather than _table/todo)

GET /mysql/_table

{
	"resource": [
		{
			"name": "todo"
		}
	]
}

You can also allow access only to a single record in a table.

Component = _table/todo/*

GET mysql/table/todo/1

{
	"id": 1,
	"name": "todo #1",
	"complete": true,
	"updated": null
}

Schema Components

_schema behaves in a similar manner to _table but deals with database schema rather than records. You can also POST to _schema/ to create or modify tables.

Component = _schema/
Component = _schema/todo

GET /mysql/_schema

{
	"resource": [
		{
			"name": "todo"
		}
	]
}
GET /mysql/_schema/todo

{
	"alias": null,
	"name": "todo",
	"label": "Todo",
	"description": null,
	"native": [],
	"plural": "Todos",
	"is_view": false,
	"primary_key": "id",
	"name_field": null,
	"field": [
		{
			"alias": null,
			"name": "id",
			"label": "Id",
			"description": null,
			"native": [],
			"type": "id",
			"db_type": "int(11)",
			"length": 11,
			"precision": null,
			"scale": null,
			"default": null,
			"required": false,
			"allow_null": false,
			"fixed_length": false,
			"supports_multibyte": false,
			"auto_increment": true,
			"is_primary_key": true,
			"is_unique": false,
			"is_index": false,
			"is_foreign_key": false,
			"ref_table": null,
			"ref_field": null,
			"ref_on_update": null,
			"ref_on_delete": null,
			"picklist": null,
			"validation": null,
			"db_function": null,
			"is_virtual": false,
			"is_aggregate": false
		},
		{
			"alias": null,
			"name": "name",
			"label": "Name",
			"description": null,
			"native": [],
			"type": "string",
			"db_type": "varchar(80)",
			"length": 80,
			"precision": null,
			"scale": null,
			"default": null,
			"required": true,
			"allow_null": false,
			"fixed_length": false,
			"supports_multibyte": true,
			"auto_increment": false,
			"is_primary_key": false,
			"is_unique": false,
			"is_index": false,
			"is_foreign_key": false,
			"ref_table": null,
			"ref_field": null,
			"ref_on_update": null,
			"ref_on_delete": null,
			"picklist": null,
			"validation": null,
			"db_function": null,
			"is_virtual": false,
			"is_aggregate": false
		},
		{
			"alias": null,
			"name": "complete",
			"label": "Complete",
			"description": null,
			"native": [],
			"type": "boolean",
			"db_type": "tinyint(1)",
			"length": 1,
			"precision": null,
			"scale": null,
			"default": null,
			"required": true,
			"allow_null": false,
			"fixed_length": false,
			"supports_multibyte": false,
			"auto_increment": false,
			"is_primary_key": false,
			"is_unique": false,
			"is_index": false,
			"is_foreign_key": false,
			"ref_table": null,
			"ref_field": null,
			"ref_on_update": null,
			"ref_on_delete": null,
			"picklist": null,
			"validation": null,
			"db_function": null,
			"is_virtual": false,
			"is_aggregate": false
		},
		{
			"alias": null,
			"name": "updated",
			"label": "Updated",
			"description": null,
			"native": [],
			"type": "boolean",
			"db_type": "tinyint(1)",
			"length": 1,
			"precision": null,
			"scale": null,
			"default": null,
			"required": false,
			"allow_null": true,
			"fixed_length": false,
			"supports_multibyte": false,
			"auto_increment": false,
			"is_primary_key": false,
			"is_unique": false,
			"is_index": false,
			"is_foreign_key": false,
			"ref_table": null,
			"ref_field": null,
			"ref_on_update": null,
			"ref_on_delete": null,
			"picklist": null,
			"validation": null,
			"db_function": null,
			"is_virtual": false,
			"is_aggregate": false
		}
	],
	"related": [],
	"access": 1
}

You can allow access to a single field in a schema, in this case the 'todo' schema. This allows for getting, creating, updating, and deleting fields.

Component = _schema/todo/*

GET mysql/_schema/todo/name

{
	"alias": null,
	"name": "name",
	"label": "Name",
	"description": null,
	"native": [],
	"type": "string",
	"db_type": "varchar(80)",
	"length": 80,
	"precision": null,
	"scale": null,
	"default": null,
	"required": true,
	"allow_null": false,
	"fixed_length": false,
	"supports_multibyte": true,
	"auto_increment": false,
	"is_primary_key": false,
	"is_unique": false,
	"is_index": false,
	"is_foreign_key": false,
	"ref_table": null,
	"ref_field": null,
	"ref_on_update": null,
	"ref_on_delete": null,
	"picklist": null,
	"validation": null,
	"db_function": null,
	"is_virtual": false,
	"is_aggregate": false
}

Stored Procedure and Functions Components

Configuring access to stored procedures and functions is essentially the same as _table and _schema. Consider a trivial stored procedure named 'findname'. It takes a single input param named 'str' and returns all 'todo' table records with matching name.

BEGIN
  SELECT * FROM todo
  WHERE name = str;
END

To access stored procs from the API there are three levels of role service access to deal with.

Component = _proc/
Allow listing of all procs that are available to be called.

Component = _proc/*
Make all procs available for calling.

Component = _proc/findname
Make a single proc named 'findname' available for calling.

The second two control which procs are available to be called, while the first one controls whether or not a list of available procs can be retrieved. You can call procs that can't be retrieved, but you can't retrieve procs that can't be called.

With no role service access provisioned a list of available procs cannot be returned. A 403 error is returned.

GET /mysql/_proc
{
	"error": {
		"code": 403,
		"context": null,
		"message": "Access Forbidden. You do not have enough privileges to access this resource.",
		"status_code": 403
	}
}

Similarly, none of the procs can be called.

GET /mysql/_proc/findname?str=Record%201
{
	"error": {
		"code": 403,
		"context": null,
		"message": "Access Forbidden. You do not have enough privileges to access this resource.",
		"status_code": 403
	}
}

To allow a listing of available procs you must allow access to mysql/_proc.

Component = _proc/

GET /mysql/_proc
{
	"resource": []
}

It returns an empty array because you haven't made any procs available to be called yet.

To allow a proc to be called you must allow access to all procs or that specific proc.

Component = _proc/*
Allow access to all procs.

Component = _proc/findname
Allow access to a specific proc.

Now the findname proc is available to be called.

GET /mysql/_proc/findname?str=Record%201
[
	{
		"id": 1,
		"name": "record 1",
		"complete": 0,
		"updated": null
	}
]

With access allowed for calling and listing you can now see findname in the list.

GET /mysql/_proc

{
	"resource": [
		{
			"name": "findname"
		}
	]
}

Assigning users to roles

To assign a role you have to include the relationship 'user_to_app_to_role_by_user_id'. As an example let's say we want to assign user id 100 with a certain role for a certain app. If the role id is 7 and the app id is 4, the following would create the required relationship to assign that role to that user for that app.

PUT /api/v2/system/user/100?related=user_to_app_to_role_by_user_id  
{
	"user_to_app_to_role_by_user_id": [{
		"app_id": "4",
		"role_id": 7,
		"user_id": 100
	}]
}

To retrieve the role assignments, do a GET with relationship 'user_to_app_to_role_by_user_id'. The last one is the one we just created.

GET /api/v2/system/user/100?related=user_to_app_to_role_by_user_id  
{
	"id": 100,
	"user_to_app_to_role_by_user_id": [
		{
			"id": 23,
			"user_id": 100,
			"app_id": 1,
			"role_id": 7
		},
		{
			"id": 24,
			"user_id": 100,
			"app_id": 2,
			"role_id": 7
		},
		{
			"id": 25,
			"user_id": 100,
			"app_id": 3,
			"role_id": 7
		},
		{
			"id": 26,
			"user_id": 100,
			"app_id": 4,
			"role_id": 7
		}
	]
}

To modify a role assignment, do a PUT with relationship 'user_to_app_to_role_by_user_id'. This example changes the role id from 7 to 8, which changes the user's role for that app only.

IMPORTANT: You must provide the user_id for the assignments you are updating, otherwise they will be deleted.

PUT /api/v2/system/user/100?related=user_to_app_to_role_by_user_id  
{
	"user_to_app_to_role_by_user_id": [
		{
			"id": 23,
			"user_id": 100,
			"app_id": 1,
			"role_id": 7
		},
		{
			"id": 24,
			"user_id": 100,
			"app_id": 2,
			"role_id": 7
		},
		{
			"id": 25,
			"user_id": 100,
			"app_id": 3,
			"role_id": 7
		},
		{
			"id": 26,
			"user_id": 100,
			"app_id": 4,
			"role_id": 8
		}
	]
}

To delete a role assignment, do a PUT with relationship 'user_to_app_to_role_by_user_id'. Set the user_id field to null for the one you wish to delete. After deletion, the user will inherit the default role for that app.

PUT /api/v2/system/user/100?related=user_to_app_to_role_by_user_id  
{
	"user_to_app_to_role_by_user_id": [
		{
			"id": 23,
			"user_id": 100,
			"app_id": 1,
			"role_id": 7
		},
		{
			"id": 24,
			"user_id": 100,
			"app_id": 2,
			"role_id": 7
		},
		{
			"id": 25,
			"user_id": 100,
			"app_id": 3,
			"role_id": 7
		},
		{
			"id": 26,
			"user_id": null,
			"app_id": 4,
			"role_id": 8
		}
	]
}

These examples are for a single user, but multiple role assignments can be done on multiple users, all in the same call.