Skip to content
This repository has been archived by the owner on Mar 10, 2023. It is now read-only.

Commit

Permalink
Merge pull request #45 from ScilifelabDataCentre/develop
Browse files Browse the repository at this point in the history
Merge for release before major changes
  • Loading branch information
talavis authored Mar 12, 2021
2 parents 2f40959 + d2e0ba3 commit 62d8d9c
Show file tree
Hide file tree
Showing 116 changed files with 7,101 additions and 3,728 deletions.
447 changes: 0 additions & 447 deletions .pylintrc

This file was deleted.

2 changes: 1 addition & 1 deletion Dockerfile-backend
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM python:3.8-alpine

RUN apk add gcc musl-dev python3-dev libffi-dev openssl-dev
RUN apk add gcc musl-dev python3-dev libffi-dev openssl-dev rust cargo

COPY ./backend/requirements.txt /requirements.txt

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-frontend
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:alpine
FROM node:14-alpine

RUN yarn global add @quasar/cli

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ The backend requires a `config.yaml` file to be mounted to `/config.yaml`.
The frontend assumes that the backend is available at `/api`.


## Code

`backend`: python3, flask

`frontend`: Quasar (Vue)

`docs`: Sphinx


[travis-badge]: https://api.travis-ci.com/ScilifelabDataCentre/Data-Tracker.svg?branch=develop
[travis-link]: https://travis-ci.com/ScilifelabDataCentre/Data-Tracker
Expand Down
118 changes: 58 additions & 60 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,141 +21,139 @@
appconf = config.init()
db_management.check_db(appconf)
app.config.update(appconf)
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=31)

if app.config["dev_mode"]["api"]:
app.register_blueprint(developer.blueprint, url_prefix="/api/v1/developer")

if app.config['dev_mode']['api']:
app.register_blueprint(developer.blueprint, url_prefix='/api/v1/developer')

app.register_blueprint(dataset.blueprint, url_prefix='/api/v1/dataset')
app.register_blueprint(order.blueprint, url_prefix='/api/v1/order')
app.register_blueprint(collection.blueprint, url_prefix='/api/v1/collection')
app.register_blueprint(user.blueprint, url_prefix='/api/v1/user')
app.register_blueprint(dataset.blueprint, url_prefix="/api/v1/dataset")
app.register_blueprint(order.blueprint, url_prefix="/api/v1/order")
app.register_blueprint(collection.blueprint, url_prefix="/api/v1/collection")
app.register_blueprint(user.blueprint, url_prefix="/api/v1/user")


oauth = OAuth(app)
for oidc_name in app.config.get('oidc_names'):
oauth.register(oidc_name, client_kwargs={'scope': 'openid profile email'})
for oidc_name in app.config.get("oidc_names"):
oauth.register(oidc_name, client_kwargs={"scope": "openid profile email"})


@app.before_request
def prepare():
"""Open the database connection and get the current user."""
flask.g.dbclient = utils.get_dbclient(flask.current_app.config)
flask.g.db = utils.get_db(flask.g.dbclient, flask.current_app.config)
if apikey := flask.request.headers.get('X-API-Key'):
if not (apiuser := flask.request.headers.get('X-API-User')): # pylint: disable=superfluous-parens
if apikey := flask.request.headers.get("X-API-Key"):
if not (
apiuser := flask.request.headers.get("X-API-User")
): # pylint: disable=superfluous-parens
flask.abort(status=400)
utils.verify_api_key(apiuser, apikey)
flask.g.current_user = flask.g.db['users'].find_one({'auth_ids': apiuser})
flask.g.permissions = flask.g.current_user['permissions']
flask.g.current_user = flask.g.db["users"].find_one({"auth_ids": apiuser})
flask.g.permissions = flask.g.current_user["permissions"]
else:
if flask.request.method != 'GET':
if flask.request.method != "GET":
utils.verify_csrf_token()
flask.g.current_user = user.get_current_user()
flask.g.permissions = flask.g.current_user['permissions'] if flask.g.current_user else None
flask.g.permissions = (
flask.g.current_user["permissions"] if flask.g.current_user else None
)


@app.after_request
def finalize(response):
"""Finalize the response and clean up."""
# close db connection
if hasattr(flask.g, 'dbserver'):
if hasattr(flask.g, "dbserver"):
flask.g.dbserver.close()
# set csrf cookie if not set
if not flask.request.cookies.get('_csrf_token'):
response.set_cookie('_csrf_token', utils.gen_csrf_token(), samesite='Lax')
if not flask.request.cookies.get("_csrf_token"):
response.set_cookie("_csrf_token", utils.gen_csrf_token(), samesite="Lax")
# add some headers for protection
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers["X-Frame-Options"] = "SAMEORIGIN"
response.headers["X-XSS-Protection"] = "1; mode=block"
return response


