mcas 1.0.5
Python API wrapper module for interacting with Microsoft Cloud App Security
License: MIT
Requires Python: >=3.7

# MCAS-Python

API Wrapper module for the Microsoft Cloud App Security API.

# Importing the Module

Install the module via pip:
```shell script
pip install mcas
```

Access the McasApi class and it's methods:
```python
from mcas.api import McasApi
```

# Connecting to the MCAS API
### `McasApi()`

Microsoft Cloud App Security API Wrapper Class. Establishes a connection to MCAS API using the 
Authorization Token & domain from config.toml.

```python
# Establishing a connection to the MCAS API
from mcas.api import McasApi
api = McasApi()
```

# McasApi Methods

### `update_api_token()`
Update API Authorization Token stored in config.toml.
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| token  | `str`     | String value of API Authorization Token to write to config.toml. Previously stored value is replaced. | Yes

##### Returns
Status of update (True/False) 

##### Example(s)
```python
# Updating API Authorization Token
api.update_api_token(token="AUTHORIZATION TOKEN")
```
___

### `update_domain()`
Update API domain stored in config.toml.
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| domain  | `str`     | String value of domain to write to config.toml. Previously stored value is replaced.  | Yes

##### Returns
Status of update (True/False) 

##### Example(s)
```python
# Update domain
api.update_domain(domain="API DOMAIN")
```
___

### `update_export_location()`
Updates location where exported files are written to in config.toml

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| path  | `str`     | Path to serve as location for exports. On Windows "\\" characters need to be prefaced by another "\\" character. (e.g. C:\\\\Path\\\\to\\\\location\\\\)  | Yes

##### Returns
Status of update (True/False) 

##### Example(s)
```python
# Update Export Location
api.update_export_location(path="PATH")
```
___

### `update_export_format()`
Updates default format used when files are exported
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| fmt  | `str`     | format to export files in by default (JSON, EXCEL, or CSV)  | Yes

##### Returns
Status of update (True/False) 

##### Example(s)
```python
# Update default export format
api.update_export_format(domain="JSON")
```
___


### `export_summary_report()`
Export MCAS summary report (Alerts, Custom IP Ranges, Admins, User Groups, Policies, Governance Tasks). Default output location 
and format are specified in config.toml.

##### Returns
Status of update (True/False) 

##### Example(s)
```python
# Export a MCAS Summary Report
api.export_summary_report()
```

## Alerts
### `get_alerts()`
Fetch information on alert(s)
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| alert_id | `str`     | Specific alert ID to pull information for  | No
| body | `dict`    | API request body stored as Python dictionary. If no body is specified only Open alerts will be returned  | No
| params   | `dict`    | Query params to include in the request  | No
| export_fmt | `str`     | Format to export the results in (JSON, EXCEL, CSV). Output location can be found in config.toml  | No
| verbose | `bool`    | Controls the verbosity of console output  | No


##### Returns
List of Alert objects. Returns nothing if a value is provided for export_fmt

##### Example(s)
```python
# Fetch all alerts with a resolution status of "Open"
alerts_body = {
    "resolution_status": {
        'eq': ["Open"]
    }
}
alerts = api.get_alerts(body=alerts_body)

# Fetch specific alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Fetch all alerts with a resolution status of "Open" and export to Excel
api.get_alerts(export_fmt="EXCEL")
```
___

### `resolve_alerts()`
Resolves Alert(s) with optional comment
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| alert_ids  | `str`     | Alert ID or IDs to target  | Yes
| comment | `str`    | Comment to list as reason for resolution. (Default - Resolved via PyCAS by %username%)  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Request status

##### Example(s)
```python
# Resolve single alert with a comment
api.resolve_alerts(alert_ids="Alert ID", comment="Reason for resolution")

# Resolve multiple alerts without a comment
api.resolve_alerts(alert_ids=["Alert 1", "Alert 2", "Alert 3"])
```
___

### `dismiss_alerts()`
Dismiss Alert(s) with optional comment
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| alert_ids  | `str`     | Alert ID or IDs to target  | Yes
| comment | `str`    | Comment to list as reason for resolution. (Default - Dismissed via PyCAS by %username%)  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Request status

