Skip to content

Commit

Permalink
Change flask_configfile to TOML (#1499)
Browse files Browse the repository at this point in the history
Co-authored-by: Peter Lombaers <peter@idfuse.nl>
  • Loading branch information
cskaandorp and PeterLombaers committed Jul 20, 2023
1 parent 3903d45 commit 3856f89
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 111 deletions.
109 changes: 52 additions & 57 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,60 +138,57 @@ one could use the User model that can be found in `/asreview/webapp/authenticati

### Full configuration

To configure the authentication in more detail we need to create a JSON file
that contains all authentication parameters. The keys in that JSON file will override any parameter that was passed in the CLI. Here's an example:
```json
{
"DEBUG": true,
"AUTHENTICATION_ENABLED": true,
"SECRET_KEY": "<secret key>",
"SECURITY_PASSWORD_SALT": "<salt>",
"SESSION_COOKIE_SECURE": true,
"REMEMBER_COOKIE_SECURE": true,
"SESSION_COOKIE_SAMESITE": "Lax",
"SQLALCHEMY_TRACK_MODIFICATIONS": true,
"ALLOW_ACCOUNT_CREATION": true,
"EMAIL_VERIFICATION": true,
"EMAIL_CONFIG": {
"SERVER": "<smtp-server>",
"PORT": "<smpt-server-port>",
"USERNAME": "<smtp-server-username>",
"PASSWORD": "<smtp-server-password>",
"USE_TLS": false,
"USE_SSL": true,
"REPLY_ADDRESS": "<preferred reply email address>"
},
"OAUTH": {
"GitHub": {
"AUTHORIZATION_URL": "https://github.com/login/oauth/authorize",
"TOKEN_URL": "https://github.com/login/oauth/access_token",
"CLIENT_ID": "<GitHub client ID>",
"CLIENT_SECRET": "<GitHub client secret>",
"SCOPE": ""
},
"Orcid": {
"AUTHORIZATION_URL": "https://sandbox.orcid.org/oauth/authorize",
"TOKEN_URL": "https://sandbox.orcid.org/oauth/token",
"CLIENT_ID": "<Orcid client ID>",
"CLIENT_SECRET": "<Orcid client secret>",
"SCOPE": "/authenticate"
},
"Google": {
"AUTHORIZATION_URL": "https://accounts.google.com/o/oauth2/auth",
"TOKEN_URL": "https://oauth2.googleapis.com/token",
"CLIENT_ID": "<Google client ID>",
"CLIENT_SECRET": "<Google client secret>",
"SCOPE": "profile email"
}
}
}
```
Store the JSON file on the server and start the ASReview application from the CLI with the
To configure the authentication in more detail we need to create a TOML or a JSON file that contains all authentication parameters. The keys in that TOML/JSON file will override any parameter that was passed in the CLI. Here's an example of a TOML file:
```toml
DEBUG = true
AUTHENTICATION_ENABLED = true
SECRET_KEY = "<secret key>"
SECURITY_PASSWORD_SALT = "<salt>"
SESSION_COOKIE_SECURE = true
REMEMBER_COOKIE_SECURE = true
SESSION_COOKIE_SAMESITE = "Lax"
SQLALCHEMY_TRACK_MODIFICATIONS = true
ALLOW_ACCOUNT_CREATION = true
ALLOW_TEAMS = false
EMAIL_VERIFICATION = false

[EMAIL_CONFIG]
SERVER = "<smtp-server>"
PORT = 465
USERNAME = "<smtp-server-username>"
PASSWORD = "<smtp-server-password>"
USE_TLS = false
USE_SSL = true
REPLY_ADDRESS = "<preferred reply email address>"

[OAUTH]
[OAUTH.GitHub]
AUTHORIZATION_URL = "https://github.com/login/oauth/authorize"
TOKEN_URL = "https://github.com/login/oauth/access_token"
CLIENT_ID = "<GitHub client ID>"
CLIENT_SECRET = "<GitHub client secret>"
SCOPE = ""

[OAUTH.Orcid]
AUTHORIZATION_URL = "https://sandbox.orcid.org/oauth/authorize"
TOKEN_URL = "https://sandbox.orcid.org/oauth/token"
CLIENT_ID = "<Orcid client ID>"
CLIENT_SECRET = "<Orcid client secret>"
SCOPE = "/authenticate"

[OAUTH.Google]
AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/auth"
TOKEN_URL = "https://oauth2.googleapis.com/token"
CLIENT_ID = "<Google client ID>"
CLIENT_SECRET = "<Google client secret>"
SCOPE = "profile email"
```
Store the TOML file on the server and start the ASReview application from the CLI with the
`--flask-configfile` parameter:
```
$ python3 -m asreview lab --flask-configfile=<path-to-JSON-config-file>
$ python3 -m asreview lab --flask-configfile=<path-to-TOML-config-file>
```
A number of the keys in the JSON file are standard Flask parameters. The keys that are specific for authenticating ASReview are summarised below:
A number of the keys in the TOML file are standard Flask parameters. The keys that are specific for authenticating ASReview are summarised below:
* AUTHENTICATION_ENABLED: if set to `true` the application will start with authentication enabled. If the SQLite database does not exist, one will be created during startup.
* SECRET_KEY: the secret key is a string that is used to encrypt cookies and is mandatory if authentication is required.
* SECURITY_PASSWORD_SALT: another string used to hash passwords, also mandatory if authentication is required.
Expand All @@ -204,12 +201,10 @@ A number of the keys in the JSON file are standard Flask parameters. The keys th

There are three optional parameters available that control what address the ASReview server listens to, and avoid CORS issues:

```json
{
"HOST": "0.0.0.0",
"PORT": 5001,
"ALLOWED_ORIGINS": ["http://localhost:3001"],
}
```toml
HOST = "0.0.0.0"
PORT = 5001
ALLOWED_ORIGINS = ["http://localhost:3000"]
```
The HOST and PORT determine what address the ASReview server listens to. If this deviates from `localhost` and port 5000, and you run the front end separately, make sure the [front end can find the backend](#front-end-development-and-connectioncors-issues). The ALLOWED_ORIGINS key must be set if you run the front end separately. Put in a list all URLs that your front end uses. This can be more than one URL. Failing to do so will certainly lead to CORS issues.

Expand Down
26 changes: 16 additions & 10 deletions asreview/webapp/start_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@
# limitations under the License.

import argparse
import json
import logging
import os
import socket
import sys
import webbrowser
from pathlib import Path
from threading import Timer

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

from flask import Flask
from flask import send_from_directory
from flask.json import jsonify
Expand Down Expand Up @@ -112,12 +117,11 @@ def _open_browser(host, port, protocol, no_browser):


def _lab_parser():

# parse arguments if available
parser = argparse.ArgumentParser(
prog="lab",
description="""ASReview LAB - Active learning for Systematic Reviews.""", # noqa
formatter_class=argparse.RawTextHelpFormatter
formatter_class=argparse.RawTextHelpFormatter,
)

parser.add_argument(
Expand Down Expand Up @@ -183,7 +187,7 @@ def _lab_parser():
"--flask-configfile",
default="",
type=str,
help="Full path to a JSON file containing Flask parameters"
help="Full path to a TOML file containing Flask parameters"
"for authentication.",
)

Expand Down Expand Up @@ -264,7 +268,13 @@ def create_app(**kwargs):
config_file_path = kwargs.get("flask_configfile", "").strip()
# Use absolute path, because otherwise it is relative to the config root.
if config_file_path != "":
app.config.from_file(Path(config_file_path).absolute(), load=json.load)
config_file_path = Path(config_file_path)
if config_file_path.suffix == ".toml":
app.config.from_file(
config_file_path.absolute(), load=tomllib.load, text=False
)
else:
raise ValueError("'flask_configfile' should have a .toml extension")

# If the frontend runs on a different port, or even on a different
# URL, then allowed-origins must be set to avoid CORS issues. You can
Expand Down Expand Up @@ -361,11 +371,7 @@ def load_user(user_id):
# allowed origins to avoid CORS problems. The allowed-origins
# can be set in the config file.
if app.config.get("ALLOWED_ORIGINS", False):
CORS(
app,
origins=app.config.get("ALLOWED_ORIGINS"),
supports_credentials=True
)
CORS(app, origins=app.config.get("ALLOWED_ORIGINS"), supports_credentials=True)

with app.app_context():
app.register_blueprint(projects.bp)
Expand Down
8 changes: 0 additions & 8 deletions asreview/webapp/tests/config/auth_basic_config.json

This file was deleted.

6 changes: 6 additions & 0 deletions asreview/webapp/tests/config/auth_basic_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
TESTING = true
DEBUG = true
SECRET_KEY = "my_very_secret_key"
SECURITY_PASSWORD_SALT = "my_salt"
AUTHENTICATION_ENABLED = true
ALLOW_ACCOUNT_CREATION = true
8 changes: 0 additions & 8 deletions asreview/webapp/tests/config/auth_no_creation.json

This file was deleted.

6 changes: 6 additions & 0 deletions asreview/webapp/tests/config/auth_no_creation.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
TESTING = true
DEBUG = true
SECRET_KEY = "my_very_secret_key"
SECURITY_PASSWORD_SALT = "my_salt"
AUTHENTICATION_ENABLED = true
ALLOW_ACCOUNT_CREATION = false
18 changes: 0 additions & 18 deletions asreview/webapp/tests/config/auth_verified_config.json

This file was deleted.

16 changes: 16 additions & 0 deletions asreview/webapp/tests/config/auth_verified_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
TESTING = true
DEBUG = true
SECRET_KEY = "my_very_secret_key"
SECURITY_PASSWORD_SALT = "my_salt"
AUTHENTICATION_ENABLED = true
ALLOW_ACCOUNT_CREATION = true
EMAIL_VERIFICATION = true

[EMAIL_CONFIG]
SERVER = "localhost"
PORT = 465
USERNAME = "admin@asreview.nl"
PASSWORD = "secret_password"
USE_TLS = false
USE_SSL = true
REPLY_ADDRESS = "no_reply@asreview.nl"
5 changes: 0 additions & 5 deletions asreview/webapp/tests/config/no_auth_config.json

This file was deleted.

3 changes: 3 additions & 0 deletions asreview/webapp/tests/config/no_auth_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TESTING = true
DEBUG = true
AUTHENTICATION_ENABLED = false
8 changes: 4 additions & 4 deletions asreview/webapp/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ def _get_app(app_type="auth-basic", path=None):
# get path of appropriate flask config
base_dir = Path(__file__).resolve().parent / "config"
if app_type == "auth-basic":
config_path = str(base_dir / "auth_basic_config.json")
config_path = str(base_dir / "auth_basic_config.toml")
elif app_type == "auth-no-creation":
config_path = str(base_dir / "auth_no_creation.json")
config_path = str(base_dir / "auth_no_creation.toml")
elif app_type == "auth-verified":
config_path = str(base_dir / "auth_verified_config.json")
config_path = str(base_dir / "auth_verified_config.toml")
elif app_type == "no-auth":
config_path = str(base_dir / "no_auth_config.json")
config_path = str(base_dir / "no_auth_config.toml")
else:
raise ValueError(f"Unknown config {app_type}")
# create app
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_long_description():
"rispy~=0.7.0",
"xlrd>=1.0.0",
"setuptools",
"flask>=2.0",
"flask>=2.3.0",
"flask_cors",
"flask-login",
"flask-mail",
Expand All @@ -63,6 +63,7 @@ def get_long_description():
"tqdm",
"gevent>=20",
"datahugger>=0.2",
"tomli", # included in Python 3.11 as tomllib
]

if sys.version_info < (3, 10):
Expand Down

0 comments on commit 3856f89

Please sign in to comment.