In this tutorial, we'll see how to use data policies and roles to set up scope-specific access controls for properties in LUSID.
Let's imagine we have a person entity that is configured with the properties Person/Details/Country
and Person/SensitiveDetails/Address
. We want to set up access controls that:
Allow most users to view
Person/Details/Country
and other properties within thePerson/Details/*
scope.Allow a small number of privileged users to view
Person/SensitiveDetails/Address
and other properties within thePerson/SensitiveDetails/*
scope in addition to properties in thePerson/Details/*
scope.
To do this, we need to create and assign two different roles to our users, one role containing a feature and data policy that allows users to make requests to the GetPersonPropertyTimeSeries
API and read Person/Details/*
property definitions and property values data, and the other role containing a data policy that allows read-only access to Person/SensitiveDetails/*
property definitions and property values data.
Note: To complete this tutorial, you must have suitable access control permissions. This can most easily be achieved by assigning your LUSID user the built-in
lusid-administrator
role, which should already be the case if you are the domain owner. If you are informed you do not have a license to perform a certain operation, contact support.
Prerequisite: Setting up property definitions and person entities
Creating property definitions
For our scenario, we first need ensure we have the following property definitions:
Person/Details/Country
Person/SensitiveDetails/BankAccount
To create Person/Details/Country
, we can call the CreatePropertyDefinition API, passing in the following:
curl -X POST "https://<your-domain>.lusid.com/api/api/propertydefinitions"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json-patch+json"
-d "{
"domain": "Person",
"scope": "Details",
"code": "Country",
"displayName": "Country trader operates in",
"dataTypeId": {"scope": "system", "code": "string"},
"lifeTime": "TimeVariant",
"constraintStyle": "Property",
"propertyDescription": "Country trader operates in"
}"
To create Person/SensitiveDetails/Address
, we can call the CreatePropertyDefinition API again, passing in the following:
curl -X POST "https://<your-domain>.lusid.com/api/api/propertydefinitions"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json"
-d "{
"domain": "Person",
"scope": "SensitiveDetails",
"code": "Address",
"displayName": "Trader home address",
"dataTypeId": {"scope": "system", "code": "string"},
"lifeTime": "TimeVariant",
"constraintStyle": "Property",
"propertyDescription": "Home address of trader"
}"
Read more on creating a property type.
Creating a person entity
We also need to ensure we have a person entity that has been decorated with the above properties. For this tutorial, let's create a trader person entity to decorate with some properties. To do this:
Create a property definition for a person entity identifier. The following call creates a property definition with a 3-stage property key of
Person/Traders/TraderId
:curl -X POST "https://<your-domain>.lusid.com/api/api/propertydefinitions" -H "Content-Type: application/json-patch+json" -H "Authorization: Bearer <your-API-access-token>" -d "{ "domain": "Person", "scope": "Traders", "code": "TraderId", "valueRequired": true, "displayName": "Trader identifier for Person entities", "dataTypeId": { "scope": "system", "code": "string" }, "lifeTime": "Perpetual", "constraintStyle": "Identifier", }"
Call the UpsertPerson API to create a person entity. Note the
labelValue
you give the trader identifier must be unique among all traders represented in LUSID, in this caseTrader5
.curl -X POST "https://<your-domain>.lusid.com/api/api/persons" -H "Content-Type: application/json-patch+json" -H "Authorization: Bearer <your-API-access-token>" -d "{ "identifiers": { "Person/Traders/TraderId": { "key": "Person/Traders/TraderId", "value": { "labelValue": "Trader5" } } }, "displayName": "John Doe", "description": "A portfolio manager and trader in US investments", "properties": { "Person/Details/Country": { "key": "Person/Details/Country", "value": { "labelValue": "USA" } }, "Person/SensitiveDetails/Address": { "key": "Person/SensitiveDetails/Address", "value": { "labelValue": "123 New Lane, New York" }, "effectiveFrom": "2016-07-01T00:00:00.0000000+00:00" } } }"
Read more on creating person entities.
Step 1: Creating a data and feature policy for unprivileged and privileged users
You can create policies to control access to the following actions for properties:
Read
Update
Delete
Any (all of the above)
For this tutorial, we will focus on controlling read access to various datapoints, but the same steps can be followed to control access for all of the actions above.
Creating a policy for a less privileged user
We first need to create a policy that allows a user to do all of the following, but not read Person/SensitiveDetails/*
:
Grants access to the following LUSID features:
GetPerson
APIGetPersonPropertyTimeSeries
API
Grants read-only access to the following data points:
All person entities.
Property definitions and property values under the scope
Person/Traders/*
.Property definitions and property values under the scope
Person/Details/*
.
To create a policy for all of the above:
Sign into the LUSID web app and use the left-hand menu to navigate to Identity and Access > Policies:
Select Create policy and choose JSON editor:
Specify the following JSON, containing:
A
code
that uniquely identifies the policy; we'll call our policyallow-read-non-sensitive-person-properties
.A friendly
description
for the policy.Any
applications
the policy relates to; for this tutorial, our policy relates only toLUSID
.Whether this policy will
Allow
orDeny
access to the specified features and data undergrant
.Within the
selectors
parameter, specify the following for our required feature policies:Optionally, a
name
anddescription
to help identify the selector.An
identifier
for each feature, containing:A
scope
value ofLUSID
.A
code
value ofapi-persons-<endpoint>
.
actions
for the policy, including:The
scope
valueLUSID
andentity
value offeature
.The
activity
we want to grant access to; for this tutorial we want to allow policy holders toexecute
the endpoints.
For our required data policies, specify the following:
An
identifier
for each person entity or property we want to grant read-only access to, containing:Optionally, a
name
anddescription
to help identify the selector.The
domain
for a property orpersonCode
for a person entity.The
scope
andcode
of the property or person entity.
A
when
parameter to further control access to these features and data, by applying a datetime the policy canactivate
from and, optionally, when it willdeactivate
and no longer provide any access.
{ "code": "allow-read-non-sensitive-person-properties", "description": "Allows read-only access to Person entity Details properties", "applications": [ "LUSID" ], "grant": "Allow", "selectors": [ { "idSelectorDefinition": { "name": "Run GetPerson", "description": "Run the GetPerson API endpoint", "identifier": { "code": "api-persons-getperson", "scope": "LUSID" }, "actions": [ { "scope": "LUSID", "activity": "Execute", "entity": "Feature" } ] } }, { "idSelectorDefinition": { "name": "Run GetPersonPropertyTimeSeries", "description": "Run the GetPersonPropertyTimeSeries API endpoint", "identifier": { "code": "api-persons-getpersonpropertytimeseries", "scope": "LUSID" }, "actions": [ { "scope": "LUSID", "activity": "Execute", "entity": "Feature" } ] } }, { "idSelectorDefinition": { "name": "Read person entities", "description": "Read all person entities", "identifier": { "code": "*", "scope": "*", "personcode": "*" }, "actions": [ { "scope": "default", "activity": "Read", "entity": "Person" } ] } }, { "idSelectorDefinition": { "name": "Read Person/Traders/*", "description": "Read Person/Traders/* property definition and values", "identifier": { "code": "*", "scope": "Traders", "domain": "Person" }, "actions": [ { "scope": "default", "activity": "Read", "entity": "PropertyDefinition" }, { "scope": "default", "activity": "Read", "entity": "PropertyValue" } ] } }, { "idSelectorDefinition": { "name": "Read Person/Details/*", "description": "Read Person/Details/* property definition and values", "identifier": { "code": "*", "scope": "Details", "domain": "Person" }, "actions": [ { "scope": "default", "activity": "Read", "entity": "PropertyDefinition" }, { "scope": "default", "activity": "Read", "entity": "PropertyValue" } ] } } ], "when": { "activate": "2024-03-06T00:00:00.0000000+00:00", "deactivate": "9999-12-31T23:59:59.9999999+00:00" } }
Select Create to save your new policy.
Creating a policy for a privileged user
We need to create a separate policy that allows a user to read property definitions and values under the scope Person/SensitiveDetails/*
.
To create this policy, we can follow the previous steps to create a policy via the LUSID web app, this time passing in the following:
{ "code": "allow-read-sensitive-details-person-properties",
"description": "Grants read access to Person/SensitiveDetails/* property definitions and values",
"applications": [ "LUSID" ],
"grant": "Allow",
"selectors": [ {
"idSelectorDefinition": {
"identifier": {
"code": "*",
"scope": "SensitiveDetails",
"domain": "Person"
},
"actions": [ {
"scope": "default",
"activity": "Read",
"entity": "PropertyDefinition"
}, {
"scope": "default",
"activity": "Read",
"entity": "PropertyValue"
} ] } } ],
"when": { "activate": "2024-03-05T00:00:00.0000000+00:00",
"deactivate": "9999-12-31T23:59:59.9999999+00:00" } }
Once created, we can view the policies in the Policies dashboard:
Step 2: Creating roles for unprivileged and privileged users
Now that we have created the required policies, we need to assign the policies to different roles which we can then assign to each user. For our tutorial, we need the following roles:
unprivileged-user
assigned the policyallow-read-non-sensitive-person-properties
.privileged-user
assigned the policiesallow-read-non-sensitive-person-properties
andallow-read-sensitive-details-person-properties
.
The simplest way to create a role is via the LUSID web app; to do this:
Sign into the LUSID web app and use the left-hand menu to navigate to Identity and Access > Roles:
Let's first create our unprivileged role:
Select Create role and specify the following:
A Code to uniquely identify the role; we can use
unprivileged-user
for our first role.A friendly Description for the role.
An Activation date and, optionally, Deactivation date for the role. For our tutorial, we want the role to be active immediately, so we can specify the current date.
Use the Policies > Choose dropdown to assign policies to the role. For our unprivileged role, we need to add the
allow-read-non-sensitive-person-properties
policy.
Select Create to create the role.
Now let's create our more privileged role:
Select Create role and specify the following:
A Code to uniquely identify the role; we can use
privileged-user
for this role.A friendly Description for the role.
An Activation date and, optionally, Deactivation date for the role. For our tutorial, we want the role to be active immediately, so we can specify the current date.
Use the Policies > Choose dropdown to assign policies to the role. For our privileged role, we need to add both the
allow-read-non-sensitive-person-properties
andallow-read-sensitive-details-person-properties
policies.
Select Create to create the role.
Note: Depending on other existing roles and policies in your domain, you may need to set a precedence for each role to determine which policies take effect in the event of a conflict. Read more on setting precedence for a role here.
Step 3: Assigning roles to users and seeing the access controls in action
Once we have created the roles, we can assign them to our desired users via the LUSID web app to enforce who can view properties containing sensitive data. To do this:
Sign into the LUSID web app use the left-hand menu to navigate to Identity and Access > Users.
Locate a user to assign a role to and select Edit.
Click Add roles and select the
unprivileged-user
role we made earlier.
Click Save to add the role to the user.
To create a privileged user, repeat steps 2-4, additionally assigning the
privileged-user
role to the user.
Once a user is assigned the unprivileged-user
role, we can see the policy in action by having the user call the GetPersonPropertyTimeSeries API, passing in the following:
curl -X GET
"https://<your-domain>.lusid.com/api/api/persons/Traders/TraderId/Trader5/properties/time-series?propertyKey=Person%2FDetails%2FCountry"
-H "Authorization: Bearer <your-API-access-token>"
Part of the response is as follows; the unprivileged user can access the non-sensitive data:
{
"values": [
{
"value": {
"labelValue": "USA"
},
...
However, if the unprivileged user attempts to view properties under the Person/SensitiveDetails/*
scope by passing in the following:
curl -X GET
"https://<your-domain>.lusid.com/api/api/persons/Traders/TraderId/Trader5/properties/time-series?propertyKey=Person%2FSensitiveDetails%2FAddress"
-H "Authorization: Bearer <your-API-access-token>"
The request fails with part of the response as follows:
{
"name": "AccessDenied",
"title": "Access cannot be granted",
"status": 403,
"detail": "Access to perform the requested action cannot be granted due to insufficient privileges. Please contact your organisation's administrator.",
"instance": "https://<your-domain>.lusid.com/app/insights/logs/0HN1UO1IR6S4U:00000131",
...
}
We can follow the instance
URL and look under the Access logs tab to understand which policy is required to gain access to such data:
If our privileged user attempts the same API call, the user can read the sensitive data; part of the response is as follows:
{
"values": [
{
"value": {
"labelValue": "123 New Lane, New York"
},
...