##### Example(s)
```python
# Dismiss single alert with a comment
api.dismiss_alerts(alert_ids="Alert ID", comment="Reason for dismissal")

# Dismiss multiple alerts without a comment
api.dismiss_alerts(alert_ids=["Alert 1", "Alert 2", "Alert 3"])
```
___

### `reopen_alerts()`
Reopens Alert(s)
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| alert_ids  | `str`     | Alert ID or IDs to target  | Yes
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Request response

##### Example(s)
```python
# Reopen Single Alert
api.reopen_alerts(alert_ids="ALERT ID", verbose=False)

# Reopen Multiple Alerts
api.reopen_alerts(alert_ids=["ALERT ID #1", "ALERT ID #2", "ALERT ID #3"])
```
___

### `mark_alert_as_read()`
Marks Alert(s) as read
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| alert_ids  | `str`     | Alert ID or IDs to target  | Yes
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Request status

##### Example(s)
```python
# Mark single alert as read
api.mark_alert_as_read(alert_ids="ALERT ID")

# Mark multiple alerts as read
api.mark_alert_as_read(alert_ids=["ALERT ID #1", "ALERT ID #2", "ALERT ID #3"])
```
___

### `mark_alert_as_unread()`
Marks Alert(s) as unread
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| alert_ids  | `str`     | Alert ID or IDs to target  | Yes
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Request status

##### Example(s)
```python
# Mark single alert as unread
api.mark_alert_as_unread(alert_ids="ALERT ID")

# Mark multiple alerts as unread
api.mark_alert_as_unread(alert_ids=["ALERT ID #1", "ALERT ID #2", "ALERT ID #3"])
```

___

## Activities
### `get_activities()`
Fetches activity information
##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| activity_id | `str` | Specific alert ID to pull information for  | No
| body | `dict` | API request body stored as Python dictionary. | No
| params | `dict`  | Query params to include in the request  | No
| export_fmt | `str`| Format to export the results in (JSON, EXCEL, CSV). Output location can be found in config.toml  | No
| verbose | `bool`| Controls the verbosity of console output  | No


##### Returns
List of Activity objects. Returns nothing if a value is provided for export_fmt.

##### Example(s)
```python
# Fetch activities associated with an Alert
activities_body = {
    'alert_id': {
        'eq': 'ALERT ID'
    }
}
activites = api.get_activities(body=activities_body)

# Fetch specific activity by ID
activities = api.get_activities(activity_id="ACTIVITY ID")
```
___

## Users
### `get_user()`
Fetches information (UPN, Entity String, User ID, & Primary Key) for a specific user

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| username  | `str`     | Username (formatted as email address) to target  | Yes
| verbose | `bool`    | Controls the verbosity of console output  | No


##### Returns
User object

##### Example(s)
```python
# Fetch information for a user
user = api.get_user(username="USER")
```
___

### `revoke_user_access_tokens()`
Revokes all access tokens for an account. This will prevent access to any of the organization's data and will force the 
user to sign into all cloud applications again.

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| user  | `str`     | User account to suspend tokens on  | Yes
| force  | `bool`     | Skip confirmation step  | No
| verbose | `bool`    | Controls the verbosity of console output  | No


##### Returns
Request response

##### Example(s)
```python
# Revoke tokens on user account w/o confirmation
api.revoke_user_access_tokens(user="USER", force=True)
```
___

### `suspend_user_account()`
Suspends user account and prevents subsequent sign-ins. This doesn't delete the user's profile or data such as 
documents, calendar events, or email messages. However, the user can no longer sign in to the account, and new 
email and calendar invitations are blocked. All shared assets remain accessible to collaborators.

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| user  | `str`     | Account to suspend  | Yes
| force  | `bool`     | Skip confirmation step  | No
| verbose | `bool`    | Controls the verbosity of console output  | No


##### Returns
Request response

##### Example(s)
```python
# Suspend user
api.suspend_user_account(user="USER")
```
___

### `unsuspend_user_account()`
Unsuspends user account

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| user  | `str`     | Account to unsuspend  | Yes
| force  | `bool`     | Skip confirmation step  | No 
| verbose | `bool`    | Controls the verbosity of console output  | No


##### Returns
Request response

##### Example(s)
```python
api.unsuspend_user_account(user="USER")
```

___