@app.route('/api/v1/')
@app.route("/api/v1/")
def api_base():
"""List entities."""
return flask.jsonify({'entities': ['dataset', 'order', 'collection', 'user', 'login']})
return flask.jsonify(
{"entities": ["dataset", "order", "collection", "user", "login"]}
)


@app.route('/api/v1/login/')
@app.route("/api/v1/login/")
def login_types():
"""List login types."""
return flask.jsonify({'types': ['apikey', 'oidc']})
return flask.jsonify({"types": ["apikey", "oidc"]})


@app.route('/api/v1/login/oidc/')
@app.route("/api/v1/login/oidc/")
def oidc_types():
"""List OpenID Connect types."""
auth_types = {}
for auth_name in app.config.get('oidc_names'):
auth_types[auth_name] = flask.url_for('oidc_login',
auth_name=auth_name)
for auth_name in app.config.get("oidc_names"):
auth_types[auth_name] = flask.url_for("oidc_login", auth_name=auth_name)

return flask.jsonify(auth_types)


@app.route('/api/v1/login/oidc/<auth_name>/login/')
@app.route("/api/v1/login/oidc/<auth_name>/login/")
def oidc_login(auth_name):
"""Perform a login using OpenID Connect (e.g. Elixir AAI)."""
client = oauth.create_client(auth_name)
redirect_uri = flask.url_for('oidc_authorize',
auth_name=auth_name,
_external=True)
flask.session['incoming_url'] = flask.request.args.get('origin') or '/'
redirect_uri = flask.url_for("oidc_authorize", auth_name=auth_name, _external=True)
flask.session["incoming_url"] = flask.request.args.get("origin") or "/"
return client.authorize_redirect(redirect_uri)


@app.route('/api/v1/login/oidc/<auth_name>/authorize/')
@app.route("/api/v1/login/oidc/<auth_name>/authorize/")
def oidc_authorize(auth_name):
"""Authorize a login using OpenID Connect (e.g. Elixir AAI)."""
if auth_name not in app.config.get('oidc_names'):
if auth_name not in app.config.get("oidc_names"):
flask.abort(status=404)
client = oauth.create_client(auth_name)
token = client.authorize_access_token()
if 'id_token' in token:
if "id_token" in token:
user_info = client.parse_id_token(token)
else:
user_info = client.userinfo()
if auth_name != 'elixir':
user_info['auth_id'] = f'{user_info["email"]}::{auth_name}'
if auth_name != "elixir":
user_info["auth_id"] = f'{user_info["email"]}::{auth_name}'
else:
user_info['auth_id'] = token['sub']
if not user.do_login(user_info['auth_id']):
user_info["auth_id"] = token["sub"]
if not user.do_login(user_info["auth_id"]):
user.add_new_user(user_info)
user.do_login(user_info['auth_id'])
user.do_login(user_info["auth_id"])

response = flask.redirect(flask.session['incoming_url'])
del flask.session['incoming_url']
response.set_cookie('loggedIn', 'true', max_age=datetime.timedelta(days=31))
response = flask.redirect(flask.session["incoming_url"])
del flask.session["incoming_url"]
return response


# requests
@app.route('/api/v1/login/apikey/', methods=['POST'])
@app.route("/api/v1/login/apikey/", methods=["POST"])
def key_login():
"""Log in using an apikey."""
try:
indata = flask.json.loads(flask.request.data)
except json.decoder.JSONDecodeError:
flask.abort(status=400)

if 'api-user' not in indata or 'api-key' not in indata:
app.logger.debug('API key login - bad keys: %s', indata)
if "api-user" not in indata or "api-key" not in indata:
app.logger.debug("API key login - bad keys: %s", indata)
return flask.Response(status=400)
utils.verify_api_key(indata['api-user'], indata['api-key'])
user.do_login(auth_id=indata['api-user'])
utils.verify_api_key(indata["api-user"], indata["api-key"])
user.do_login(auth_id=indata["api-user"])
response = flask.Response(status=200)
response.set_cookie('loggedIn', 'true', max_age=datetime.timedelta(days=31))
return response


@app.route('/api/v1/logout/')
@app.route("/api/v1/logout/")
def logout():
"""Log out the current user."""
flask.session.clear()
response = flask.Response(status=200)
response.set_cookie('_csrf_token', utils.gen_csrf_token(), 0)
response.set_cookie('loggedIn', 'false', 0)
response.set_cookie("_csrf_token", utils.gen_csrf_token(), 0)
return response


Expand Down Expand Up @@ -184,10 +182,10 @@ def error_not_found(_):


# to allow coverage check for testing
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
else:
# Assume this means it's handled by gunicorn
gunicorn_logger = logging.getLogger('gunicorn.error')
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
gunicorn_logger = logging.getLogger("gunicorn.error")
if gunicorn_logger:
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
Loading

0 comments on commit 62d8d9c

Please sign in to comment.