Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

559-endpoint-log-events - Adds middleware for calling analytics events for each endpoint #622

Merged
merged 33 commits into from
Jun 21, 2022

Conversation

eastandwestwind
Copy link
Contributor

@eastandwestwind eastandwestwind commented Jun 9, 2022

Purpose

Adds middleware for analytics events on endpoints

Changes

Checklist

  • Update CHANGELOG.md file
    • Merge in main so the most recent CHANGELOG.md file is being appended to
    • Add description within the Unreleased section in an appropriate category. Add a new category from the list at the top of the file if the needed one isn't already there.
    • Add a link to this PR at the end of the description with the PR number as the text. example: #1
  • Applicable documentation updated (guides, quickstart, postman collections, tutorial, fidesdemo, database diagram.
  • If docs updated (select one):
    • documentation complete, or draft/outline provided (tag docs-team to complete/review on this branch)
    • documentation issue created (tag docs-team to complete issue separately)
  • Good unit test/integration test coverage
  • This PR contains a DB migration. If checked, the reviewer should confirm with the author that the down_revision correctly references the previous migration before merging
  • The Run Unsafe PR Checks label has been applied, and checks have passed, if this PR touches any external services

Ticket

Fixes #559

@eastandwestwind eastandwestwind changed the title 559-endpoint-log-events Draft middleware for calling analytics events for each endpoint 559-endpoint-log-events - Adds middleware for calling analytics events for each endpoint Jun 14, 2022
src/fidesops/main.py Outdated Show resolved Hide resolved
@@ -15,6 +15,7 @@ module.exports = {
// causes bug in re-exporting default exports, see
// https://github.com/eslint/eslint/issues/15617
'no-restricted-exports': [0],
'import/prefer-default-export': 'off',
Copy link
Contributor Author

@eastandwestwind eastandwestwind Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allows us to directly export function here- https://github.com/ethyca/fidesops/pull/622/files#diff-adfcb02b29653b385133da876800f29f50b8c5bc39875695ebdf2324c63525f7R4

We also have other instances throughout admin-ui where we don't use default exports, so I'm unsure why this wasn't caught before.

async def dispatch_log_request(request: Request, call_next: Callable) -> Response:
fides_source: Optional[str] = request.headers.get("X-Fides-Source")
now: datetime = datetime.now(tz=timezone.utc)
endpoint = f"{request.method}: {request.url.path}"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now HTTP method gets filtered out by a validator in fideslog, but this will be fixed in ethyca/fideslog#66

docker-compose.yml Show resolved Hide resolved
tests/integration_tests/test_execution.py Outdated Show resolved Hide resolved
src/fidesops/main.py Outdated Show resolved Hide resolved
clients/admin-ui/package-lock.json Show resolved Hide resolved
src/fidesops/main.py Outdated Show resolved Hide resolved
src/fidesops/main.py Show resolved Hide resolved
src/fidesops/main.py Outdated Show resolved Hide resolved
docker=in_docker_container(),
event=Event.endpoint_call.value,
event_created_at=event_created_at,
local_host=running_on_local_host(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this always true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're always running on localhost from the perspective of 1. always either running within the docker container or 2. running locally e.g. using pyenv or something. In this way, all ports/addresses are referencing internal/local addresses.

src/fidesops/main.py Show resolved Hide resolved
f = asyncio.Future()
f.set_result(Response(status_code=200))

def mock_call_next(request: Request) -> Any:
Copy link
Contributor Author

@eastandwestwind eastandwestwind Jun 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pattisdr I'm unsure if you'd have any pointers here, but I'm struggling to mock the return of an async function, while also preserving the BackgroundTask that is added to the response here- 53f414e#diff-51868caf44d288309a19f7b59360f148adfa01226f70bf528441cef723439ffcR57

Currently, this code will fail at send_analytics_request_mock.assert_called_once_with() because the background task is not getting added properly using this async mock.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking this out!

@eastandwestwind
Copy link
Contributor Author

eastandwestwind commented Jun 17, 2022

@pattisdr this is ready for another pass. Thanks for being patient! Couple things to note:

  1. I've removed tests for now since everything I tried (including AsyncMock, testing full-stack using the /health endpoint) has produced more things to debug.
  2. Allow 0.0.0.0 host in endpoint property fideslog#67 is another issue on fideslog to allow for the endpoint to include 0.0.0.0 hostname, which fidesops sends in production. For now, I've added code to the fidesops side to unblock us for release before the fideslog portion is merged.

@pattisdr
Copy link
Contributor

thanks @eastandwestwind, I'll review this AM

@pattisdr
Copy link
Contributor

@eastandwestwind what about unit tests of this format? just testing that prepare_and_log_request was called with the correct parameters. I'm just arbitrarily adding them to the patch connections api endpoint tests:

class TestPatchConnections:
...
    @mock.patch('fidesops.main.prepare_and_log_request')
    def test_patch_connections_incorrect_scope_analytics(
        self, mocked_prepare_and_log_request, api_client: TestClient, generate_auth_header, payload
    ) -> None:
        url = V1_URL_PREFIX + CONNECTIONS
        auth_header = generate_auth_header(scopes=[STORAGE_DELETE])
        response = api_client.patch(url, headers=auth_header, json=payload)
        assert 403 == response.status_code
        assert mocked_prepare_and_log_request.called
        call_args = mocked_prepare_and_log_request._mock_call_args[0]

        assert call_args[0] == 'PATCH: http://testserver/api/v1/connection'
        assert call_args[1] == 403
        assert isinstance(call_args[2], datetime)
        assert call_args[3] is None
        assert call_args[4] == 'HTTPException'

    @mock.patch('fidesops.main.prepare_and_log_request')
    def test_patch_http_connection_successful_analytics(
        self, mocked_prepare_and_log_request, api_client, db: Session, generate_auth_header, url
    ):
        auth_header = generate_auth_header(scopes=[CONNECTION_CREATE_OR_UPDATE])
        payload = [
            {
                "name": "My Post-Execution Webhook",
                "key": "webhook_key",
                "connection_type": "https",
                "access": "read",
            }
        ]
        response = api_client.patch(url, headers=auth_header, json=payload)
        assert 200 == response.status_code
        body = json.loads(response.text)
        assert body["succeeded"][0]["connection_type"] == "https"
        http_config = ConnectionConfig.get_by(db, field="key", value="webhook_key")
        http_config.delete(db)

        call_args = mocked_prepare_and_log_request._mock_call_args[0]

        assert call_args[0] == 'PATCH: http://testserver/api/v1/connection'
        assert call_args[1] == 200
        assert isinstance(call_args[2], datetime)
        assert call_args[3] is None
        assert call_args[4] is None

@pattisdr pattisdr added the run unsafe ci checks Triggers running of unsafe CI checks label Jun 21, 2022
Copy link
Contributor

@pattisdr pattisdr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work @eastandwestwind

Let's also make sure that the landing page that @TheAndrewJackson is working on is adding this X-Fides-Source to the request headers there too

@pattisdr pattisdr merged commit 0cc31c1 into main Jun 21, 2022
@pattisdr pattisdr deleted the 559-endpoint-log-events branch June 21, 2022 16:27
sanders41 pushed a commit that referenced this pull request Sep 22, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
run unsafe ci checks Triggers running of unsafe CI checks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Fideslog] Send an analytics event for each endpoint
4 participants