## IP Ranges
### `get_ip_range()`
Fetches a list of custom IP ranges

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| name | `str`     | Name of specific IP Range to return information for  | No
| enabled_only | `bool`    | Return only enabled IP ranges (boolean)  | No
| export_fmt | `str`     | Format to export the results in  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
List of IP_Range objects. Returns nothing if a value is provided for export_fmt

##### Example(s)
```python
# Get all custom IP Ranges
ip_range = api.get_ip_range()

# Get all enabled custom IP Ranges
ip_range = api.get_ip_range(enabled_only=True)

# Get specific IP Range by name
ip_range = api.get_ip_range(name="SPECIFIC IP RANGE NAME")
```

___

### `create_ip_range()`
Creates a new custom IP range

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| name  | `str`     | Name of IP Range  | Yes
| category | `str`    | Category of IP range (i.e. Corporate, Risky, VPN, etc.) see IP_Categories in references.toml  | Yes
| subnet_masks  | `str` or `list`     | Python list of [CIDR Notated](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing_) IP addresses/ranges  | Yes
| tags | `list`     | IP Tags to assign to category. If tag does not already exist, a new one is made.  | No
| isp | `str`    | Organization/Internet Service Provider. If provided it will override the automatically generated value.  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
ID of new IP Range

##### Example(s)
```python
# Create a custom IP range
api.create_ip_range(name="IP RANGE NAME", subnet_masks=["CIDR RANGE 1", "CIDR RANGE 2", "CIDR RANGE 3"],
                    tags=["TAG 1", "TAG 2", "TAG 3"], category="VPN", isp="ISP")
```
___

### `delete_ip_range()`
Deletes an IP Range by name (or ID)

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| name  | `str`     | Name of range to delete  | Yes (Either name or range_id)
| range_id | `str`    | ID of range to delete  | Yes (Either name or range_id)
| verbose| `bool`    | Controls verbosity of console output  | No

##### Returns
Request status

##### Example(s)
```python
# Delete a custom IP range by name
api.delete_ip_range(name="IP RANGE NAME")

# Delete a custom IP range by ID
api.delete_ip_range(range_id="IP RANGE ID")
```
___

### `update_ip_range()`
Updates properties of an existing IP Range by name.

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| name  | `str`     | Name of range to update  | Yes
| new_category | `str`    | New category classification for IP Range. The old category is retained if an argument is not provided  | No
| add_subnet_masks | `str` or `list`    | Subnet masks to add to selected IP Range  | No
| remove_subnet_masks | `str` or `list`    | Subnet masks to remove from selected IP Range. Remove all subnet masks by providing a value of 'all'. | No
| new_isp | `str`    | New ISP for range. The old ISP is retained if an argument is not provided  | No
| add_tags | `str` or `list`    | List of tags to add to selected IP Range  | No
| remove_tags | `str` or `list`     | Name of range to update. Remove all tags by providing a value of 'all'.  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Dictionary of IP Range Properties

##### Example(s)
```python
# Update IP Range - Subnet Masks
api.update_ip_range(name="IP RANGE NAME", add_subnet_masks=["CIDR RANGE 1", "CIDR RANGE 2", "CIDR RANGE 3"],
                    remove_subnet_masks="all")
                    
# Update IP Range - Category
api.update_ip_range(name="IP RANGE NAME", new_category="Other")
```
___

## Admin Access
### `get_admin_access()`
Returns users setup with admin access in the MCAS Portal

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| user | `str`     | specific user to filter off of. (Formatted as email address)  | No
| export_fmt | `str`     | Format to export the results in  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
List of Admin objects Returns nothing if a value is provided for export_fmt

##### Example(s)
```python
# Get MCAS Admins
mcas_admins = api.get_admin_access()

# Get Specific MCAS Admin Privileges
# mcas_admins = api.get_admin_access(user="USER")
```
___

### `add_admin_access()`
Grant admin access to the MCAS portal for a specified user. Access levels include: App Admin, User Group Admin, Cloud Discovery Global Admin, and Cloud Discovery Report Admin.

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| user  | `str`     | User to be granted access to MCAS  | Yes
| access_level | `str`    | Level of access to be given  | Yes
| applications  | `str` or `list` | Application(s) to grant access to | Required if access level is App Admin
| groups | `str` or `list`   | Group(s) to grant access to.  | Required if access level is User Group Admin
| reports  | `str` or `list`     | Report(s) to grant access to. | Required if access level is Cloud discovery report admin
| read_only | `bool`    | Denotes if the access granted should be read-only. Default is false. | Required if access level is App Admin or User Group Admin
| allow_deanonymization  | `bool` | Allows admin to de-anonymize personal identifiable information in reports. Default is false. | No, only relevent if access level is Cloud discovery global admin or Cloud discovery report admin
| verbose | `bool`   | Controls the verbosity of console output  | No

