Skip to content

Commit

Permalink
api: add JWT required endpoints
Browse files Browse the repository at this point in the history
Closes reanahub#77

Signed-off-by: leticia <leticia.farias.wanderley@cern.ch>
  • Loading branch information
leticia committed Jul 3, 2019
1 parent d4cfc0a commit 5d791b7
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 5 deletions.
68 changes: 68 additions & 0 deletions docs/openapi.json
Expand Up @@ -7,6 +7,74 @@
},
"parameters": {},
"paths": {
"/api/auth": {
"post": {
"description": "This resource looks for an user with the provided information (email, id) and, if there is such user, creates a JWT cookie on the user browser.",
"operationId": "auth_user",
"parameters": [
{
"description": "Required. The email of the user.",
"in": "query",
"name": "email",
"required": true,
"type": "string"
},
{
"description": "Required. API key of the admin.",
"in": "query",
"name": "password",
"required": true,
"type": "string"
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "User authenticate successfully. Returns the JWT set cookie headers and a successful login boolean.",
"examples": {
"application/json": {
"login": true
}
},
"schema": {
"properties": {
"login": {
"type": "boolean"
}
},
"type": "object"
}
},
"401": {
"description": "Request failed. The data provided could authenticate the user.",
"examples": {
"application/json": {
"message": "Couldn't authenticate."
}
},
"schema": {
"properties": {
"login": {
"type": "boolean"
}
},
"type": "object"
}
},
"500": {
"description": "Request failed. Internal server error.",
"examples": {
"application/json": {
"message": "Internal server error."
}
}
}
},
"summary": "Authenticates the user with the provided information."
}
},
"/api/ping": {
"get": {
"description": "Ping the server.",
Expand Down
10 changes: 10 additions & 0 deletions reana_server/config.py
Expand Up @@ -20,3 +20,13 @@
ADMIN_USER_ID = "00000000-0000-0000-0000-000000000000"

SHARED_VOLUME_PATH = os.getenv('SHARED_VOLUME_PATH', '/var/reana')

JWT_SECRET_KEY = 'hyper secret key'

JWT_TOKEN_LOCATION = ['cookies']

JWT_COOKIE_SECURE = False

JWT_ACCESS_COOKIE_PATH = '/'

JWT_COOKIE_CSRF_PROTECT = True
4 changes: 3 additions & 1 deletion reana_server/factory.py
Expand Up @@ -12,6 +12,7 @@

from flask import Flask
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from reana_commons.config import REANA_LOG_FORMAT, REANA_LOG_LEVEL
from reana_db.database import Session

Expand All @@ -34,5 +35,6 @@ def create_app():
app.register_blueprint(secrets.blueprint, url_prefix='/api')

app.session = Session
CORS(app)
jwt = JWTManager(app)
CORS(app, supports_credentials=True)
return app
92 changes: 91 additions & 1 deletion reana_server/rest/users.py
Expand Up @@ -9,10 +9,14 @@
"""Reana-Server User Endpoints."""

import logging
import json
import traceback

from flask import Blueprint, jsonify, request

from flask_jwt_extended import (
create_access_token, create_refresh_token,
set_access_cookies, set_refresh_cookies
)
from reana_server.utils import _create_user, _get_users

blueprint = Blueprint('users', __name__)
Expand Down Expand Up @@ -200,3 +204,89 @@ def create_user(): # noqa
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500


@blueprint.route('/auth', methods=['POST'])
def auth_user():
r"""Endpoint to authenticate users.
---
post:
summary: Authenticates the user with the provided information.
description: >-
This resource looks for an user with the provided
information (email, id) and, if there is such user, creates
a JWT cookie on the user browser.
operationId: auth_user
produces:
- application/json
parameters:
- name: email
in: query
description: Required. The email of the user.
required: true
type: string
- name: password
in: query
description: Required. API key of the admin.
required: true
type: string
responses:
200:
description: >-
User authenticate successfully. Returns the JWT set cookie
headers and a successful login boolean.
schema:
type: object
properties:
login:
type: boolean
examples:
application/json:
{
"login": true
}
401:
description: >-
Request failed. The data provided could authenticate
the user.
schema:
type: object
properties:
login:
type: boolean
examples:
application/json:
{
"message": "Couldn't authenticate."
}
500:
description: >-
Request failed. Internal server error.
examples:
application/json:
{
"message": "Internal server error."
}
"""
try:
data = json.loads(request.data.decode('utf-8'))
user_email = data['username']
reana_access_token = data['password']
users = _get_users(None, user_email, None, reana_access_token)
if users:
user = users[0]
access_token = create_access_token(identity=user.id_)
refresh_token = create_refresh_token(identity=user.id_)

response = jsonify({'login': True})
set_access_cookies(response, access_token)
set_refresh_cookies(response, refresh_token)
return response, 200
else:
response = jsonify({'message': "Could not authenticate user."})
return response, 401
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
9 changes: 6 additions & 3 deletions reana_server/rest/workflows.py
Expand Up @@ -17,6 +17,7 @@
from flask import Blueprint
from flask import current_app as app
from flask import jsonify, request, send_file
from flask_jwt_extended import jwt_optional, get_jwt_identity
from reana_commons.config import INTERACTIVE_SESSION_TYPES
from reana_commons.utils import get_workspace_disk_usage
from reana_db.database import Session
Expand All @@ -32,6 +33,7 @@


@blueprint.route('/workflows', methods=['GET'])
@jwt_optional
def get_workflows(): # noqa
r"""Get all current workflows in REANA.
Expand Down Expand Up @@ -148,15 +150,16 @@ def get_workflows(): # noqa
}
"""
try:
user = get_user_from_token(request.args.get('access_token'))
user = get_jwt_identity()
if not user:
user = get_user_from_token(request.args.get('access_token')).id_
type = request.args.get('type', 'batch')
verbose = request.args.get('verbose', False)
response, http_response = current_rwc_api_client.api.\
get_workflows(
user=str(user.id_),
user=str(user),
type=type,
verbose=bool(verbose)).result()

return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -59,6 +59,7 @@
'Flask>=0.11',
'fs>=2.0',
'flask-cors>=3.0.6',
'flask-jwt-extended>=3.19.0',
'marshmallow>=2.13',
'pyOpenSSL==17.5.0',
'reana-commons[kubernetes]>=0.6.0.dev20190604,<0.7.0',
Expand Down

0 comments on commit 5d791b7

Please sign in to comment.