From d8dae95022fddc9ac39b5a825ecee8474f8b7895 Mon Sep 17 00:00:00 2001
From: Lionel Trebuchon
Date: Sat, 23 Nov 2019 01:33:08 +0100
Subject: [PATCH 01/13] Enhance documentation, take note about unregistering
from events mailing list
---
.gitignore | 3 +++
README.md | 9 +++++++--
amivapi/events/email_links.py | 7 ++++++-
3 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 91512f95..d23eb761 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,6 @@ config.yaml
amivapi/config.yaml
apikeys.yaml
amivapi_storage
+
+# Pycharm
+.idea/*
\ No newline at end of file
diff --git a/README.md b/README.md
index bb0c533c..e5ac7466 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ feel free to fork and modify.
If you only want to use AMIV API, check out the online documentation
(There's a link in the github description above).
-If you are an administrator and wish to get the AMIV API running, keep reading!
+If you are an administrator and wish to get the AMIV API r`unning, keep reading!
If you are a developer looking to work on AMIV API, it's best to look at the
code directly. You can start with [bootstrap.py](amivapi/bootstrap.py),
@@ -72,8 +72,13 @@ The following command runs a MongoDB service available on the default port
password `amivapi`.
```sh
+# Initialize "swarm", a scheduling and clustering tool, that will enable us to create a network overlay
+docker swarm init
+
# Create a network so that the api service can later be connected to the db
-docker network create --driver overlay backend
+docker network create --driver overlay backend # Overlay so that it is representative of the real-life AMIV structure
+
+#
docker service create \
--name mongodb -p 27017:27017 --network backend\
-e MONGODB_DATABASE=amivapi \
diff --git a/amivapi/events/email_links.py b/amivapi/events/email_links.py
index 80180317..aa5f4ad0 100644
--- a/amivapi/events/email_links.py
+++ b/amivapi/events/email_links.py
@@ -10,7 +10,7 @@
from bson import ObjectId
from eve.methods.delete import deleteitem_internal
from eve.methods.patch import patch_internal
-from flask import Blueprint, current_app, redirect
+from flask import Blueprint, current_app, redirect, request
from itsdangerous import BadSignature, URLSafeSerializer
from amivapi.events.queue import update_waiting_list
@@ -72,6 +72,11 @@ def on_delete_signup(token):
deleteitem_internal('eventsignups', concurrency_check=False,
**{current_app.config['ID_FIELD']: signup_id})
+ # Verify if user confirmed
+ # definitive = request.args.get('definitive')
+ # if definitive:
+
+ # Removal
redirect_url = current_app.config.get('SIGNUP_DELETED_REDIRECT')
if redirect_url:
return redirect(redirect_url)
From 4d84d0698801f52e65c5990381e0233ce0e2d8e8 Mon Sep 17 00:00:00 2001
From: Lionel Trebuchon
Date: Sat, 23 Nov 2019 17:54:43 +0100
Subject: [PATCH 02/13] Enhance README.md with how to run API locally (Python)
---
README.md | 37 +++++++++++++++++++------------------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/README.md b/README.md
index e5ac7466..f11654ef 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ You only need to install Docker, nothing else is required.
### Manual Installation for Development
-For development, we recommend to clone the repository and install AMIV API
+For development, we recommend to **fork** the repository and install AMIV API
manually.
First of all, we advise using a [virtual environment](https://docs.python.org/3/tutorial/venv.html).
@@ -107,7 +107,7 @@ Now it's time to configure AMIV API. Create a file `config.py`
ROOT_PASSWORD = 'root'
# MongoDB Configuration
-MONGO_HOST = 'mongodb'
+MONGO_HOST = 'mongodb' # or 'localhost' if you run the API from Python
MONGO_PORT = 27017
MONGO_DBNAME = 'amivapi'
MONGO_USERNAME = 'amivapi'
@@ -157,7 +157,7 @@ Configuration files can be used easily for services using
docker config create amivapi_config config.py
```
-Now start the API service (make sure to put it in the same network as MongoDB
+Now start the API service (make sure to put it in the same network (here `backend`) as MongoDB
if you are running a MongoDB service locally).
```sh
@@ -174,6 +174,13 @@ docker service create \
--name amivapi-cron --network backend \
--config source=amivapi_config,target=/api/config.py \
amiveth/amivapi amivapi cron --continuous
+
+# To attach your command line to the docker instance, use
+docker attach amivapi{...} # Use tab completion to find the name of the service
+
+# As we run docker as a service, it restarts by itself even if you use docker kill
+# To stop the service, use
+docker service rm amivapi
```
(If you want to mount the config somewhere else, you can use the environment
@@ -181,23 +188,17 @@ variable `AMIVAPI_CONFIG` to specify the config path in the container.)
### Run locally
-If you have installed AMIV API locally, you can use the CLI to start it:
-
-```sh
-# Start development server
-amivapi run dev
+If you have installed AMIV API locally, you can use the CLI to start it.
-# Start production server (requires the `bjoern` package)
-amivapi run prod
+First, change `MONGO_HOST = 'mongodb'` in `config.py` to `'MONGO_HOST = 'localhost'`.
+Then, in CLI:
-# Execute scheduled tasks periodically
-amivapi cron --continuous
-
-# Specify config if its not `config.py` in the current directory
-amivapi --config run dev
-
-# Get help, works for sub-commands as well
-amivapi --help
+```sh
+amivapi run dev # Start development server
+amivapi run prod # Start production server (requires the `bjoern` package)
+amivapi cron --continuous # Execute scheduled tasks periodically
+amivapi --config run dev # Specify config if its not `config.py` in the current directory
+amivapi --help # Get help, works for sub-commands as well
amivapi run --help
```
From cb1ec323b5c43c638aa1d450d86058fc89ca7ef1 Mon Sep 17 00:00:00 2001
From: Lionel Trebuchon
Date: Sat, 23 Nov 2019 18:22:50 +0100
Subject: [PATCH 03/13] WIP on adding one security to unregister from event
---
amivapi/events/email_links.py | 39 +++++++----
.../events/templates/unregister_event.html | 67 +++++++++++++++++++
amivapi/settings.py | 4 +-
amivapi/utils.py | 2 +
4 files changed, 98 insertions(+), 14 deletions(-)
create mode 100644 amivapi/events/templates/unregister_event.html
diff --git a/amivapi/events/email_links.py b/amivapi/events/email_links.py
index aa5f4ad0..6687183e 100644
--- a/amivapi/events/email_links.py
+++ b/amivapi/events/email_links.py
@@ -10,7 +10,7 @@
from bson import ObjectId
from eve.methods.delete import deleteitem_internal
from eve.methods.patch import patch_internal
-from flask import Blueprint, current_app, redirect, request
+from flask import Blueprint, current_app, redirect, request, make_response, render_template, g
from itsdangerous import BadSignature, URLSafeSerializer
from amivapi.events.queue import update_waiting_list
@@ -66,19 +66,32 @@ def on_delete_signup(token):
try:
s = URLSafeSerializer(get_token_secret())
signup_id = ObjectId(s.loads(token))
+ # event_name = s.loads(token)
except BadSignature:
return "Unknown token"
- deleteitem_internal('eventsignups', concurrency_check=False,
- **{current_app.config['ID_FIELD']: signup_id})
-
# Verify if user confirmed
- # definitive = request.args.get('definitive')
- # if definitive:
-
- # Removal
- redirect_url = current_app.config.get('SIGNUP_DELETED_REDIRECT')
- if redirect_url:
- return redirect(redirect_url)
- else:
- return current_app.config['SIGNUP_DELETED_TEXT']
+ # definitive = request.args.get('DEFINITIVE_DELETE')
+ # Get first name for personal greeting
+ query = {'_id': ObjectId(g.current_user)}
+ projection = {'firstname': 1} # Firstame is a required field for users
+ data = current_app.data.driver.db['users'].find_one(query, projection)
+ user = data['firstname']
+ unregister_page = True
+ if unregister_page:
+ # Serve the unregister_event page
+ response = make_response(render_template("unregister_event.html",
+ user=user,
+ event=event_name,
+ error_msg=error_msg))
+ response.set_cookie('token', token)
+ return response
+ else: # Legacy
+ # Removal
+ deleteitem_internal('eventsignups', concurrency_check=False,
+ **{current_app.config['ID_FIELD']: signup_id})
+ redirect_url = current_app.config.get('SIGNUP_DELETED_REDIRECT')
+ if redirect_url:
+ return redirect(redirect_url)
+ else:
+ return current_app.config['SIGNUP_DELETED_TEXT']
diff --git a/amivapi/events/templates/unregister_event.html b/amivapi/events/templates/unregister_event.html
new file mode 100644
index 00000000..22829f4d
--- /dev/null
+++ b/amivapi/events/templates/unregister_event.html
@@ -0,0 +1,67 @@
+
+
+
+
+ AMIV Unregister from Event
+
+
+
+ {% if error_msg %}
+
+ {% endif %}
+
+
+
+
+
+ {% if user %}
+ Hi {{user}}!
+ {% else %}
+ Hello!
+ {% endif %}
+
+
+ {% if user and event %}
+ We will irrevocably unregister you ({{user}}) from the event {{event}}.
+ Is that ok?
+ {% else %}
+ You clicked on a opt-out link of the AMIV at ETHZ student organization. We cannot process your request,
+ because we either do {not} know the event you wish to unregister from, or your user name, or both.
+ {% endif %}
+
+
+
+
+
+
+
diff --git a/amivapi/settings.py b/amivapi/settings.py
index 59dc669b..67935427 100644
--- a/amivapi/settings.py
+++ b/amivapi/settings.py
@@ -62,11 +62,13 @@
REMOTE_MAILING_LIST_ADDRESS = None
REMOTE_MAILING_LIST_KEYFILE = None
REMOTE_MAILING_LIST_DIR = './' # Use home directory on remote by default
+# Signups via email (@email_blueprint.route('/delete_signup/') in email_links.py)
+# DEFINITIVE_DELETE = ''
# SMTP server defaults
API_MAIL = 'api@amiv.ethz.ch'
API_MAIL_SUBJECT = "[AMIV] {subject}"
-SMTP_HOST = 'localhost'
+SMTP_HOST = 'localhost' # None
SMTP_PORT = 587
SMTP_TIMEOUT = 10
diff --git a/amivapi/utils.py b/amivapi/utils.py
index c6253f5b..bc55e567 100644
--- a/amivapi/utils.py
+++ b/amivapi/utils.py
@@ -96,6 +96,8 @@ def mail(to, subject, text):
'receivers': to,
'text': text
})
+ elif app.config.get('DEBUG', False):
+ print('{}{}{}{}'.format(subject, sender, to, text))
elif config.SMTP_SERVER and config.SMTP_PORT:
msg = MIMEText(text)
msg['Subject'] = subject
From 1bf724cb654afa58f415097e9d0f458464e4014f Mon Sep 17 00:00:00 2001
From: Lionel Trebuchon
Date: Sat, 23 Nov 2019 20:13:12 +0100
Subject: [PATCH 04/13] Transmit token to unregister page
---
amivapi/events/email_links.py | 62 ++++++++++++-------
.../events/templates/unregister_event.html | 26 +-------
2 files changed, 43 insertions(+), 45 deletions(-)
diff --git a/amivapi/events/email_links.py b/amivapi/events/email_links.py
index 6687183e..a3740031 100644
--- a/amivapi/events/email_links.py
+++ b/amivapi/events/email_links.py
@@ -7,6 +7,7 @@
Needed when external users want to sign up for public events or users want to
sign off via links.
"""
+import sys
from bson import ObjectId
from eve.methods.delete import deleteitem_internal
from eve.methods.patch import patch_internal
@@ -66,32 +67,49 @@ def on_delete_signup(token):
try:
s = URLSafeSerializer(get_token_secret())
signup_id = ObjectId(s.loads(token))
- # event_name = s.loads(token)
+
except BadSignature:
return "Unknown token"
# Verify if user confirmed
# definitive = request.args.get('DEFINITIVE_DELETE')
# Get first name for personal greeting
- query = {'_id': ObjectId(g.current_user)}
- projection = {'firstname': 1} # Firstame is a required field for users
- data = current_app.data.driver.db['users'].find_one(query, projection)
- user = data['firstname']
- unregister_page = True
- if unregister_page:
+ error_msg = ''
+ query = {'_id': signup_id}
+ data_signup = current_app.data.driver.db['eventsignups'].find_one(query)
+ if data_signup is None:
+ error_msg = "This event might not exist anymore, or the link is broken, or we made a mistake."
+ user = data_signup['user']
+ if user is None:
+ user = data_signup['email']
+ else:
+ query = {'_id': user}
+ data_user = current_app.data.driver.db['users'].find_one(query)
+ user = data_user["firstname"]
+
# Serve the unregister_event page
- response = make_response(render_template("unregister_event.html",
- user=user,
- event=event_name,
- error_msg=error_msg))
- response.set_cookie('token', token)
- return response
- else: # Legacy
- # Removal
- deleteitem_internal('eventsignups', concurrency_check=False,
- **{current_app.config['ID_FIELD']: signup_id})
- redirect_url = current_app.config.get('SIGNUP_DELETED_REDIRECT')
- if redirect_url:
- return redirect(redirect_url)
- else:
- return current_app.config['SIGNUP_DELETED_TEXT']
+ response = make_response(render_template("unregister_event.html",
+ user=user,
+ event=data_signup["title_en"],
+ error_msg=error_msg,
+ token=token))
+ response.set_cookie('token', token)
+ return response
+
+
+@email_blueprint.route('/delete_confirmed/')
+def on_delete_confirmed(token):
+ try:
+ s = URLSafeSerializer(get_token_secret())
+ signup_id = ObjectId(s.loads(token))
+
+ except BadSignature:
+ return "Unknown token"
+
+ deleteitem_internal('eventsignups', concurrency_check=False,
+ **{current_app.config['ID_FIELD']: signup_id})
+ redirect_url = current_app.config.get('SIGNUP_DELETED_REDIRECT')
+ if redirect_url:
+ return redirect(redirect_url)
+ else:
+ return current_app.config['SIGNUP_DELETED_TEXT']
diff --git a/amivapi/events/templates/unregister_event.html b/amivapi/events/templates/unregister_event.html
index 22829f4d..a7a85deb 100644
--- a/amivapi/events/templates/unregister_event.html
+++ b/amivapi/events/templates/unregister_event.html
@@ -37,30 +37,10 @@