##### Returns
Request status

##### Example(s)
```python
# Assign Cloud Discovery Global Admin Access
api.add_admin_access(user="USER", 
                     access_level="Cloud Discovery Global Admin", 
                     allow_deanonymization=True)       
```
___

### `remove_admin_access()`
Returns users setup with admin access in the MCAS Portal

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| user  | `str`     | User (formatted as email address) to target  | Yes
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
Request status

##### Example(s)

```python
# Revoke Admin Access
api.remove_admin_access(user="USER")
```
___

## User Groups
### `get_user_groups()`
Fetches user groups imported manually & automatically into MCAS.

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| name | `str` or `list`    | Name(s) of specific user group(s) to fetch information for. Returns a list of all user groups if an value is not provided for name.  | No
| export_fmt | `str`     | Format to export the results in  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
List of User Group Objects. Returns nothing if a value is provided for export_fmt.

##### Example(s)
```python
# Get User Groups
user_groups = api.get_user_groups()

# Get specific User Group
user_groups = api.get_user_groups(name="USER GROUP NAME")
```
___

## Reports
### `get_reports()`
Fetches continuous cloud discovery reports setup in MCAS.

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| name | `str` or `list`     | Name(s) of specific report(s) to fetch information for. If not provided, a list of all reports will be returned  | No
| export_fmt | `str`     | Format to export the results in  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
List of Report objects. Returns nothing if a value is provided for export_fmt.

##### Example(s)
```python
# Get Continuous Cloud Discovery Reports
reports = api.get_reports()

# Get information about a specific report
reports = api.get_reports(name="REPORT NAME")
```
___

## Policies
### `get_policies()`
Fetches MCAS policies

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| names | `str` or `list` | Name(s) of specific policy(ies) to pull information for If not provided all policies are returned  | No
| policy_ids | `str` or `list` | IDs of specific policy to pull information for. If not provided all policies are returned  | No
| verbose | `bool`    | Controls verbosity of console output  | No
| export_fmt | `str`     | Format to export the results in  | No

##### Returns
Lists of Policy objects. Returns nothing if a value is provided for export_fmt.

##### Example(s)
```python
# Get all policies
policies = api.get_policies()

# Get Information about a subset of policies by ID
subset_of_policies_by_id = api.get_policies(policy_ids=["POLICY ID 1", "POLICY ID 2", "POLICY ID 3"])

# Get Information about a subset of policies by name
subset_of_policies_by_name = api.get_policies(names=["POLICY NAME 1", "POLICY NAME 2"])
```
___

## Governance Tasks
### `get_governance_tasks`
Fetches audit logs of MCAS governance tasks

##### Parameters
| **Name**    | **Type** | **Description** | **Required** |
| :--------: | :--------: | ------ | :--------: | 
| body | `dict` | API request body stored as Python dictionary  | No
| params | `dict`    | Query params to include in the request  | No
| export_fmt | `str`     | Format to export the results in  | No
| verbose | `bool`    | Controls verbosity of console output  | No

##### Returns
List of Governance Task dataclass objects. Returns nothing if a value is provided for export_fmt.

##### Example(s)
```python
# Get all governance tasks
governance_tasks = api.get_governance_tasks()
```

# Data Classes & Methods

## Activity

| **Attribute**    | **Type** |
| :--------: | :--------: |
| id | `str` |
| app | `str`    |
| category | `str`     |
| description | `str`    |
| user | `str` |
| upn | `str`    |
| device | `str`     |
| country | `str`    |
| timestamp | `str` |
| ip_address | `str`    |
| user_agent | `str`     |
| machine_id | `str`    |
| isp | `str`    |

### Methods
None
___

## Admin

| **Attribute**    | **Type** |
| :--------: | :--------: |
| username | `str` |
| permission_type | `str`    |
| granted_by | `str`     |
| groups | `list`    |
| reports | `list` |

