Lockpath KeyLight v2

Use the LockPath KeyLight integration to manage GRC tickets in the Keylight platform.

Case Management · Lockpath Keylight

Details

IDLockpath KeyLight v2
ProviderNavex Global
CategoryCase Management
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix 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 type
  • incidentFetchInterval — Incidents Fetch Interval
  • component_name — Name of component to fetch from
  • filter_field — Name of field to fetch by
  • fetch_limit — Fetch Limit
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents

Commands (14)

  • 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 Demisto.

  • kl-delete-record

    Deletes a selected record from within a chosen component.

  • kl-delete-record-attachment

    Deletes a specific attachment.

  • kl-get-component

    Retrieves a component specified by ID or alias. If no parameters are specified, all components will be retrieved.

  • kl-get-field

    Retrieves details for a field specified by ID.

  • kl-get-field-list

    Retrieves a detail field listing for a component specified by ID.

  • kl-get-lookup-report-column-fields

    Retrieves information of each field in a field path, which relates to a lookup report column.

  • kl-get-record

    Returns the complete set of fields for a given record within a component.

  • kl-get-record-attachment

    Returns a single attachment associated with the component ID, record ID, documents field ID, and the document ID.

  • kl-get-record-attachments

    Return the attachments of a specific field and record.

  • kl-get-record-count

    Get the number of records for a specific component and filter.

  • 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.

  • kl-get-user-by-id

    Get user details by his ID.

  • kl-update-record

    Update 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()