Scoutr provides full access control over the endpoints a set of users is permitted to call and the output that is
returned. This is done using field filters, field exclusions, and permitted endpoints, which are outlined in the next
section.

This access control functionality is implemented at both a user and a group level. A user can be a member of zero or
more groups. The implementation of [auth identifiers](#auth-identifier) and [groups](#groups) is outlined in their
respective sections.

The two types of access control supported are via API Gateway or via OIDC. Helper functions have been created for
each access control type to assist with passing the correct request format into Scoutr.

### API Gateway Authentication
For API Gateway authentication, the request format is generated by the `build_api_gateway_request` method.

#### Example
Refer to the [example serverless endpoint](https://github.com/GESkunkworks/scoutr/blob/master/example/api_gateway/example/endpoints/list.py)

### OIDC Authentication
It is assumed that there is an Apache server running in front of the application that performs OIDC authentication
and passes the OIDC claims as headers.

The simplest method to setup the API is to use [Flask API](https://www.flaskapi.org/). Helper methods have been
provided to make the setup as simple as possible. The [`init_flask`](https://github.com/GESkunkworks/scoutr/blob/master/scoutr/flask/routes.py#L11) method
automatically generates the belows endpoints:

- GET `/user/` - Returns information about the authenticated user
- POST `/user/has-permission/` - Determine if user has permission to access an endpoint. The body of this request should
    contain `method` and `path` keys as JSON.
- GET `/<primary_list_endpoint>/` - Primary endpoint used to list data. The value of `primary_list_endpoint` is determined
    by an argument passed to `init_flask()`
- GET `/audit/` - List and search all audit logs
- GET `/audit/<item>/` - List audit logs for a particular resource
- GET `/history/<item>/` - Show history for a particular resource
- POST `/search/<search_key>/` - Search endpoint that allows searching by any key for one or more values. The body of
    this request should be a JSON list of values.

#### Example
Refer to the [example flask application](https://github.com/GESkunkworks/scoutr/blob/master/example/flask/main.py)

### Concepts

#### Field filters

List of field filters to apply to queries by this group. Each item in this list must be structured as:

If the type of `value` is a string, it will be filtered using a `field = value` operation. To support multiple
values for a single field, tf the type of `value` is a list, it will be filtered using a
`field IN ['value1', 'value2', ..., 'valueN']` operation. When multiple field filters are specified, they are
combined together using an `AND` operation.

##### Syntax
```json
[
    {"field": "field1", "value": "filter_value"},
    {"field": "field2", "value": ["value1", "value2"]},
]
```

#### Field exclusions

Field exclusions allow for excluding one or more fields from the output of all queries. These fields are from any output
during the post-processing phase of all queries. Additionally, if a user attempts to create or update an item that
contains a field from this list, the operation will be denied.

##### Syntax
```json
[
    "field1",
    "field2"
]
```

#### Permitted endpoints

Before taking any action, every call from API gateway is validated to ensure the user has permissions to
perform the call. For convenience, regular expressions can be used within the `endpoint` field.

##### Syntax
```json
[
    {"method": "GET|POST|PUT|DELETE", "endpoint": "/endpoint"},
    {"method": "GET|POST|PUT|DELETE", "endpoint": "^/endpoint2/.+$"}
]
```

### Groups

A group object be made up of:

- `group_id` - Identifier for the group
- `permitted_endpoints` - Optional list of permitted endpoints
- `filter_fields` - Optional list of field filters
- `exclude_fields` - Optional list of field exclusions
- `update_fields_permitted` - Optional list of the only fields that can be updated
- `update_fields_restricted` - Optional list of fields to restrict updates for

The name of the group table must be passed in to the constructor.

#### Example
```json
{
    "group_id": "read-only",
    "permitted_endpoints": [
        {
            "endpoint": "^/account/.+$",
            "method": "GET"
        },
        {
            "endpoint": "^/accounts.*$",
            "method": "GET"
        },
        {
            "endpoint": "^/search/.+$",
            "method": "POST"
        }
    ],
    "exclude_fields": [
        "field1"
    ],
    "update_fields_permitted": [
        "field4"
    ],
    "update_fields_restricted": [
        "field5"
    ],
    "filter_fields": [
        {
            "field": "field2",
            "value": "value1"
        },
        {
            "field": "field3",
            "value": [
                "value2",
                "value3"
            ]
        }
    ]
}
```

### Auth Identifier

#### Types

There are three types of accepted authentication identifiers:

- USERNAME
- OIDC_GROUP
- API_KEY

Though not required, it is recommended for each object type to have a `type` key that corresponds to its
authentication type (OIDC_GROUP, USERNAME, or API_KEY).

The field requirements for each object type are outlined in the following sections

##### USERNAME
- id (partition key) - this is the user's username

Though not required, it is recommended to also include a `name` field containing the user's full name to make it
easier to identify the user at a glance.

##### OIDC_GROUP
- id (partition key) - this is expected to be the group id

Though not required, it is recommended to also include a `name` field containing the group's display name to make it
easier to identify the group at a glance.

If a user is a member of more than one OIDC group, the permissions granted by each configured group will be combined
together to generate the effective permissions applied to the user.

##### API_KEY

- id (partition key) - this is the api key id
- name
- username
- email

#### Groups

Optionally, each auth object can include a `groups` object, which should be a list of group ids that the user is a
member of:
```json
{
    "groups": [
        "read-only",
        "view-audit-logs"
    ]
}
```

Any permissions defined in the groups are combined together to make up the user's permissions. In addition, the same
permissions that a group defines (`filter_fields`, `exclude_fields`, `update_fields_permitted`,
`update_fields_restricted`, `permitted_endpoints`) can be expressed at the user level. These permissions will be
combined together with the permissions outlined in the groups the user is a member of. Permissions defined at the user
level **DO NOT** override those specified at the group level - they are combined.

If a user is a member of multiple OIDC groups, the permissions are combined such that

- Any `filter_fields` definied in a child `groups` block are combined together with an `AND` expression
- All of the combined permissions in each OIDC group the user is a member of are combined together with an `OR` expression

The name of the user table must be passed in to the constructor.

### Audit Logs

For every authorized, successful call to the API, an entry will be logged in the audit log table. Each record will
follow the below format:

```json
{
  "action": "CREATE|UPDATE|DELETE|GET|LIST|SEARCH|{CUSTOM-ACTION}",
  "body": {
    "key": "value"
  },
  "method": "HTTP method from API gateway",
  "path": "/endpoint/path",
  "path_params": {
    "key": "value"
  },
  "query_params": {
    "key": "value"
  },
  "resource": {
    "key": "value"
  },
  "time": "2019-10-04T18:44:30.166635",
  "user": {
    "api_key_id": "ID",
    "name": "John Doe",
    "source_ip": "1.2.3.4",
    "username": "222222222",
    "user_agent": "curl"
  }
}
```

The following fields may not be included or may not have values for all types of actions:

- body
- query_params
- path_params
- resource