### Methods
### `revoke_access()`

##### Example(s)
```python
# Get admin access for specific user
mcas_admin = api.get_admin_access(user="USER")

# Revoke access from an MCAS admin
mcas_admin.revoke_access()
```
___

## Alert

| **Attribute**    | **Type** |
| :--------: | :--------: |
| category | `str` |
| description | `str`    |
| date | `str`     |
| id | `str`    |
| resolution_comment | `str` |
| action_required | `int` |
| resolved_by | `str`    |
| url | `str`     |
| resolved_dismissed_date | `str`    |

### Methods
### `get_related_activities()`
Gets all related activities tied to an alert

##### Returns
List of Activity Objects

##### Example(s)
```python
# Fetch Alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Fetch all associated activites for a specific alert
related_activities = alert_by_id.get_related_activities()
```

### `resolve()`
Resolves Alert with optional comment

##### Parameters
| **Name**    | **Type** | **Description** |
| :--------: | :--------: | ------ |
| comment (Optional) | `str`    | Comment to list as reason for resolution. (Default - Resolved via PyCAS by %username%)  |
| verbose (Optional)   | `bool`    | Controls verbosity of console output  |

##### Returns
Request status

##### Example(s)
```python
# Fetch Alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Resolve alert with comment
alert_by_id.resolve(comment="RESOLUTION COMMENT")
```

### `mark_as_read()`
Marks Alert as read

##### Parameters
| **Name**    | **Type** | **Description** |
| :--------: | :--------: | ------ |
| verbose (Optional)   | `bool`    | Controls verbosity of console output  |

##### Returns
Request status

##### Example(s)
```python
# Fetch Alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Marks alert as read
alert_by_id.mark_as_read()
```

### `mark_as_unread()`
Marks Alert as unread

##### Parameters
| **Name**    | **Type** | **Description** |
| :--------: | :--------: | ------ |
| verbose (Optional)   | `bool`    | Controls verbosity of console output  |

##### Returns
Request status

##### Example(s)
```python
# Fetch Alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Marks alert as unread
alert_by_id.mark_alert_as_unread()
```

### `dimiss()`
Marks Alert as unread

##### Parameters
| **Name**    | **Type** | **Description** |
| :--------: | :--------: | ------ |
| comment (Optional) | `str`    | Comment to list as reason for resolution. (Default - Dismissed via PyCAS by %username%)  |
| verbose (Optional)   | `bool`    | Controls verbosity of console output  |

##### Returns
Request status

##### Example(s)
```python
# Fetch Alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Dismiss alert
alert_by_id.dimiss(comment="DISMISSAL COMMENT")
```

### `reopen()`
Reopens Alert

##### Parameters
| **Name**    | **Type** | **Description** |
| :--------: | :--------: | ------ |
| verbose (Optional)   | `bool`    | Controls verbosity of console output  |

##### Returns
Request response

##### Example(s)
```python
# Fetch Alert by ID
alert_by_id = api.get_alerts(alert_id="Alert ID")

# Reopen alert
alert_by_id.reopen()
```
___

## User

| **Attribute**    | **Type** |
| :--------: | :--------: |
| upn | `str` |
| entity_string | `dict`    |
| id | `str`     |
| primary_key | `str`    |

### Methods
### `suspend_account()`
Suspends user account and prevents subsequent sign-ins. This doesn't delete the user's profile or data such as
documents, calendar events, or email messages. However, the user can no longer sign in to the account.

##### Returns
Request response

##### Example(s)
```python
# Fetch user
user = api.get_user(username="USER")

# Suspend user
user.suspend_account()
```

### `unsuspend_account()`
Unsuspends user account.

##### Returns
Request response

##### Example(s)
```python
# Fetch user
suspended_user = api.get_user(username="USER")

# Unsuspend user
suspended_user.unsuspend_account()
```

### `revoke_access_tokens()`
Revokes all access tokens for an account. This will prevent access to any of the organization's data and will
force the user to sign into all applications again.

##### Returns
Request response

##### Example(s)```python
# Fetch user
user = api.get_user(username="USER")

