Lockpath KeyLight v2
Use the LockPath KeyLight integration to manage GRC tickets in the Keylight platform.
Case Management · Lockpath Keylight
Details
| ID | Lockpath KeyLight v2 |
|---|---|
| Provider | Navex Global |
| Category | Case Management |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Use the LockPath KeyLight integration to manage GRC tickets in the Keylight platform.
This integration was integrated and tested with version 5.5.018.10 of Lockpath KeyLight.
Configure Lockpath KeyLight v2 in Cortex
| Parameter | Description | Required |
|---|---|---|
| server | Server URL in the form of https://[server]:<port>; (e.g. https://192.168.0.1:4443) |
True |
| credentials | Credentials | True |
| incidentType | Incident type | False |
| component_name | Name of component to fetch from | False |
| filter_field | Name of field to fetch by | False |
| fetch_limit | Fetch Limit | False |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | False |
| isFetch | Fetch incidents | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
kl-get-component
Retrieves a component specified by ID or alias. If no parameters are specified, all components will be retrieved.
Base Command
kl-get-component
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The id of the component. | Optional |
| alias | The alias of the component. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Component.ID | String | The ID of the component. |
| Keylight.Component.Name | String | The name of the component. |
| Keylight.Component.ShortName | String | The short name of the component. |
| Keylight.Component.SystemName | String | The system name of the component. |
Command Example
!kl-get-component alias="_auditdemisto"
Context Example
{
"Keylight": {
"Component": {
"ID": 10359,
"Name": "Audit (Demisto Test)",
"ShortName": "_auditdemisto",
"SystemName": "_auditdemisto"
}
}
}
Human Readable Output
Keylight Components
| ID | Name | ShortName | SystemName |
|---|---|---|---|
| 10359 | Audit Tasks (Demisto Test) | _auditdemisto | _auditdemisto |
kl-get-field-list
Retrieves a detail field listing for a component specified by ID.
Base Command
kl-get-field-list
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The id of the component. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Field.ID | String | The ID of the field. |
| Keylight.Field.Name | String | The field's name. |
| Keylight.Field.SystemName | String | The system name of the field. |
| Keylight.Field.ShortName | String | The short name of the field. |
| Keylight.Field.ReadOnly | Boolean | Is the field read only. |
| Keylight.Field.Required | Boolean | Is the field required. |
Command Example
!kl-get-field-list component_id="10359"
Context Example
{
"Keylight": {
"Field": [
{
"FieldType": 5,
"ID": 8998,
"MatrixRows": [],
"Name": "Assignee",
"OneToMany": false,
"ReadOnly": false,
"Required": true,
"ShortName": "_assignee",
"SystemName": "_assignee"
},
{
"FieldType": 8,
"ID": 9071,
"MatrixRows": [],
"Name": "Attachment",
"OneToMany": true,
"ReadOnly": false,
"Required": false,
"ShortName": "_attachment",
"SystemName": "_attachment"
},
{
"FieldType": 1,
"ID": 9013,
"MatrixRows": [],
"Name": "Comments",
"OneToMany": false,
"ReadOnly": false,
"Required": false,
"ShortName": "_commentstest",
"SystemName": "_commentstest"
},
{
"FieldType": 3,
"ID": 8949,
"MatrixRows": [],
"Name": "Created At",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "CreatedAt",
"SystemName": "CreatedAt"
},
{
"FieldType": 5,
"ID": 8950,
"MatrixRows": [],
"Name": "Created By",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "CreatedBy",
"SystemName": "CreatedBy"
},
{
"FieldType": 2,
"ID": 8948,
"MatrixRows": [],
"Name": "Current Revision",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "Version",
"SystemName": "Version"
},
{
"FieldType": 10,
"ID": 8956,
"MatrixRows": [],
"Name": "Deleted",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "Deleted",
"SystemName": "Deleted"
},
{
"FieldType": 1,
"ID": 9082,
"MatrixRows": [],
"Name": "Description",
"OneToMany": false,
"ReadOnly": false,
"Required": true,
"ShortName": "_taskdesc",
"SystemName": "_taskdesc"
},
{
"FieldType": 8,
"ID": 9084,
"MatrixRows": [],
"Name": "Document Attachment",
"OneToMany": true,
"ReadOnly": false,
"Required": false,
"ShortName": "_Document",
"SystemName": "_Document"
},
{
"FieldType": 3,
"ID": 9002,
"MatrixRows": [],
"Name": "Due Date",
"OneToMany": false,
"ReadOnly": false,
"Required": true,
"ShortName": "_duedatetest",
"SystemName": "_duedatetest"
},
{
"FieldType": 8,
"ID": 9006,
"MatrixRows": [],
"Name": "Evidence",
"OneToMany": true,
"ReadOnly": false,
"Required": false,
"ShortName": "_evidencetest",
"SystemName": "_evidencetest"
},
{
"FieldType": 2,
"ID": 8947,
"MatrixRows": [],
"Name": "Id",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "Id",
"SystemName": "Id"
},
{
"FieldType": 2,
"ID": 8959,
"MatrixRows": [],
"Name": "Published Revision",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "PublishedVersion",
"SystemName": "PublishedVersion"
},
{
"FieldType": 1,
"ID": 9083,
"MatrixRows": [],
"MaxLength": 100,
"Name": "Task ID",
"OneToMany": false,
"ReadOnly": false,
"Required": true,
"ShortName": "_taskid",
"SystemName": "_taskid"
},
{
"FieldType": 3,
"ID": 8952,
"MatrixRows": [],
"Name": "Updated At",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "UpdatedAt",
"SystemName": "UpdatedAt"
},
{
"FieldType": 5,
"ID": 8953,
"MatrixRows": [],
"Name": "Updated By",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "UpdatedBy",
"SystemName": "UpdatedBy"
},
{
"FieldType": 1,
"ID": 9012,
"MatrixRows": [],
"MaxLength": 100,
"Name": "Work Log",
"OneToMany": false,
"ReadOnly": false,
"Required": false,
"ShortName": "_worktime",
"SystemName": "_worktime"
},
{
"FieldType": 5,
"ID": 8957,
"MatrixRows": [],
"Name": "Workflow Stage",
"OneToMany": false,
"ReadOnly": true,
"Required": false,
"ShortName": "WorkflowStage",
"SystemName": "WorkflowStage"
}
]
}
}
Human Readable Output
Keylight fields for component 10359
| ID | Name | SystemName | ShortName | ReadOnly | Required |
|---|---|---|---|---|---|
| 8998 | Assignee | _assignee | _assignee | false | true |
| 9071 | Attachment | _attachment | _attachment | false | false |
| 9013 | Comments | _commentstest | _commentstest | false | false |
| 8949 | Created At | CreatedAt | CreatedAt | true | false |
| 8950 | Created By | CreatedBy | CreatedBy | true | false |
| 8948 | Current Revision | Version | Version | true | false |
| 8956 | Deleted | Deleted | Deleted | true | false |
| 9082 | Description | _taskdesc | _taskdesc | false | true |
| 9084 | Document Attachment | _Document | _Document | false | false |
| 9002 | Due Date | _duedatetest | _duedatetest | false | true |
| 9006 | Evidence | _evidencetest | _evidencetest | false | false |
| 8947 | Id | Id | Id | true | false |
| 9083 | Task ID | _taskid | _taskid | false | true |
| 8952 | Updated At | UpdatedAt | UpdatedAt | true | false |
| 8953 | Updated By | UpdatedBy | UpdatedBy | true | false |
| 9012 | Work Log | _worktime | _worktime | false | false |
| 8957 | Workflow Stage | WorkflowStage | WorkflowStage | true | false |
kl-get-field
Retrieves details for a field specified by ID.
Base Command
kl-get-field
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the component. Get the ID from the kl-get-component command. | Required |
| field_name | The name of the field. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Field.ID | String | The ID of the field. |
| Keylight.Field.Name | String | The field's name. |
| Keylight.Field.SystemName | String | The system name of the field. |
| Keylight.Field.ShortName | String | The short name of the field. |
| Keylight.Field.ReadOnly | Boolean | Is the field read only. |
| Keylight.Field.Required | String | Is the field required. |
Command Example
!kl-get-field component_id="10359" field_name="Task ID"
Context Example
{
"Keylight": {
"Field": {
"FieldType": 1,
"ID": 9083,
"MatrixRows": [],
"MaxLength": 100,
"Name": "Task ID",
"OneToMany": false,
"ReadOnly": false,
"Required": true,
"ShortName": "_taskid",
"SystemName": "_taskid"
}
}
}
Human Readable Output
Keylight field 9083
| ID | Name | SystemName | ShortName | ReadOnly | Required |
|---|---|---|---|---|---|
| 9083 | Task ID | _taskid | _taskid | false | true |
kl-get-record
Returns the complete set of fields for a given record within a component.
Base Command
kl-get-record
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The component ID. Get the D from the kl-get-component. | Required |
| field_names | The filter specific for field names. Case sensitive. If one of the names contains a space, add all names in parenthesis (such as "Id,Published Revision"). |
Optional |
| record_id | The record ID. Get the ID from Keylight or from the kl-get-records command. | Required |
| detailed | Whether to get detailed records. Default is false. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Record.ID | String | The record ID. |
| Keylight.Record.Fields | String | The fields in the record. |
| Keylight.Record.ComponentID | String | The component ID of the record. |
| Keylight.Record.DisplayName | String | The display name of the record. |
Command Example
!kl-get-record record_id=13 component_id=10359
Context Example
{
"Keylight": {
"Record": {
"ComponentID": "10359",
"DisplayName": "This is a task",
"Fields": {
"Assignee": {
"ID": 6,
"Value": "Admin, Keylight"
},
"Attachment": [],
"Audit Project": null,
"Authority Doc Citations": null,
"Comments": null,
"Created At": "2019-11-20T14:26:17.2285486",
"Created By": {
"ID": 268,
"Value": "Development, Demisto"
},
"Current Revision": 1,
"Deleted": false,
"Description": null,
"Document Attachment": [],
"Due Date": null,
"Evidence": [],
"Id": 13,
"Published Revision": 1,
"Task ID": "This is a task",
"Updated At": "2019-11-20T14:26:17.2285486",
"Updated By": {
"ID": 268,
"Value": "Development, Demisto"
},
"Work Log": null,
"Workflow Stage": {
"ID": 221,
"Value": "Published"
}
},
"ID": 13
}
}
}
Human Readable Output
Details for record This is a task
| ComponentID | DisplayName | ID |
|---|---|---|
| 10359 | This is a task | 13 |
With the following fields
| Assignee | Attachment | Audit Project | Authority Doc Citations | Comments | Created At | Created By | Current Revision | Deleted | Description | Document Attachment | Due Date | Evidence | Id | Published Revision | Task ID | Updated At | Updated By | Work Log | Workflow Stage |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Value: Admin, Keylight </br>ID: 6 |
2019-11-20T14:26:17.2285486 | Value: Development, Demisto </br>ID: 268 |
1 | false | 13 | 1 | This is a task | 2019-11-20T14:26:17.2285486 | Value: Development, Demisto </br>ID: 268 |
Value: Published </br>ID: 221 |
kl-get-records
Retrieves the title/default field for a set of records within a chosen component.
Filters may be applied to retrieve only the records meeting the selected criteria.
Base Command
kl-get-records
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the desired component. Get the ID from the kl-get-component command. | Required |
| page_index | The index of the page of result to return. Must be >= 0 | Optional |
| page_size | The index of the page of result to return. Must be between 0 and 100. | Optional |
| filter_type | The type of filter to apply. Can be: "Contains", "Excludes", "Starts With", "Ends With", "Equals", "Not Equals", "Greater Than", "Less Than", "Greater Than", "Less Than", "Greater Equals Than", "Between", "Not Between", "Is Null", "Is Not Null". | Optional |
| filter_field_name | The name of the field for which to apply the filter. | Optional |
| filter_value | The value for which to filter. | Optional |
| detailed | Whether to get detailed records. | Optional |
| returned_fields | A list of specific fields to return. If empty, return all fields. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Record.ComponentID | String | The ID of the component containing the record. |
| Keylight.Record.DisplayName | String | The display name of the record. |
| Keylight.Record.Fields | Unknown | The fields in the record. |
| Keylight.Record.ID | Unknown | The ID of the record. |
Command Example
!kl-get-records component_id="10359" filter_type="Starts With" filter_field_name="Task ID" filter_value="Updated" detailed="True"
Context Example
{
"Keylight": {
"Record": null
}
}
Human Readable Output
Records for component 10359
with filter “Starts With: Updated” on field “Task ID”
No entries.
kl-get-record-count
Get the number of records for a specific component and filter.
Base Command
kl-get-record-count
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the desired component. Get the ID from the kl-get-component command. | Required |
| filter_type | The type of filter to apply. Can be: "Contains", "Excludes", "Starts With", "Ends With", "Equals", "Not Equals", "Greater Than", "Less Than", "Greater Than", "Less Than", "Greater Equals Than", "Between", "Not Between", "Is Null", "Is Not Null". | Optional |
| filter_field_name | The name of the field for which to apply the filter. | Optional |
| filter_value | The value for which to filter. | Optional |
Context Output
There is no context output for this command.
Command Example
!kl-get-record-count component_id=10359
Context Example
{}
Human Readable Output
There are 27 records in component 10359
kl-get-record-attachments
Return the attachments of a specific field and record.
Base Command
kl-get-record-attachments
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the desired component. Get the ID from the kl-get-component command. | Required |
| record_id | The record ID. Can get from Keylight or from the kl-get-records command. | Required |
| field_name | The name of the field that holds the attachments. Must be type "Documents". | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Attachment.FileName | String | The name of the attachment. |
| Keylight.Attachment.FieldID | String | The field ID of the attachment. |
| Keylight.Attachment.DocumentID | String | The ID of the document containing the attachment. |
| Keylight.Attachment.ComponentID | String | The component ID of the attachment. |
| Keylight.Attachment.RecordID | String | The record ID of the attachment. |
Command Example
!kl-get-record-attachments component_id=10359 field_name="Evidence" record_id=4
Context Example
{
"Keylight": {
"Attachment": {
"ComponentID": "10359",
"DocumentID": 409,
"FieldID": 9006,
"FileName": "20170105_133423 (1).jpg",
"RecordID": "4"
}
}
}
Human Readable Output
Field Evidence in record 4 has the following attachments
| ComponentID | DocumentID | FieldID | FileName | RecordID |
|---|---|---|---|---|
| 10359 | 409 | 9006 | 20170105_133423 (1).jpg | 4 |
kl-get-record-attachment
Returns a single attachment associated with the component ID, record ID, documents field ID, and the document ID.
Base Command
kl-get-record-attachment
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the desired component. Get the ID from the kl-get-component command. | Required |
| record_id | The record ID. Can get from Keylight or from the kl-get-records command. | Required |
| field_name | The name of the field that holds the attachments. Must be type "Documents". | Required |
| document_id | The ID of the document. | Required |
Context Output
There is no context output for this command.
Command Example
!kl-get-record-attachment component_id=10359 field_name="Evidence" record_id=4 document_id=409
Context Example
{
"File": {
"EntryID": "3780@02173cac-a766-46f9-865a-a98cd0a061dc",
"Extension": "jpg",
"Info": "image/jpeg",
"MD5": "e0d98c1054eff8763e7bc3c06e3a8a6b",
"Name": "20170105_133423 (1).jpg",
"SHA1": "af3037c5ffd649b25c5eef2af58a8e7583bf963c",
"SHA256": "5af85edac1bdec966440ee138d283b00f0e10e6b47a5e7de1782ad2c51e49cbf",
"SHA512": "55d1db4a3754c3cd6a1221ea73493c130f97e8bb21ef55b8b1b3d73677492edb94fee675528f7b655e1053b91e7c1ca5968401fb4b6a8d4a329991ef9b690a6b",
"SSDeep": "24576:zyK02314tDlzpa/64euXT6CtlPjPdq0O7UR5RCqSpupwP2jpOUcVnRhInUS:zyK02314FL4rX1UURPCqSpupwyQcUS",
"Size": 1810926,
"Type": "JPEG image data, Exif standard: [TIFF image data, little-endian, direntries=12, height=1836, manufacturer=samsung, model=SM-G920F, orientation=upper-left, xresolution=210, yresolution=218, resolutionunit=2, software=G920FXXU4DPGV, datetime=2017:01:11 13:14:17, width=3264], baseline, precision 8, 3264x1836, frames 3"
}
}
Human Readable Output
kl-delete-record
Deletes a selected record from within a chosen component.
Base Command
kl-delete-record
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The component ID. Get the ID from the kl-get-component command. | Required |
| record_id | The record ID. | Required |
Context Output
There is no context output for this command.
Command Example
!kl-delete-record component_id="10359" record_id="106"
Context Example
{}
Human Readable Output
Record 106 of component 10359 was deleted successfully
kl-delete-record-attachment
Deletes a specific attachment.
Base Command
kl-delete-record-attachment
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the component. Get the ID from the kl-get-component command. | Required |
| record_id | The ID of the record to delete. | Required |
| field_id | The ID of the field. | Required |
| document_id | The ID of the document to delete. | Required |
Context Output
There is no context output for this command.
Command Example
##### Human Readable Output
### kl-get-lookup-report-column-fields
***
Retrieves information of each field in a field path, which relates to a lookup report column.
##### Base Command
`kl-get-lookup-report-column-fields`
##### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| lookup_field_id | The lookup field ID, which relates to a lookup field that uses the report definition. | Required |
| field_path_id | The field path ID, which relates to the field path that retrieves fields. Get from the kl-get-record command. Detailed=True. | Required |
##### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Keylight.LookupField.ID | String | The lookup field&\#x27;s ID. |
| Keylight.LookupField.Name | String | The lookup field&\#x27;s name. |
| Keylight.LookupField.ComponentID | String | The lookup field&\#x27;s component ID. |
| Keylight.LookupField.SystemName | String | The system name of the lookup field. |
##### Command Example
Human Readable Output
kl-create-record
Creates a new record within the specified component of the Keylight application.
- The Required option for a field is only enforced through the user interface, not through Cortex XSOAR.
Base Command
kl-create-record
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the component the record should be created in. Get the ID from the kl-get-component command. | Required |
| record_json | A JSON file in the format that the API requests. The exact format is found in the API documentation. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Record.ComponentID | String | The component ID of the record. |
| Keylight.Record.DisplayName | String | The display name of the record. |
| Keylight.Record.Fields | Unknown | The fields in the record. |
| Keylight.Record.ID | String | The record ID. |
Command Example
!kl-create-record component_id="10359" record_json="[{\"fieldName\": \"Task ID\", \"value\": \"Created by Demisto Test Playbook\", \"isLookup\": false}, {\"fieldName\": \"Audit Project\", \"value\": 3, \"isLookup\": true}]"
Context Example
{
"Keylight": {
"Record": {
"ComponentID": "10359",
"DisplayName": "Created by Demisto Test Playbook",
"Fields": {
"Assignee": null,
"Attachment": [],
"Audit Project": {
"ID": 3,
"Value": "123"
},
"Authority Doc Citations": null,
"Comments": null,
"Created At": "2020-04-19T07:20:16.195364Z",
"Created By": {
"ID": 268,
"Value": "Development, Demisto"
},
"Current Revision": 1,
"Deleted": false,
"Description": null,
"Document Attachment": [],
"Due Date": null,
"Evidence": [],
"Id": 359,
"Published Revision": null,
"Task ID": "Created by Demisto Test Playbook",
"Updated At": "2020-04-19T07:20:16.195364Z",
"Updated By": {
"ID": 268,
"Value": "Development, Demisto"
},
"Work Log": null,
"Workflow Stage": {
"ID": 221,
"Value": "Published"
}
},
"ID": 359
}
}
}
Human Readable Output
Task “Created by Demisto Test Playbook”
| ComponentID | DisplayName | ID |
|---|---|---|
| 10359 | Created by Demisto Test Playbook | 359 |
With the following fields
| Assignee | Attachment | Audit Project | Authority Doc Citations | Comments | Created At | Created By | Current Revision | Deleted | Description | Document Attachment | Due Date | Evidence | Id | Published Revision | Task ID | Updated At | Updated By | Work Log | Workflow Stage |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Value: 123 </br>ID: 3 |
2020-04-19T07:20:16.195364Z | Value: Development, Demisto </br>ID: 268 |
1 | false | 359 | Created by Demisto Test Playbook | 2020-04-19T07:20:16.195364Z | Value: Development, Demisto </br>ID: 268 |
Value: Published </br>ID: 221 |
kl-update-record
Update fields in a specified record.
- The Required option for a field is only enforced through the user interface, not through Cortex XSOAR.
Base Command
kl-update-record
Input
| Argument Name | Description | Required |
|---|---|---|
| component_id | The ID of the component. Get the ID from the kl-get-component command. | Required |
| record_id | The ID of the record to be updated. Get the ID from Keylight or from the kl-get-records command. | Required |
| record_json | A JSON file in the format that the API requests. The exact format is found in the API documentation. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Keylight.Record.ComponentID | String | The ID of the component the record is in. |
| Keylight.Record.DisplayName | String | The display name of the record. |
| Keylight.Record.Fields | String | The fields in the record. |
| Keylight.Record.ID | String | The record ID |
Command Example
##### Human Readable Output
### kl-get-user-by-id
***
Get user details by his ID.
##### Base Command
`kl-get-user-by-id`
##### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| user_id | The user ID. | Required |
##### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Keylight.User.Id | String | The user ID. |
| Keylight.User.FullName | String | The user&\#x27;s full name. |
| Keylight.User.Username | String | The user&\#x27;s username. |
| Keylight.User.Active | Boolean | Is the user active. |
| Keylight.User.FirstName | String | The user&\#x27;s first name. |
| Keylight.User.MiddleName | String | The users middle name. |
| Keylight.User.LastName | String | The user&\#x27;s last name. |
| Keylight.User.EmailAddress | String | The user&\#x27;s email address. |
| Keylight.User.HomePhone | String | The user&\#x27;s home phone. |
| Keylight.User.WorkPhone | String | The user&\#x27;s work phone. |
| Keylight.User.MobilePhone | String | The user&\#x27;s mobile phone. |
##### Command Example
```!kl-get-user-by-id user_id=268```
##### Context Example
{
“Keylight”: {
“User”: {
“APIAccess”: true,
“AccountType”: 1,
“Active”: true,
“Deleted”: false,
“EmailAddress”: “demisto@demisto.com”,
“Fax”: “”,
“FirstName”: “Demisto”,
“FullName”: “Development, Demisto”,
“FunctionalRoles”: [],
“Groups”: [
{
“Id”: 42,
“Name”: “Demisto Development”
}
],
“HomePhone”: “”,
“Id”: 268,
“IsLDAP”: false,
“IsSAML”: false,
“Language”: 1033,
“LastName”: “Development”,
“Locked”: false,
“MiddleName”: “”,
“MobilePhone”: “”,
“SecurityConfiguration”: {
“DisplayName”: “Standard User Configuration”,
“Id”: 7
},
“SecurityRoles”: [
{
“Id”: 28,
“Name”: “Demisto Developer”
}
],
“Title”: “Demisto Users”,
“Username”: “demisto@demisto.com”,
“WorkPhone”: “”
}
}
}
```
Human Readable Output
Keylight user 268
| APIAccess | AccountType | Active | Deleted | EmailAddress | Fax | FirstName | FullName | FunctionalRoles | Groups | HomePhone | Id | IsLDAP | IsSAML | Language | LastName | Locked | MiddleName | MobilePhone | SecurityConfiguration | SecurityRoles | Title | Username | WorkPhone |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| true | 1 | true | false | demisto@demisto.com | Demisto | Development, Demisto | {‘Id’: 42, ‘Name’: ‘Demisto Development’} | 268 | false | false | 1033 | Development | false | Id: 7 </br>DisplayName: Standard User Configuration |
{‘Id’: 28, ‘Name’: ‘Demisto Developer’} | Demisto Users | demisto@demisto.com |
Configuration parameters
server— Server URL in the form of https://[server]:<port> (e.g. https://192.168.0.1:4443) (required)credentials— Credentials (required)incidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalcomponent_name— Name of component to fetch fromfilter_field— Name of field to fetch byfetch_limit— Fetch Limitinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidents
Commands (14)
-
kl-create-recordCreates a new record within the specified component of the Keylight application. * The Required option for a field is only enforced through the user interface, not through Demisto.
-
kl-delete-recordDeletes a selected record from within a chosen component.
-
kl-delete-record-attachmentDeletes a specific attachment.
-
kl-get-componentRetrieves a component specified by ID or alias. If no parameters are specified, all components will be retrieved.
-
kl-get-fieldRetrieves details for a field specified by ID.
-
kl-get-field-listRetrieves a detail field listing for a component specified by ID.
-
kl-get-lookup-report-column-fieldsRetrieves information of each field in a field path, which relates to a lookup report column.
-
kl-get-recordReturns the complete set of fields for a given record within a component.
-
kl-get-record-attachmentReturns a single attachment associated with the component ID, record ID, documents field ID, and the document ID.
-
kl-get-record-attachmentsReturn the attachments of a specific field and record.
-
kl-get-record-countGet the number of records for a specific component and filter.
-
kl-get-recordsRetrieves the title/default field for a set of records within a chosen component. Filters may be applied to retrieve only the records meeting the selected criteria.
-
kl-get-user-by-idGet user details by his ID.
-
kl-update-recordUpdate fields in a specified record. * The Required option for a field is only enforced through the user interface, not through Demisto.
import traceback from datetime import datetime, timedelta import demistomock as demisto import urllib3 from CommonServerPython import * from CommonServerUserPython import * # Disable insecure warnings urllib3.disable_warnings() FILTER_DICT = { "Contains": "1", "Excludes": "2", "Starts With": "3", "Ends With": "4", "Equals": "5", "Not Equals": "6", "Greater Than": "7", "Less Than": "8", "Greater Equals Than": "9", "Less Equals Than": "10", "Between": "11", "Not Between": "12", "Is Null": "15", "Is Not Null": "16", } INTEGRATION_CONTEXT_SIZE = 15 """CLIENT""" class Client(BaseClient): @logger def _http_request( self, method, url_suffix, full_url=None, headers=None, auth=None, json_data=None, params=None, data=None, files=None, timeout=10, resp_type="json", ok_codes=None, **kwargs, ): res = super()._http_request( method=method, url_suffix=url_suffix, full_url=full_url, headers=headers, auth=auth, json_data=json_data, params=params, data=data, files=files, timeout=timeout, resp_type=resp_type, ok_codes=ok_codes, **kwargs, ) return res def login(self, username: str, password: str) -> bool: """ Logs in to the server and keeps the cookie as header. Args: username: password: Returns: Returns if connection was successful. """ body = {"username": username, "password": password} res = self._http_request("POST", "/SecurityService/Login", resp_type="response", json_data=body) successful = res.content == b"true" return successful def logout(self): """ Logs out of the connection. """ self._http_request("GET", "/SecurityService/Logout") def return_components(self, link: str, params: dict = None) -> None: res = self._http_request("GET", link, params=params) if isinstance(res, dict): res["ID"] = res.pop("Id") else: for comp in res: comp["ID"] = comp.pop("Id") ec = {"Keylight.Component(val.ID && val.ID==obj.ID))": res} hr = tableToMarkdown("Keylight Components", res) return_outputs(hr, ec, res) def return_fields(self, suffix: str, params: dict = None, title: str = None) -> None: """ Runs and returns field commands according to the suffix . Args: suffix: which api call to make params: if the command neads a params this are them title: The title for the table to markdown Returns: """ res = self._http_request("GET", suffix, params=params) if isinstance(res, dict): res["ID"] = res.pop("Id") else: for field in res: field["ID"] = field.pop("Id") ec = {"Keylight.Field(val.ID && val.ID==obj.ID))": res} hr = tableToMarkdown(title, res, ["ID", "Name", "SystemName", "ShortName", "ReadOnly", "Required"]) return_outputs(hr, ec, res) def return_records(self, component_id: str, record_id: str, field_names: str, suffix: str) -> None: """ Returns to demisto record calls according to suffix Args: component_id: The component IF record_id: which record to return field_names: what fields to return suffix: The suffix for the API request Returns: None """ params = {"componentID": component_id, "recordId": record_id} res = self._http_request("GET", suffix, params=params) field_names = argToList(field_names) all_fields = self.field_output_to_hr_fields(res.get("FieldValues", []), component_id, field_names) record = {"ID": res.get("Id"), "ComponentID": component_id, "DisplayName": res.get("DisplayName", "")} hr = tableToMarkdown(f'Details for record {record.get("DisplayName")}:', record) hr += tableToMarkdown("With the following fields:", all_fields) record["Fields"] = all_fields ec = {"Keylight.Record(val.ID && val.ID==obj.ID))": record} return_outputs(hr, ec, res) def return_filtered_records( self, component_id: str, page_size: str, page_index: str, suffix: str, filter_type: str = None, filter_field_id: str = None, filter_value: str = None, ) -> dict: """ Args: component_id: component id page_size: how many results to return per page page_index: what page number suffix: API suffix filter_type: What filter to apply (out of FILTER_DICT filter_field_id: which field to apply the filter on filter_value: the filter value Returns: number of records according to a certain query made up of filter_type, filter_value and filter_field_id """ data = {"componentId": component_id, "pageIndex": page_index, "pageSize": page_size} if filter_type: data["filters"] = [create_filter(filter_type, filter_value, filter_field_id)] # type: ignore else: data["filters"] = [] # type: ignore res = self._http_request("POST", suffix, json_data=data) for result in res: result["ID"] = result.pop("Id") result["ComponentID"] = component_id return res def change_record(self, component_id: str, record_id: str | None = None, record_json: dict = None) -> None: json_data = { "componentId": component_id, "dynamicRecord": {"FieldValues": self.string_to_FieldValues(record_json, component_id)}, } suffix = "/ComponentService/CreateRecord" if record_id: json_data["dynamicRecord"]["Id"] = record_id # type: ignore suffix = "/ComponentService/UpdateRecord" res = self._http_request("POST", suffix, json_data=json_data) fields = self.field_output_to_hr_fields(res.get("FieldValues", []), component_id) record = {"ID": res.get("Id"), "ComponentID": component_id, "DisplayName": res.get("DisplayName", "")} hr = tableToMarkdown(f'Task "{record.get("DisplayName")}":', record) hr += tableToMarkdown("With the following fields:", fields) record["Fields"] = fields ec = {"Keylight.Record(val.ID && val.ID==obj.ID))": record} return_outputs(hr, ec, res) """HELPER CLIENT FUNCTIONS""" def component_id_from_name(self, name: str) -> str: """ Args: name: Name of component Returns: The component ID """ component_list = self._http_request("GET", "/ComponentService/GetComponentList") component = {} # type: dict for comp in component_list: if comp.get("Name") == name: component = comp return str(component.get("Id")) def field_id_from_name(self, name: str, component_id: str) -> str | None: """ Args: name: The field's name component_id: Returns: The field_id if it exists """ field_map = demisto.getIntegrationContext().get(str(component_id)) if not field_map: self.update_field_integration_context(component_id) field_map = demisto.getIntegrationContext().get(str(component_id)) fields = field_map.get("fields") for field_key, field_name in fields.items(): if field_name == name: return field_key return None @logger def update_field_integration_context(self, component_id: str) -> None: """ update integration context to include the component_id and have at most 7 tables stored Update policy : FIFO Integration context will look: { component_id: { last_update: $date fields: {field_key: field_name. field_key, field_name, ..., } } } Args: component_id: The id of the component we want to add to the integration context Returns: None """ field_map = demisto.getIntegrationContext() if field_map.get(str(component_id)): field_map.pop(str(component_id)) params = {"componentId": component_id} fields = self._http_request("GET", "/ComponentService/GetFieldList", params=params) field_names = {} for field in fields: field_names[str(field.get("Id"))] = field.get("Name") update = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") if len(field_map) == INTEGRATION_CONTEXT_SIZE: min_time = update min_component = "" for component in field_map: updated = field_map.get(component).get("updated") if parse_date_string(updated) < parse_date_string(min_time): min_time = updated min_component = component field_map.pop(min_component) field_map[str(component_id)] = {"fields": field_names, "updated": update} demisto.setIntegrationContext(field_map) @logger def field_output_to_hr_fields(self, field_output: dict, component_id: str, returned_fields: list = None) -> dict: """ Args: field_output: a dictionary of key,values that is the output of FieldValue field component_id: What component the fields are from returned_fields: A list of field names to return. If None - all fields returned Returns: """ field_map = demisto.getIntegrationContext().get(str(component_id)) final_fields = {} if not field_map: self.update_field_integration_context(component_id) field_map = demisto.getIntegrationContext().get(str(component_id)) fields = field_map.get("fields") for field_dict in field_output: field_key = field_dict.get("Key") field_val = field_dict.get("Value") if not fields.get(str(field_key)): self.update_field_integration_context(component_id) fields = demisto.getIntegrationContext().get(str(component_id)).get("fields") field_name = fields.get(str(field_key)) if isinstance(field_val, dict) and field_val.get("DisplayName"): field_val = {"Value": field_val.get("DisplayName"), "ID": field_val.get("Id", -1)} if not returned_fields or field_name in returned_fields: final_fields[field_name] = field_val return final_fields @logger def string_to_FieldValues(self, fields_json: dict | list, component_id: str) -> list: """ Args: field_json in the format: [{ "fieldName": "Task ID", "value": "1", "isLookup": false }, ... ] Returns: returns the for right format (dynamicRecord) for creating and updating a record. """ key_val_return = [] for field in fields_json: field_id = self.field_id_from_name(field.get("fieldName", ""), component_id) value = field.get("value", "") if not field_id: raise ValueError(f'Could not find the field "{field.get("fieldName", "")}" in component {component_id}.') if field.get("isLookup", ""): key_val_return.append({"Key": field_id, "Value": {"Id": value}}) else: key_val_return.append({"Key": field_id, "Value": value}) return key_val_return """HELPER FUNCTIONS""" @logger def create_filter(filter_type: str, filter_value: str, filter_field_id: str) -> dict: """ Args: filter_type: What type of filter to apply on the field. out of FILTER_DICT filter_value: filter_field_id: Returns: A filter made from the arguments in the format keylight needs. """ # adding filter if exists if not FILTER_DICT.get(filter_type): raise ValueError("Filter Type is invalid.") filter = {"FieldPath": [int(filter_field_id)], "FilterType": FILTER_DICT.get(filter_type), "Value": filter_value} return filter """COMMAND FUNCTIONS""" def get_component_command(client: Client, args: dict) -> None: """ Args: client: The client args: Demisto.args() Returns: A list of all components. """ if args.get("component_id"): params = {"id": args.get("component_id")} client.return_components("/ComponentService/GetComponent", params) elif args.get("alias"): params = {"alias": args.get("alias")} client.return_components("/ComponentService/GetComponentByAlias", params) else: client.return_components("/ComponentService/GetComponentList") def get_field_list_command(client: Client, args: dict) -> None: params = {"componentId": args.get("component_id")} client.return_fields("/ComponentService/GetFieldList", params, f"Keylight fields for component {params.get('componentId')}:") def get_field_command(client: Client, args: dict) -> None: field_id = client.field_id_from_name(args.get("field_name", ""), args.get("component_id", "")) params = {"id": field_id} client.return_fields("/ComponentService/GetField", params, f"Keylight field {params.get('id')}:") def get_record_command(client: Client, args: dict) -> None: path = "/ComponentService/GetDetailRecord" if args.get("detailed", "False") == "True" else "/ComponentService/GetRecord" client.return_records(args.get("component_id", ""), args.get("record_id", ""), args.get("field_names", ""), path) def get_records_command(client: Client, args: dict) -> None: page_size = str(min(int(args.get("page_size", "10")), 100)) component_id = args.get("component_id", "") page_index = args.get("page_index", "0") filter_type = args.get("filter_type") filter_value = args.get("filter_value", "") field_name = args.get("filter_field_name", "") returned_fields = argToList(args.get("returned_fields", "")) filter_field_id = None if filter_type and filter_value and field_name: filter_field_id = client.field_id_from_name(field_name, component_id) if not filter_field_id: raise ValueError(f'Could not find the field "{field_name}" in component {component_id}.') detailed = "/ComponentService/GetDetailRecords" if args.get("detailed", "False") == "True" else "/ComponentService/GetRecords" res = client.return_filtered_records( component_id, page_size, page_index, detailed, filter_type, filter_field_id, filter_value ) for record in res: record["Fields"] = client.field_output_to_hr_fields(record.pop("FieldValues"), component_id, returned_fields) ec = {"Keylight.Record(val.ID == obj.ID)": res} title = f"Records for component {component_id}" if filter_type: title += f' \n### with filter "{filter_type}: {filter_value}" on field "{field_name}"' records = [] for record in res: temp_dict = record.get("Fields").copy() for key in temp_dict: if isinstance(temp_dict[key], dict): temp_dict[key] = temp_dict[key].get("Value") temp_dict["Id"] = record.get("ID") temp_dict["DisplayName"] = record.get("DisplayName") records.append(temp_dict) hr = tableToMarkdown(title, records) # hr = f'# {title}\n' # for record in res: # hr += tableToMarkdown(f'Record {record.get("DisplayName", "")} (ID: {record.get("ID", "")}):', # record.get("Fields")) return_outputs(hr, ec, res) def get_record_count_command(client: Client, args: dict) -> None: component_id = args.get("component_id", "") filter_type = args.get("filter_type", "") filter_value = args.get("filter_value", "") filter_field_name = args.get("filter_field_name", "") data = {"componentId": component_id} if not filter_type or not filter_value or not filter_field_name: data["filters"] = [] else: filter_field_id = client.field_id_from_name(filter_field_name, component_id) if not filter_field_id: raise ValueError("Could not find the field name.") data["filters"] = [create_filter(filter_type, filter_value, filter_field_id)] res = client._http_request("POST", "/ComponentService/GetRecordCount", json_data=data) title = f"## There are **{res}** records in component {component_id}.\n" if filter_type: title += f'### with filter: "{filter_type} {filter_value}" on field `{filter_field_name}`' return_outputs(title) def get_record_attachments_command(client: Client, args: dict) -> None: field_name = args.get("field_name", "") record_id = args.get("record_id", "") component_id = args.get("component_id", "") field_id = client.field_id_from_name(field_name, component_id) params = {"componentID": component_id, "recordId": record_id, "fieldId": field_id} res = client._http_request("GET", "/ComponentService/GetRecordAttachments", params=params) for doc in res: doc["FieldID"] = doc.pop("FieldId") doc["DocumentID"] = doc.pop("DocumentId") doc["RecordID"] = record_id doc["ComponentID"] = component_id if not res: hr = f"## Field {field_id} in record {record_id} has no attachments." return_outputs(hr) return hr = tableToMarkdown(f"Field {field_name} in record {record_id} has the following attachments:", res) ec = {"Keylight.Attachment(val.FieldID == obj.FieldID && val.DocumentID == obj.DocumentID)": res} return_outputs(hr, ec, res) def get_record_attachment_command(client: Client, args: dict) -> None: component_id = args.get("component_id", "") field_name = args.get("field_name", "") record_id = args.get("record_id", "") doc_id = args.get("document_id", "") field_id = client.field_id_from_name(field_name, component_id) params = {"componentID": component_id, "recordId": record_id, "fieldId": field_id, "documentId": doc_id} res = client._http_request("GET", "/ComponentService/GetRecordAttachment", params=params) demisto.results(fileResult(res.get("FileName", ""), base64.b64decode(res.get("FileData")))) def delete_record_attachment_command(client: Client, args: dict) -> None: field_id = args.get("field_id", "") record_id = args.get("record_id", "") doc_id = args.get("document_id", "") component_id = args.get("component_id", "") json_data = { "componentId": component_id, "dynamicRecord": {"Id": record_id, "FieldValues": [{"Key": field_id, "value": [{"Id": doc_id}]}]}, } client._http_request("POST", "/ComponentService/DeleteRecordAttachments", json_data=json_data) return_outputs("### Attachment was successfully deleted from the Documents field.") def delete_record_command(client: Client, args: dict) -> None: component_id = args.get("component_id", "") record_id = args.get("record_id", "") json_data = {"componentId": component_id, "recordId": record_id} client._http_request("DELETE", "/ComponentService/DeleteRecord", json_data=json_data) return_outputs(f"### Record {record_id} of component {component_id} was deleted successfully.") def get_lookup_report_column_fields_command(client: Client, args: dict) -> None: field_path_id = args.get("field_path_id", "") lookup_field_id = args.get("lookup_field_id", "") params = {"lookupFieldId": lookup_field_id, "fieldPathId": field_path_id} res = client._http_request("GET", "/ComponentService/GetLookupReportColumnFields", params=params) for rec in res: rec["ID"] = rec.pop("Id") rec["ComponentID"] = rec.pop("ComponentId") ec = {"Keylight.LookupField(val.ID === obj.ID)": res} hr = tableToMarkdown(f"Here is more information about field path {field_path_id}, lookup field {lookup_field_id}:", res) return_outputs(hr, ec, res) def create_record_command(client: Client, args: dict) -> None: component_id = args.get("component_id", "") record_json = args.get("record_json", "{}").replace("'", '"') record_json = json.loads(record_json) client.change_record(component_id, record_json=record_json) def update_record_command(client: Client, args: dict) -> None: component_id = args.get("component_id", "") record_id = args.get("record_id", "") record_json = args.get("record_json", "{}").replace("'", '"') record_json = json.loads(record_json) client.change_record(component_id, record_id, record_json) def get_user_by_id_command(client: Client, args: dict) -> None: user_id = args.get("user_id", "") res = client._http_request("GET", f"/SecurityService/GetUser?id={user_id}") hr = tableToMarkdown(f"Keylight user {user_id}", res) ec = {"Keylight.User(val.Id && val.Id==obj.Id)": res} return_outputs(hr, ec, res) def fetch_incidents(client: Client, args: dict) -> None: name = demisto.params().get("component_name", "") filter_field = demisto.params().get("filter_field", "") page_size = str(min(int(demisto.params().get("fetch_limit", "50")), 50)) if not name or not filter_field: raise ValueError("No component alias or field to filter by specified.") last_fetch_time = demisto.getLastRun().get("last_fetch_time") if not last_fetch_time: now = datetime.now() last_fetch = now - timedelta(days=120) last_fetch_time = last_fetch.strftime("%Y-%m-%dT%H:%M:%S") # Find component ID component_id = demisto.getLastRun().get("component", {}).get(name) if not component_id: component_id = client.component_id_from_name(name) if component_id == "None": raise ValueError("Could not find component name.") field_id = demisto.getLastRun().get("field", {}).get(filter_field) if not field_id: field_id = client.field_id_from_name(filter_field, component_id) if not field_id: raise ValueError("Could not find field name.") res = client.return_filtered_records( component_id, page_size, "0", "/ComponentService/GetDetailRecords", "Greater Than", field_id, last_fetch_time ) incidents = [] max_fetch_time = last_fetch_time for record in res: record["Fields"] = client.field_output_to_hr_fields(record.pop("FieldValues"), component_id) occurred_at = record.get("Fields", {}).get(filter_field, datetime.now().strftime("%Y-%m-%dT%H:%M:%S")) incident = { "name": f'Keylight record {record.get("DisplayName")}', "occurred": occurred_at.split(".")[0] + "Z", "rawJSON": json.dumps(record), } if datetime.strptime(occurred_at.split(".")[0], "%Y-%m-%dT%H:%M:%S") > datetime.strptime( max_fetch_time.split(".")[0], "%Y-%m-%dT%H:%M:%S" ): max_fetch_time = occurred_at incidents.append(incident) demisto.setLastRun({"last_fetch_time": max_fetch_time, "component": {name: component_id}, "field": {filter_field: field_id}}) demisto.incidents(incidents) def main(): params = demisto.params() proxy = params.get("proxy") verify = not params.get("insecure") address = params.get("server", "").rstrip("/") username = params.get("credentials", {}).get("identifier", "") password = params.get("credentials", {}).get("password", "") client = Client(address, verify, proxy, headers={"Accept": "application/json"}) commands = { "kl-get-component": get_component_command, "kl-get-field-list": get_field_list_command, "kl-get-field": get_field_command, "kl-get-record-count": get_record_count_command, "kl-get-record": get_record_command, "kl-get-records": get_records_command, "kl-delete-record": delete_record_command, "kl-create-record": create_record_command, "kl-update-record": update_record_command, "kl-get-lookup-report-column-fields": get_lookup_report_column_fields_command, "kl-get-record-attachment": get_record_attachment_command, "kl-get-record-attachments": get_record_attachments_command, "kl-delete-record-attachment": delete_record_attachment_command, "kl-get-user-by-id": get_user_by_id_command, "fetch-incidents": fetch_incidents, } LOG(f"Command being called is {demisto.command()}") logged_in = False try: logged_in = client.login(username, password) if logged_in: if demisto.command() == "test-module": demisto.results("ok") else: commands[demisto.command()](client, demisto.args()) except Exception as e: if not logged_in: return_error( f"Could not connect to instance. Make sure your credentials are correct and haven't changed. Error: {e!s}" ) else: return_error(f"Failed to execute {demisto.command()} command. Error: {e!s}", error=traceback.format_exc()) finally: if logged_in: client.logout() if __name__ in ("__main__", "__builtin__", "builtins"): main()