Skip to content

Commit 4932539

Browse files
awolfdenAdam Wolfman
andauthored
Add audit logs example application (#24)
* Add audit logs example application * Update README * Update readme step numbers Co-authored-by: Adam Wolfman <[email protected]>
1 parent 86fe69f commit 4932539

File tree

20 files changed

+1028
-5
lines changed

20 files changed

+1028
-5
lines changed

python-flask-admin-portal-example/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ MarkupSafe==2.0.1
99
requests==2.26.0
1010
urllib3==1.26.7
1111
Werkzeug==2.0.1
12-
workos==1.14.0
12+
workos==1.16.0
1313
python-dotenv
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
pip-wheel-metadata/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
*.py,cover
51+
.hypothesis/
52+
.pytest_cache/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
target/
76+
77+
# Jupyter Notebook
78+
.ipynb_checkpoints
79+
80+
# IPython
81+
profile_default/
82+
ipython_config.py
83+
84+
# pyenv
85+
.python-version
86+
87+
# pipenv
88+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91+
# install all needed dependencies.
92+
#Pipfile.lock
93+
94+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95+
__pypackages__/
96+
97+
# Celery stuff
98+
celerybeat-schedule
99+
celerybeat.pid
100+
101+
# SageMath parsed files
102+
*.sage.py
103+
104+
# Environments
105+
.env
106+
.venv
107+
env/
108+
venv/
109+
ENV/
110+
env.bak/
111+
venv.bak/
112+
113+
# Spyder project settings
114+
.spyderproject
115+
.spyproject
116+
117+
# Rope project settings
118+
.ropeproject
119+
120+
# mkdocs documentation
121+
/site
122+
123+
# mypy
124+
.mypy_cache/
125+
.dmypy.json
126+
dmypy.json
127+
128+
# Pyre type checker
129+
.pyre/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python.formatting.provider": "black"
3+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 WorkOS
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# python-flask-audit-logs-example
2+
3+
An example Flask application demonstrating how to use the [WorkOS Python SDK](https://github.com/workos/workos-python) to send and retrieve Audit Log events. This example is not meant to show a real-world example of an Audit Logs implementation, but rather to show concrete examples of how events can be sent using the Python SDK.
4+
5+
## Prerequisites
6+
7+
- Python 3.6+
8+
9+
## Flask Project Setup
10+
11+
1. Clone the main git repo for these Python example apps using your preferred secure method (HTTPS or SSH).
12+
13+
```bash
14+
# HTTPS
15+
$ git clone https://github.com/workos/python-flask-example-applications.git
16+
```
17+
18+
or
19+
20+
```bash
21+
# SSH
22+
$ git clone [email protected]:workos/python-flask-example-applications.git
23+
```
24+
25+
2. Navigate to the Audit Logs app within the cloned repo.
26+
27+
```bash
28+
$ cd python-flask-example-applications/python-flask-audit-logs-example
29+
```
30+
31+
3. Create and source a Python virtual environment. You should then see `(env)` at the beginning of your command-line prompt.
32+
33+
```bash
34+
$ python3 -m venv env
35+
$ source env/bin/activate
36+
(env) $
37+
```
38+
39+
4. Install the cloned app's dependencies.
40+
41+
```bash
42+
(env) $ pip install -r requirements.txt
43+
```
44+
45+
5. Obtain and make note of the following values. In the next step, these will be set as environment variables.
46+
47+
- Your [WorkOS API key](https://dashboard.workos.com/api-keys)
48+
- Your [WorkOS Client ID](https://dashboard.workos.com/configuration)
49+
50+
6. Ensure you're in the root directory for the example app, `python-flask-audit-logs-example/`. Create a `.env` file to securely store the environment variables. Open this file with the Nano text editor. (This file is listed in this repo's `.gitignore` file, so your sensitive information will not be checked into version control.)
51+
52+
```bash
53+
(env) $ touch .env
54+
(env) $ nano .env
55+
```
56+
57+
7. Once the Nano text editor opens, you can directly edit the `.env` file by listing the environment variables:
58+
59+
```bash
60+
WORKOS_API_KEY=<value found in step 6>
61+
WORKOS_CLIENT_ID=<value found in step 6>
62+
APP_SECRET_KEY=<any string value you\'d like>
63+
```
64+
65+
To exit the Nano text editor, type `CTRL + x`. When prompted to "Save modified buffer", type `Y`, then press the `Enter` or `Return` key.
66+
67+
8. Source the environment variables so they are accessible to the operating system.
68+
69+
```bash
70+
(env) $ source .env
71+
```
72+
73+
You can ensure the environment variables were set correctly by running the following commands. The output should match the corresponding values.
74+
75+
```bash
76+
(env) $ echo $WORKOS_API_KEY
77+
(env) $ echo $WORKOS_CLIENT_ID
78+
```
79+
80+
9. The final setup step is to start the server.
81+
82+
```bash
83+
(env) $ flask run
84+
```
85+
86+
If you are using macOS Monterey and port 5000 is not available and you'll need to start the app on a different port with this slightly different command.
87+
88+
```bash
89+
(env) $ flask run -p 5001
90+
```
91+
92+
You'll know the server is running when you see no errors in the CLI, and output similar to the following is displayed:
93+
94+
```bash
95+
* Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
96+
* Environment: production
97+
WARNING: This is a development server. Do not use it in a production deployment.
98+
Use a production WSGI server instead.
99+
* Debug mode: off
100+
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
101+
```
102+
103+
Navigate to `localhost:5000`, or `localhost:5001` depending on which port you launched the server, in your web browser. You should see the WorkOS logo and a place to enter your Organization ID. The next steps occur in your WorkOS dashboard.
104+
105+
You can stop the local Flask server for now by entering `CTRL + c` on the command line.
106+
107+
## Audit Logs Setup with WorkOS
108+
109+
10. Follow the [Audit Logs configuration steps](https://workos.com/docs/audit-logs/emit-an-audit-log-event/sign-in-to-your-workos-dashboard-account-and-configure-audit-log-event-schemas) to set up the following 5 events that are sent with this example:
110+
111+
Action title: "user.signed_in" | Target type: "team"
112+
Action title: "user.logged_out" | Target type: "team"
113+
Action title: "user.organization_set" | Target type: "team"
114+
Action title: "user.organization_deleted" | Target type: "team"
115+
Action title: "user.connection_deleted" | Target type: "team"
116+
117+
11. Next, take note of the Organization ID for the Org which you will be sending the Audit Log events for. This ID gets entered into the splash page of the example application.
118+
119+
12. Once you enter the Organization ID and submit it, you will be brought to the page where you'll be able to send the audit log events that were just configured. You'll also notice that the action of setting the Organization triggered an Audit Log already. Click the buttons to send the respective events.
120+
121+
13. To obtain a CSV of the Audit Log events that were sent for the last 30 days, click the "Export Events" button. This will bring you to a new page where you can download the events. Downloading the events is a 2 step process. First you need to create the report by clicking the "Generate CSV" button. Then click the "Access CSV" button to download a CSV of the Audit Log events for the selected Organization for the past 30 days.
122+
123+
## Need help?
124+
125+
If you get stuck and aren't able to resolve the issue by reading our API reference or tutorials, you can reach out to us at [email protected] and we'll lend a hand.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import json
2+
import os
3+
from urllib.parse import urlparse, parse_qs
4+
from flask import Flask, session, redirect, render_template, request, url_for
5+
import workos
6+
from datetime import datetime, timedelta
7+
from audit_log_events import (
8+
user_signed_in,
9+
user_logged_out,
10+
user_connection_deleted,
11+
user_organization_deleted,
12+
user_organization_set,
13+
)
14+
15+
# Flask Setup
16+
DEBUG = False
17+
app = Flask(__name__)
18+
app.secret_key = os.getenv("APP_SECRET_KEY")
19+
20+
# WorkOS Setup
21+
22+
workos.api_key = os.getenv("WORKOS_API_KEY")
23+
workos.project_id = os.getenv("WORKOS_CLIENT_ID")
24+
workos.base_api_url = "http://localhost:7000/" if DEBUG else workos.base_api_url
25+
26+
27+
def to_pretty_json(value):
28+
return json.dumps(value, sort_keys=True, indent=4)
29+
30+
31+
app.jinja_env.filters["tojson_pretty"] = to_pretty_json
32+
33+
34+
@app.route("/", methods=["POST", "GET"])
35+
def index():
36+
try:
37+
return render_template(
38+
"send_events.html",
39+
organization_id=session["organization_id"],
40+
)
41+
except KeyError:
42+
return render_template("login.html")
43+
44+
45+
@app.route("/set_org", methods=["POST", "GET"])
46+
def set_org():
47+
organization_id = request.form["org"]
48+
session["organization_id"] = organization_id
49+
organization_set = workos.client.audit_logs.create_event(
50+
organization_id, user_organization_set
51+
)
52+
return redirect("/")
53+
54+
55+
@app.route("/send_event", methods=["POST", "GET"])
56+
def send_event():
57+
event_type = request.form["event"]
58+
organization_id = session["organization_id"]
59+
events = [
60+
user_signed_in,
61+
user_logged_out,
62+
user_organization_deleted,
63+
user_connection_deleted,
64+
]
65+
event = events[int(event_type)]
66+
organization_set = workos.client.audit_logs.create_event(organization_id, event)
67+
return redirect("/")
68+
69+
70+
@app.route("/export_events", methods=["POST", "GET"])
71+
def export_events():
72+
organization_id = session["organization_id"]
73+
return render_template("export_events.html", organization_id=organization_id)
74+
75+
76+
@app.route("/get_events", methods=["POST", "GET"])
77+
def get_events():
78+
organization_id = session["organization_id"]
79+
event_type = request.form["event"]
80+
today = datetime.today()
81+
last_month = today - timedelta(days=30)
82+
last_month_iso = last_month.isoformat()
83+
today_iso = today.isoformat()
84+
85+
if event_type == "generate_csv":
86+
create_export_response = workos.client.audit_logs.create_export(
87+
organization=organization_id,
88+
range_start=last_month_iso,
89+
range_end=today_iso,
90+
)
91+
session["export_id"] = create_export_response.to_dict()["id"]
92+
93+
if event_type == "access_csv":
94+
export_id = session["export_id"]
95+
fetch_export_response = workos.client.audit_logs.get_export(export_id)
96+
return redirect(fetch_export_response.to_dict()["url"])
97+
98+
return redirect("export_events")
99+
100+
101+
@app.route("/logout")
102+
def logout():
103+
session.clear()
104+
return redirect("/")

0 commit comments

Comments
 (0)