# Expire all existing tokens on users account
user.revoke_user_access_tokens()
```
___

## UserGroup

| **Attribute**    | **Type** |
| :--------: | :--------: |
| name | `str` |
| description | `str`    |
| created_by | `str`     |
| id | `str`    |
| membership_count | `int`    |

### Methods
None

___

## IPRange

| **Attribute**    | **Type** |
| :--------: | :--------: |
| name | `str` |
| id | `str`    |
| category | `str`     |
| subnets | `list`    |
| tags | `list`    |

### Methods
### `delete()`
Deletes IP Range

##### Returns
Request Status

##### Example(s)
```python
# Fetch IP Range
ip_range = api.get_ip_range(name="SPECIFIC IP RANGE NAME")

# Delete IP Range
ip_range.delete()
```

### `update()`
Updates IP Range

##### Parameters
| **Name**    | **Type** | **Description** |
| :--------: | :--------: | ------ |
| new_category (Optional) | `str`    | New category classification for IP Range. The old category is retained if an argument is not provided  |
| add_subnet_masks (Optional) | `str` or `list`    | Subnet masks to add to selected IP Range  |
| remove_subnet_masks (Optional)  | `str` or `list`    | Subnet masks to remove from selected IP Range. Remove all subnet masks by providing a value of 'all'. |
| new_isp (Optional) | `str`    | New ISP for range. The old ISP is retained if an argument is not provided  |
| add_tags (Optional) | `str` or `list`    | List of tags to add to selected IP Range  |
| remove_tags (Optional)  | `str` or `list`     | Name of range to update. Remove all tags by providing a value of 'all'.  |
| verbose (Optional) | `bool`    | Controls verbosity of console output  |

##### Returns
Dictionary of IP Range Properties

##### Example(s)
```python
# Fetch IP Range
ip_range = api.get_ip_range(name="SPECIFIC IP RANGE NAME")

