The Ionic Platform allows developers to add high-value security to their application. With no background in cryptography, you can quickly and confidently add data protection and management to your application.
The Ionic Admin SDK enables developers to manage users, groups, data markings and data access policies in their tenant. Currently supports SCIM, Markings and Policies APIs.
This library hasn't been published on npm yet and needs to be installed from GitHub:
npm i https://github.com/VirgilSecurity/ionic-admin-nodejs/releases/download/v0.2.2/ionic-admin-sdk-0.2.2.tgz
All of the APIs are available from the instance of the IonicApiClient
class, which must be instantiated with your Ionic API base URL, your Tenant ID and authentication options:
const { IonicApiClient } = require('ionic-admin-sdk');
const client = new IonicApiClient({
baseUrl: 'https://api.ionic.com',
tenantId: '<YOUR_TENANT_ID>',
auth: { }, // see "Authentication" section below
});
Ionic Management API requires authentication. In order to use API authentication, an Ionic Administrator account must be created. This account must have access to the API, via the scope access:api
. Further authorization is required for each operation. For example, if making a request to create a new policy, the user must be assigned to a role that grants permission to create new policies, via the scope policies:create
.
There are three supported authentication schemes:
- Bearer or OAuth 2.0 Authentication
- MAC Authentication
- Basic Authentication
In order to use Bearer authentication, an administrator must first create an API Key. Once created, a "Secret Token" is provided and should be copied and stored securely. The "Secret Token" must be securely provided (i.e. via environment variable) as auth.secretToken
option to the IonicApiClient
constructor along with auth.type
option equal to 'bearer'
:
const { IonicApiClient } = require('ionic-admin-sdk');
const client = new IonicApiClient({
baseUrl: 'https://api.ionic.com',
tenantId: '<YOUR_TENANT_ID>',
auth: {
type: 'bearer',
secretToken: process.env.IONIC_API_SECRET_TOKEN,
},
});
For MAC authentication, an API Key must be created. Once created, an "ID" and "Secret" are provided and should be copied and stored securely. These are provided as auth.apiKeyId
and auth.apiKeySecret
options to the IonicApiClient
constructor along with auth.type
option equal to 'mac'
:
const { IonicApiClient } = require('ionic-admin-sdk');
const client = new IonicApiClient({
baseUrl: 'https://api.ionic.com',
tenantId: '<YOUR_TENANT_ID>',
auth: {
type: 'mac',
apiKeyId: process.env.IONIC_API_KEY_ID,
apiKeySecret: process.env.IONIC_API_KEY_SECRET,
},
});
To use basic authentication, you must create an API Access Account. You can create an API Access Account in the Ionic Administrator Console. This is done by creating a user with the "API Administrator" role. The username and password must be provided as auth.username
and auth.password
options to the IonicApiClient
constructor along with auth.type
option equal to 'basic'
:
const { IonicApiClient } = require('ionic-admin-sdk');
const client = new IonicApiClient({
baseUrl: 'https://api.ionic.com',
tenantId: '<YOUR_TENANT_ID>',
auth: {
type: 'basic',
username: process.env.IONIC_USERNAME,
password: process.env.IONIC_PASSWORD,
},
});
The SCIM API enables users to programmatically manage users registered in the Ionic Platform. These are available via scim
property of the IonicApiClient
instance.
All of the API endpoints, except the Bulk Operations are supported.
Each supported endpoint corresponds to a method, e.g. scim.listScopes
, with the method name being the endpoint name in camelCase. One exception to that are "Update Group PUT" and "Update Group PATCH" endpoints, which correspond to scim.updateGroup
and scim.patchGroup
methods respectively.
All of the methods accept and return JSON in the same format as the API endpoints. Methods corresponding to endpoints with the resource ID in the URL (e.g. Fetch User), accept the ID as the first parameter.
If an endpoint accepts query parameters, the corresponding method accepts an object with property names corresponding to query parameter names as the last argument:
const user = await client.scim.fetchUser(userId, { attributes: ['name', 'email', 'groups' ] });
Create User
const user = await client.scim.createUser({
schemas: [client.scim.Schemas.Core, client.scim.Schemas.Ionic],
name: { givenName: 'John', familyName: 'Doe' },
email: [{ value: 'jdoe@example.com' }],
[client.scim.Schemas.Ionic]: {
domainUpn: 'jdoe@example.com',
sendEmail: false,
groups: [{ type: 'group', value: groupId }]
}
});
console.log(user.id); // e.g. 5890d3baf8ab7b0291acd1fc
List Users
const params = { skip: 1, limit: 10 };
const result = await client.scim.listUsers(params);
console.log(result.Resources.length); // <= 10
Fetch User
const user = await client.scim.fetchUser(userId);
Update User
const updateData = { schemas: [client.scim.Schemas.Core], name: { familyName: 'Anderson' } };
const updatedUser = await client.scim.updateUser(userId, updateData);
Delete User
await client.scim.deleteUser(userId);
Update Device
const disabledDevice = await client.scim.updateDevice(deviceId, {
schemas: [client.scim.Schemas.Core],
status: false
});
console.log(disableDevice.status); // false
List Groups
const groupList = await client.scim.listGroups();
console.log(groupList.totalResults); // e.g. 5
Update Group PATCH
const patchData: GroupPatchData = {
schemas: [client.scim.Schemas.Core, client.scim.Schemas.Ionic],
members: [
{
value: '777777777777777777',
operation: 'delete',
},
{
value: '888888888888888888',
},
],
displayName: 'NewDisplayName',
};
// when "attributes" query parameter is not specified returns nothing
const result = await scim.patchGroup(groupId, patchData);
console.log(result); // undefined
// or
// when "attributes" is specified returns the updated group
const updatedGroup = client.scim.patchGroup(groupId, patchData, { attributes: ['displayName', 'members'] });
console.log(updatedGroup.id);
Delete Role
await client.scim.deleteRole(roleId);
Data Markings allow developers to associate attributes to the keys that protect application data. These attributes can be used for both data access policy purposes and for analytics purposes. This API is avalable via dataMarkings
property of the IonicApiClient
instance.
All of the API endpoints are supported. Each endpoint corresponds to a method on the client.dataMarkings
object, e.g. client.dataMarkings.listMarkings
, with the method name being the endpoint name in camelCase.
All of the methods accept and return JSON in the same format as the API endpoints. Methods corresponding to endpoints with the resource ID in the URL (e.g. Fetch Data Marking), accept the ID as the first parameter.
If an endpoint accepts query parameters, the corresponding method accepts an object with property names corresponding to query parameter names as the last argument:
const marking = await client.dataMarkings.fetchMarking(markingId, { valueLimit: 10 });
console.log(marking.detail.values.length); // <= 10
Create Data Marking
const dataMarking = await client.dataMarkings.createMarking({ name: 'custom-marking' });
console.log(dataMarking.id); // e.g. 533333333333333333333333
List Data Markings
const dataMarkingList = await client.dataMarkings.listMarkings({ valueLimit: 10 });
console.log(dataMarkingList.totalResults); // e.g. 10
Fetch Data Marking
const dataMarkingById = await client.dataMarkings.fetchMarking(markingId);
// or with "valueLimit" query parameter
// const dataMarkingById = await client.dataMarkings.fetchMarking(markingId, { valueLimit: 10 });
Create or Update Multiple Markings
const data = [{ name: 'CreatedName' }, { id: markingToUpdate.id, name: 'UpdatedName' }];
const result = await markings.createOrUpdateMarkings(data);
console.log(result.length); // 2
Create Marking Value
const markingValue = await client.dataMarkings.createValue(markingId, { name: 'custom-value' });
console.log(markingValue.id); // e.g. 555555555555555555555555
List Marking Values
const markingValueList = await client.dataMarkings.listValues(markingId);
console.log(markingValueList.totalResults); // e.g. 100
Data policies allow developers to create rules for how data can be accessed. The Policies API is used to administer policies for a tenant and is available via dataPolicies
property of the IonicApiClient
instance. Administrators can perform list, fetch, create, update, and delete actions on policies using these API methods.
All of the API endpoints are supported. Each endpoint corresponds to a method on the client.dataPolicies
object, e.g. client.dataPolicies.listPolicies
, with the method name being the endpoint name in camelCase. One exception to that is "Create or Update Multiple Policies" endpoint which corresponds to client.dataPolicies.createOrUpdatePolicies
method.
All of the methods accept and return JSON in the same format as the API endpoints. Methods corresponding to endpoints with the resource ID in the URL (e.g. Fetch Policy), accept the ID as the first parameter.
If an endpoint accepts query parameters, the corresponding method accepts an object with property names corresponding to query parameter names as the last argument:
const policies = await client.dataPolicies.createOrUpdatePolicies(data, { merge: 'true' });
Create Policy
const policy = await client.dataPolicies.createPolicy({ policyId: 'MyPolicy', description: 'All data' });
console.log(policy.id); // e.g. 66666666666666666666666
Fetch Policy
const policyById = await client.dataPolicies.fetchPolicy(policy.id);
console.log(polucyById.description); // All data
Update Policy
const updatedPolicy = await client.dataPolicies.updatePolicy(policy.id, { policyId: 'NewName' });
console.log(updatedPolicy.policyId); // NewName
Create or Update Multiple Policies
const data = [
{ id: policy.id, policyId: 'UpdatedPolicyName' },
{ policyId: 'NewPolicyName', description: 'All data' },
];
const result = await client.dataPolicies.createOrUpdatePolicies(data);
// or with the "merge" query parameter set to "true"
// const result = await client.dataPolicies.createOrUpdatePolicies(data, { merge: 'true' });
console.log(result.length); // 2
List Policies
const policyList = await client.dataPolicies.listPolicies();
console.log(policyList.totalResults); // e.g. 8
To all of the list*
methods (except for scim.listScopes
) parameters may be provided to control the number of results and apply some filtering criteria to the returned list.
Pagination
The following parameters may be used for pagination: startIndex
and count
or skip
and limit
. The limit
and count
parameters may be used interchangeable. The skip
and startIndex
parameters have an off-by-one relationship defined by startIndex=skip+1
.
const userList = await client.scim.listUsers({ startIndex: 11, count: 10 });
console.log(userList.Resources); // array of Users
const dataPolicyList = await client.dataPolicies.listPolicies({ skip: 10, limit: 10 });
console.log(dataPolicyList.Resources); // array of Data Markings
Output control
In SCIM API methods attributes
parameter can be used to specify the fields to include in the response:
const groupList = await client.scim.listGroups({
startIndex: 11,
count: 10,
attributes: ['name', 'members']
});
console.log(groupList.Resources); // array of Groups
The Data Markings API listMarkings
method accept a valueLimit
parameter to limit the number of values to include with each marking:
const dataMarkingList = await client.dataMarkings.listMarkings({ skip: 0, limit: 10, valueLimit: 5 });
console.log(dataMarkingList.Resources); // array of Markings
Search and Filter
Filter parameters may be passed into list*
methods as the filter
option. The list of attributes supported depends on the Endpoint being called and corresponds to the list defined in the appropriate endpoint documentation (e.g. List Users).
To control the way the value is matched, one or more filter operators may be scpecified. As opposed to the API, where the filter operators are appended to the field name, here they must be passed as an object with properties matching the names of suffixes defined in the API documentation:
const userList = await client.scim.listUsers({
filter: {
email: 'username@example.com', // no operator, the field will be matched exactly
domainUpn: { __contains: 'something' },
externalId: { __startswith: 'I.E.' },
groups: { __any: ['some_group_id'] },
roles: { __empty: true },
createdTs: { __gte: (Date.now() / 1000) - 600 }, // created in the last 10 minutes
}
});
Note: When more than one named parameter is used, they will be combined using an intersection ("and") query strategy.
When multiple filter parameters are provided and or: true
, results will include a union of records matching each parameter provided:
const groupList = await client.scim.listGroups({
filter: {
name: { __startswith: 'My' },
description: { __empty: false },
or: true
}
});
The __ne
filter operator may be used to return records that have an associated field value that does not match the provided value:
const groupList = await client.scim.listGroups({
filter: {
name: { __ne: 'MyGroup' },
}
});
This may also be combined with other operators to negate them:
const groupList = await client.scim.listGroups({
filter: {
name: { __contains: { __ne: 'Group' } },
}
});
Multiple operators for the same field may be specified:
const groupList = await client.scim.listGroups({
filter: {
createdTs: { __lte: (Date.now() / 1000) - 600, __gte: (Date.now() / 1000) - 300 },
}
});
When the server returns a response with the status code that falls out of range of 2xx, the Promise returned from the API method is rejected with an error object with the following structure:
{
name: 'IonicApiError',
message: 'Request failed with status code 400', // message explaining what went wrong in general terms
httpStatus: 400, // HTTP Status Code returned by the server
data: // Optional details about the error returned by the server
{
status: 400,
detail: { message: ['The schema is missing'] }, // array of messages describing the actual problem
source:
{
method: 'POST',
url: '/1234567890/scim/Users',
cid: '94860bab-7355-48d6-53bc-cf2e3ef412b1'
},
code: 30001,
error: 'SCHEMA_MISSING'
}
}
For constructing policies there is a helper function called createPolicy
available under the policies
namespace of the library. It accepts a hash with three properties - policyId, enabled and ruleCombiningAlgId and returns a PolicyBuilder
object with a fluent interface that allows for further configuration of the policy - setting the Target and Rules. As in the API policyId is the only required field. The description field will depend on the Target and will be generated automatically.
const { policies } = require('ionic-admin-sdk');
const policyBuilder = policies.createPolicy({
policyId: 'MyPolicy',
ruleCombiningAlgId: 'first-applicable'
});
To set the Target for the policy, two methods of the PolicyBuilder
can be used interchangeably - appliesToAllData
, which makes a policy that applies to all requests, and appliesTo
which allows to specify a condition under which the policy is applied. Both of these methods return the same instance of PolicyBuilder
you can change further method calls to:
Condition syntax will be explored below:
const policyBuilder = policies.createPolicy({ policyId: 'MyPolicy' }).appliesToAllData();
// or
const policyBuilder2 = policies.createPolicy({ policyId: 'MyPolicy' }).appliesTo({...});
After the Target is set, you can specify the Rules for the policy. There are four methods on the PolicyBuilder
to help with that:
alwaysAllow()
- Adds a rule with "Permit" effect, "Always allow." as description and no conditionalwaysDeny()
- Adds a rule with "Deny" effect, "Always deny." as description and no conditionallowIf(condition)
- Adds a rule with "Permit" effect, description computed fromcondition
and the condition itselfdenyIf(condition)
- Adds a rule with "Deny" effect, description computed fromcondition
and the condition itselfallowOtherwise()
- Adds a rule with "Permit" effect, "Allow otherwise." as description and no conditiondenyOtherwise()
- Adds a rule with "Deny" effect, "Deny otherwise." as description and no condition
You can see that the first two methods are semantically equivalent to the last two methods. They exist because they make for more readable code and rule descriptions
These methods also return the PolicyBuilder
instance:
const policyBuilder = policies.createPolicy({ policyId: 'MyPolicy' })
.appliesToAllData()
.allwaysAllow();
const policyBuilder2 = policies.createPolicy({ policyId: 'MyPolicy', ruleCombiningAlgId: 'first-applicable' })
.appliesToAllData()
.allowIf({...}) // condition syntax is explained below
.denyOtherwise();
Finally, when the Target and the Rules have been configured, the PolicyBuilder
's toJson
method can be used to get an object representation of the policy, that can be passed into the API method to create the Policy resource on the server:
const policyData = policies.createPolicy({ policyId: 'MyPolicy' })
.appliesToAllData()
.alwaysAllow()
.toJson();
const policy = await client.dataPolicies.createPolicy(policyData);
Condition construction
The construction of simple or complex conditions using the functions provided by the Ionic Policy Engine applied against the attributes available in a decision request is what makes XACML and the Ionic Policy Engine flexible and powerful.
All Functions (except for the Higher Order Bag functions) and all Key Request Attributes listed in the Ionic XACML Reference are available under the policies
namespace of the library.
Attributes are arranged as objects hierarchy with the root Attributes
object which has properties for each Category (i.e. resource
, subject
and environment
) which in turn have properties for every ID in the category with the property name equal to camelCase(ID)
:
console.log(policies.Attributes.resource.createdDateTime); // { "category": "resource", id: "created-dateTime" }
Using custom attributes is not currently supported and will become available in the upcoming releases
The XACML Functions are represented as regular functions under the fns
namespace with names in camelCase:
console.log(typeof policies.fns.stingEqual); // function
console.log(typeof policies.fns.stringAtLeastOneMemberOf); // function
console.log(typeof policies.fns.or); // function
The condition
parameter passed into appliesTo
, allowIf
or denyIf
methods must be a function with the return type of boolean
:
// create policy to ensure that data classified as top secret is only accessed by trusted guys
const myPolicy = createPolicy({ policyId: 'MyPolicy', ruleCombiningAlgId: 'first-applicable' })
.appliesTo(stringEqual(Attributes.resource.classification, 'topsecret'))
.allowIf(stringEqual(Attributes.subject.groupName, 'trusted_guys'))
.denyOtherwise()
.toJson();
// create policy to ensure that data classified as sensitive or very sensitive is only accessed in country XYZ
const myPolicy2 = createPolicy({ policyId: 'MyPolicy2', ruleCombiningAlgId: 'first-applicable' })
.appliesTo(stringAtLeastOneMemberOf(Attributes.resource.classification, ['sensitive', 'very-sensitive']))
.allowIf(stringEqual(Attributes.environment.locationCountry, 'XYZ'))
.denyOtherwise()
.toJson();
// create policy to ensure that marked data is only accessed at least 3 days from data protection date
const myPolicy = createPolicy({ policyId: 'MyPolicy3', ruleCombiningAlgId: 'first-applicable' })
.appliesTo(integerGreaterThan(stringBagSize(Attributes.resource.classification), 0))
.allowIf(
dateTimeGreaterThanOrEqual(
Attributes.environment.currentDateTime,
dateTimeAddDayTimeDuration(Attributes.resource.createdDateTime, 'P3D'),
),
)
.denyOtherwise()
.toJson();
- Ability to specify custom request attributes in Policy targets and rules
- Bulk Operations in SCIM APIs
- Higher Order Bag functions for condition construction
- Prettier autogenerated descriptions for Policies and Rules
- More tests
- API Reference Documentation
- Downloads and Metrics APIs?
- Publish on NPM
- etc.
This library is released under the 3-clause BSD License.
If you'd like extra help from our support team, you can contact us at support.ionicsecurity.com for questions related to account management, restoring or deleting accounts, billing, technical questions about Ionic tech, and feature requests and bug reports.