This repository showcases the solution to the coding challenege given by Sqreen to me as a part of the interview process.
- On receiving a notification the application should
- Check that the signature is correct.
- Redispatch the notification to multiple targets (e.g. log, email, HTTP, SMS, Slack).
The project requires the candidate to build a flask app, that listens for webhook notifications from Sqreen. Here the details about the project
-
The project is built using Python 3.6+, pyest as the testing framework.
-
We don't particularly need two apps (one to be attacked [but sqreened] & one to receive notifications) but can use the same app to do both, this has been solved by parsing the incoming notifications, if application_id in notification JSON represents the application which is receiving notifications, this means our server has a security issue.
-
If a such a scenario arises, apart from dispatching notification to the target backends, we also notify the admins of the server or application using the available backends we have like SMS, EMAIL, SLACK etc, as well also tag the logs with a tag: [INTENAL SECURITY ALERT]
- Example:
2019-11-21 05:24:21,308: INFO]: [INTERNAL SECURITY ALERT]: {'humanized_title': 'Massive account takeover attempts on [email protected]', 'pulse_genre': 'account_takeover', 'environment': 'production', 'date_started': '2019-11-20T21:47:00.090825+00:00', 'date_ended': None, 'blocked': True, 'url': 'https://my.sqreen.com/application/5dd058775c3feb0021c0bb43/pulses/5dd5b454e56c7e0024169b4b'}
- Example:
-
I have used to celery to process notifications dispatch to different backends, because the code has been written by keeping in mind I am actually building an app for production ready use. RabbitMQ has been used the broker for Celery. Each component can be scaled separately.
-
flaskr is the project directory which contains the application code for our server.
-
tests is the project directory which contains the tests.
-
tests/conftest.py contains the required fixtures for our tests.
-
flaskr/utils module contains helper classes used to for verifying the signature of an incoming request and processing and dispatching the incoming notifciation to target backends.
-
flaskr/config.py, has project wide configurations and constants.
-
API for this project has been versioned, the flaskr/v1 folder the concerned controller and decorators used to handle the incoming request.
-
The end-point for the webhook is
https://{{host_url}}/v1/webhook
, it listens for a POST request- A valid incoming request will be responded with a response code of
200 - OK
, otherwise if the the request does have the required header or the header is wrong, API responds with the response code of401 - UnAuthorized
or if the structure of request is wrong the API responds with response code of422 - Unprocessable Entity
.
- A valid incoming request will be responded with a response code of
-
There is a generic interface for dispatching a notification to target backends, named:
TaskProcessor
this class abstracts the process of dispatching an incoming notification from Sqreen. You can find theTaskProcessor
class here. -
There is a generic interface for target backends, named:
BackendProcessor
this class abstracts the process of dispatching an incoming notification from Sqreen. You can find theBackendProcessor
class here. -
As per the implementation details of the target backends, i.e: how will the notifcation will be send to backends like: SMS, SLACK or EMAIL, each backend has a specific {Backend}Manager class. The implementation of sending notification to a specific Backend has not been implemented but a basic code structure has been done, as it was in the scope of the task.
-
There are two types of test written.
- Functional tests, that tests the overall functionality of our API.
- Unit tests, that tests the a single module over various test cases.
- Pytest is the testing framework used.
- Incoming request has been considered of JSON format, with content-type as
application/json
- Since the incoming request contains a collection of events, a JSON request that is not an array will be rejected with
response code - 422, Unprocessable Entity.
. - The problem has been solved by building it as a production ready software.
- The code has been made modular by methods to carry out small tasks, this makes it easy to read as well as easily debuggable.
- The code runs with a runtime complexity of O(n), where n is the number of events passed in the incoming JSON.
-
Put your signature key in the local environment under:
SIGNATURE_KEY
. -
Put your app token from sqreen in the local environment under
SQREEN_TOKEN
. -
Put your application id from sqreen dashboard in the local environment under:
SCREEN_APPLICATION_ID
-
Put your application name from sqreen dashboard under:
SQREEN_APP_NAME
. -
Create a virtual environment, by
virtualenv venv
. -
$ source venv/bin/activate
. -
Install the requirements from requirements.txt
-
In the root directory of the project, to run the flask server.
export FLASK_APP=flaskr
export FLASK_ENV=production/development
flask run
- Logs will be stored in
logs/server.log
-
To run celery.
$ rabbitmq-server
- In the root directory of the project, to run the celery worker.
$ celery -A flaskr.celery.tasks worker --loglevel=info -f logs/celery.log
- Logs will be stored in
logs/celery.log
-
To run tests.
- Once all above steps are completed.
$ pyest