# Update IP Range Properties
ip_range.update(add_tags="TAG1", add_subnet_masks=["CIDR RANGE 1", "CIDR RANGE 2"])
```

___

## GovernanceTask

| **Attribute**    | **Type** |
| :--------: | :--------: |
| id | `str` |
| timestamp | `str`    |
| initiator | `str`     |
| target_object | `str`    |
| task_name | `str`    |
| status | `bool`     |
| status_message | `str`    |
| is_suspend_user_action | `bool`    |

### Methods
None

___

## Policy

| **Attribute**    | **Type** |
| :--------: | :--------: |
| name | `str` |
| description | `str`    |
| enabled | `str`     |
| id | `str`    |
| created | `str`    |
| last_modified | `str`     |
| enable_alerts | `bool`    |
| daily_alert_limit | `int`    |
| created_by | `str`     |
| alert_email_recipients | `str`    |

### Methods
None

___

## Report

| **Attribute**    | **Type** |
| :--------: | :--------: |
| name | `str` |
| id | `str`    |
| created | `str`     |
| last_modified | `str`    |

### Methods
None

___

# Body Argument Formatting

#### Alerts
| **Name**    | **Operators** | **Type** | **Possible Values** | **Example** |
| :--------: | :--------: | :--------: | :--------: | :--------: |
| alert_id | `eq` or `neq` | `str` or `list[str]` | Alert ID(s) formatted as string(s) | "alert_id": {"eq": ["1234", "6789"]} |
| severity | `eq` or `neq` | `str` or `list[str]` | `High`, `Medium`, `Low` | "severity": {"eq": ["High", "Medium"]} |
| resolution_status | `eq` or `neq` | `str` or `list[str]` | `Open`, `Resolved`, `Dismissed` | “resolution_status”: {“eq”: “Dismissed”} |
| users | `eq` or `neq` | `str` or `list[str]` | Any email address(es) in your tenet | “users”: {“eq”: user1@test.com}|
| source | `eq` or `neq` | `str` ore `list[str]` | `Policies` or `Built-in` | “source”: {“eq”:  “Built-in”} |
| application | `eq` or `neq` | `str`, `list[str]`, `int`, or `list[int]` | Any application listed in `references.toml` | “application”: {“eq”: “[12345, 8957, 0053]”} |
| instance | `eq` or `neq` | `str`, `list[str]`, `int`, or `list[int]` | Any instance(s) listed in `references.toml` | “instance”: {“eq”: 8857}|
| read | `eq` or `neq` | `bool` | `True` (Read Alerts) or `False` (Unread Alerts) | “read”: {“eq”: False} |
| date | `gte`, `lte`, `range`, `gte_ndays`, or `lte_ndays` | `str`, or `int` | gte / lte / range: Date(s) formatted as MM/DD/YYYY HH:MM AM/PM, gte_ndays / lte_ndays: Number of days formatted as an integer | “date”: { “range”: { “start”:  “01/01/2019 11:55 AM”, “end”: “01/10/2019 5:35 PM”}}|
| resolution_date | `gte`, `lte`, `range` | `str` or `list[str]` | Date(s) formatted as MM/DD/YYYY HH:MM AM/PM | “resolution_date”: {“gte”: “01/01/2019 11:55 AM”} |
| file_id | `eq` or `neq` | `str` | Any string value | “file_id: {“eq”: 3ffcmmsjf442d} |

#### Activites
| **Name**    | **Operators** | **Type** | **Possible Values** | **Example** |
| :--------: | :--------: | :--------: | :--------: | :--------: |
| application | `eq` or `neq` | `str`, `list[str]`, `int`, or `list[int]` | Any application listed in `references.toml` | “application”: {“eq”: “[12345, 8957, 0053]”}|
| instance | `eq` or `neq` | `str`, `list[str]`, `int`, or `list[int]` | Any instance(s) listed in `references.toml` | “instance”: {“eq”: 8857}|
| org_unit | `eq`, `neq`, `isset`, or `isnotset` | `str` or `list[str]` | Any string or Boolean value | “instance”: {“eq”: 8857}|
| admin_activity | `eq` or `neq` | `bool` | `True` (Only Admin activity) or `False` (non-admin activity) | “admin_activity”: {“eq”: False}|
| impersonated_activity | `eq` or `neq` | `bool` | `True` (Only impersonated activity) or `False` (non-impersonated activity) | “impersonated_activity”: {“eq”: True}|
| device_type | `eq` or `neq` | `str` or `list[str]`| `Desktop`, `Mobile`, `Tablet`, `Other`, `None` | “device_type”: {“eq”: [“Desktop”, “Mobile”]}|
| device_tag | `eq` or `neq` | `str` or `list[str]` | `Compliant`, `Domain Joined`, `Valid Client Certificate` | “device_type”: {“neq”: [“Complaint”, “Domain Joined”]}|
| user_agent_string | `eq` or `neq` | `str` | Any string value | “user_agent_string ”: {“eq”: “Exchange ActiveSync”}|
| user_agent_tag | `eq` or `neq` | `str` or `list[str]` | `Native Client`, `Outdated Browser`, `Outdated Operating System`, `Robot` | “user_agent_tag ”: {“eq”: [“Outdated Browser”, “Outdated Operating System”]|
| country | `eq`, `neq`, `isset`, or `isnotset` | `str` or `list[str]` | Any country(-ies) listed in `references.toml` | “country ”: {“eq”: [“Germany”, “France”, “China”]|
| source | `eq` or `neq` | `str` or `list[str]` | `Access Control`, `Session Control`, `App Connector Analysis`, `Discovery`, or `MDATP` | “source_activities ”: {“eq”: [“Session Control”, “Access Control”] |
| domain | `eq`, `neq`, `isset`, or `isnotset` | `str` or `list[str]` | Any string value | “domain”: {“eq”: [“contoso.com”, “contososchool.edu”]|
| users | `eq`, `neq`, `isset`, `isnotset`, or `startswith` | `str`, `list[str]`, or `bool` | Any email address(es) in your tenet or Boolean value | “users”: {“eq”: user1@test.com}|
| user_groups | `eq`, `neq`, `isset`, `isnotset`, or `startswith` | `str`, `list[str]`, or `bool` | Any user group(s) from `references.toml` or Boolean value | “user_groups”: {“eq”: [“Risky Users”, “Sales – International”} |
| ip_address | `eq`, `neq`, `isset`, `isnotset`, `startswith`, or `doesnotstartwith` | `str`, `list[str]`, or `bool` | Any string or Boolean values | “ip_address”: {“startswith”: “1.2.3”} |
| ip_category | `eq` or `neq` | `str` or `list[str]` | `None`, `Corporate`, `Administrative`, `Risky`, `VPN`, `Cloud Providor`, or `Other` | “ip_category”: {“neq”: [“Corporate”, “VPN”, “Cloud Providor]} |
| registered_isp | `eq`, `neq`, `isset`, `isnotset`, or `contains` | `str`, `list[str]`, or `bool`| Any string or Boolean value | “registered_isp”: {“contains”: [“Verizon”, “Spectrum”, “Google]} |
| date | `gte`, `lte`, `range`, `gte_ndays`, or `lte_ndays` | `str`, or `int` | gte / lte / range: Date(s) formatted as MM/DD/YYYY HH:MM AM/PM, gte_ndays / lte_ndays: Number of days formatted as an integer | “date”: { “range”: { “start”:  “01/01/2019 11:55 AM”, “end”: “01/10/2019 5:35 PM”}}|
| policy | `eq` or `neq` | `str`, or `list[str]` | Any policy listed in `references.toml` | “policy”: {“neq”: “Policy A”} |
| file_id | `eq` | `str` | Any string value | “file_id”: {“eq”: “abc123def456”} |
| file_name | `eq`, `startswith`, or `endswith` | `str` | Any string value | “file_name”: {“startswith”: “super_secret_file_”} |
| activity_objects_item | `eq`, `contains`, or `startswith` | `str` | Any string value | “activity_objects_item”: {“eq”: “keyword123”} |
| activity_objects_id | `eq` or `neq` | `str` or `list[str]` | Any string value | “activity_objects_id”: {“eq”: “9eegcbdb654dd”} |
| investigation_priority | `gt`, `isnotset` or `isset` | `int` or `bool` | Boolean or Integer between 1 (Low) and 10 (High) | “investigation_priority”: {“gt”: 7} |
| event_type | TBD | TBD | TBD | TBD |
| alert_id | `eq` | `str` | Any string value | “alert_id”: {“eq”: “127592fn54gg”} |

#### Governance Tasks
| **Name**    | **Operators** | **Type** | **Possible Values** | **Example** |
| :--------: | :--------: | :--------: | :--------: | :--------: |
| application | `str` | `str` | `str` | `str` |

# Steps for Updating Config/Reference files

### Updating the Reference file (references.toml)

Delete references.toml file. A new copy will be created the next time an MCAS API connection is made.

Windows - `c:\users\%username%\MCAS\cache\references\references.toml`

MacOS - `users/%username%/MCAS/cache/references/references.toml`

If you would like the update the reference file without deleting it you can create an instance of McasApi and run the 
load_custom_references() method. The first instantiation of the class will take a few seconds as it updates the reference 
file. Subsequent instantiations will load normally.
   
```python
McasApi().load_custom_references()
```

### Updating the Config file (config.toml)

Delete config.toml. You will be prompted to enter an API domain and authorization token the next time an MCAS API
connection is made.

Windows - `c:\users\%username%\MCAS\cache\config\config.toml`

MacOS - `users/%username%/MCAS/cache/config/config.toml`

If you would like to update the API domain, authorization token, export location, or export format without deleting the file you can use the 
update_api_token, update_domain, update_export_location, or update_export format methods.

```python
# Update API Authorization Token
McasApi().update_api_token(token="NEW API AUTHORIZATION TOKEN")

# Update API Authorization Token
McasApi().update_domain(domain="NEW API DOMAIN")

# Update Export Location
McasApi().update_export_location(path="NEW PATH")

# Update Export Format
McasApi().update_export_format(fmt="NEW EXPORT FORMAT")
```

# Running Module Tests
During the initial run you will be prompted to enter information so that the subsequent tests can be run. You will not
be prompted on future runs of tests.py.

+ User - Tests fetching information, granting / removing admin access, voiding access tokens, and suspending / enabling
+ Alert ID - Tests fetching, resolving, dismissing, reopening, marking read / unread
+ Activity ID - Tests fetching information on an activity
+ IP Range - Tests fetching information on a custom IP range

To run tests:
```shell script
```

# Acknowledgments
* [Powershell module for Microsoft Cloud App Security](https://github.com/microsoft/MCAS)
* [Adallom API Samples](https://cdn.cloudappsecurity.com/console/0.168.107/api_samples.zip)
* [Microsoft Cloud App Security API Docs](https://docs.microsoft.com/en-us/cloud-app-security/api-introduction)