Skip to content

2.0.0

Compare
Choose a tag to compare
@MichaelPalmer1 MichaelPalmer1 released this 07 Dec 21:17
· 5 commits to master since this release
2a79b13

Scoutr 2.0.0

This is a major, major, major code refactor with multiple breaking changes. The codebase has been refactored to be much cleaner and more extensible for multiple NoSQL backend providers. It brings it up to the same level of support as the Go version.

New Features

  • Added GCP Firestore (FirestoreAPI) and MongoDB (MongoAPI) support
  • sentry_sdk is now mocked if the package is not installed
  • For OIDC-focused deployments, the Config object accepts parameters to specify the name of the OIDC headers to pull user and group information from. Note that MongoAPI uses a MongoConfig class instead of the Config class.
  • Ability to use operators on user filter fields (now rebranded as read filters)
  • More granular filtering per action

Changes

  • BREAKING: Primary key of the group table is changing (see below)
  • BREAKING: Renamed and moved scoutr.dynamo.DynamoAPI.value_in_list to scoutr.utils.value_in_set. It now expects a set as input instead of a list
  • BREAKING: DynamoAPI constructor expects a Config instance as input instead of a series of parameters
  • BREAKING: Deleted scoutr.dynamo.DynamoAPI. It is replaced by scoutr.providers.aws.DynamoAPI.
  • BREAKING: Moved scoutr.api_gateway to scoutr.helpers.api_gateway
  • BREAKING: Moved scoutr.flask to scoutr.helpers.flask
  • BREAKING: Renamed filter_fields to read_filters
  • Modified how multiple filters targeting the same field are combined together (see below)

Group Table

  • BREAKING: The primary key used in the groups table has been changed from group_id to id to remain consistent with the auth table. This may require the underlying table to be recreated.

Validation

  • An additional argument required_fields is now available on CREATE operations. Validation will be performed using the validation object like before. If a field in the validation object does not exist in the request body, that validation is skipped
  • Each validation function runs in a separate thread to improve performance

Error output

In the event of any validation failures on creates/updates, an error dictionary will be returned that maps the failed key to the error message:

{
    "errors": {
        "date": "Date must be formatted as YYYY-MM-DD",
        "id": "Id is not valid",
        "name": "Name is not valid"
    }
}

If there is a single error otherwise, the response will be the same as before:

{
  "error": "User is not authorized"
}

Filtering

  • Added create_filters, update_filters, and delete_filters to allow for more granular filtering based on the action being performed by the user.

  • FilterField objects now support an operator where you can specify a specific operation to perform. The supported operations are the same as the operators used in filtering (i.e. eq, ne, in, not in, etc.)

    {
      "field": "type",
      "operator": "ne",
      "value": "Test"
    }

Filter Merging

The way that filters are merged together has changed. Now, for all filters that target the same key, those filters will be run together with an OR operation. Then, those filters will be combined with filters for other keys using an AND operation. For example:

{
    "read_filters": [
      	// Inherited from group1
        {
            "field": "product",
            "operator": "contains",
            "value": "ABC"
        },
        // Inherited from group2
        {
            "field": "product",
            "operator": "ne",
            "value": "ABCDEF"
        },
      	// Inherited from group3
        {
            "field": "status",
            "value": "Active"
        }
    ]
}

Previously, this would have generated a query expression that would return no results because the two product filters would be conflicting:

status = "Active" AND product CONTAINS "ABC" AND product != "ABCDEF"

However, using the new filter merging methodology, the generated query will be:

status = "Active" AND (
	product CONTAINS "ABC" OR product != "ABCDEF"
)

Granular Filtering

Create Filters

When performing a CREATE action, the filtering is applied as follows:

  1. Ensure user has permission to set the fields they have included in the body. This checks against the values in exclude_fields. If any matches are found, an exception is thrown.
  2. Run field validation
  3. Run create_filters. If the filter criteria fails, the request will be denied with an Unauthorized error.

Update Filters

When performing an UPDATE action, the filtering is applied as follows:

  1. Use the update_filters to determine if the user has permissions to access the item. If no item is found (i.e. it does not exist or user does not have permissions to access the item), then a Not Found error will be thrown.
  2. Use update_fields_permitted, update_fields_restricted, and exclude_fields to determine if the user has permissions to update the fields they specified in the request body. If either of these criteria fail, an Unauthorized exception will be thrown
  3. Run update_filters against the existing item merged together with the user’s desired updates. If the filter criteria fails, the request will be denied with an Unauthorized error.
  4. Run field validation as before.
Example

User permissions:

{
  "update_filters": [
    {
      "field": "product",
      "operator": "eq",
      "value": "a"
    }
  ]
}

Record in the database:

{
  "product": "a",
  "approved": false
}

If a user tried to perform the below update:

{
  "product": "b",
  "approved": true
}

This update would be denied because the update_filters do not permit the user to modify the product key to any value that is not equal to “a”. However, this update would be permitted:

{
  "approved": true,
  "reason": "approved by user"
}

Similarly, if the user wanted to update the below record:

{
  "product": "b",
  "approved": false
}

This would be denied because they do not have permissions to update an item where the product key is not equal to “a”.

Delete Filters

When performing a DELETE action, the filtering is applied as follows:

  1. Use the delete_filters to determine if the user has permissions to delete the item. If no item is found (i.e. it does not exist or the user does not have permissions to delete it), then the request will be denied with a Bad Request error.