Commit 7570f940 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Further isolated api endpoints to allow rawapi container.

parent d2a2b827
Pipeline #40503 passed with stages
in 13 minutes and 7 seconds
......@@ -12,4 +12,5 @@ __pycache__
try.http
target/
project/
local/
\ No newline at end of file
local/
test_*
\ No newline at end of file
......@@ -44,7 +44,12 @@ content-type: application/json
}
###
POST http://localhost:8000/nomad/api/admin/repair_uploads HTTP/1.1
GET http://localhost:8000/nomad/api/admin/repair_uploads HTTP/1.1
###
GET http://localhost:8000/nomad/api/raw/test/some HTTP/1.1
Accept: application/json
###
......
......@@ -22,10 +22,20 @@ There is a separate documentation for the API endpoints from a client perspectiv
.. autodata:: app
.. automodule:: nomad.api.app
.. automodule:: nomad.api.auth
.. automodule:: nomad.api.upload
.. automodule:: nomad.api.repository
.. automodule:: nomad.api.archive
.. automodule:: nomad.api.admin
"""
from .app import app
from . import upload, repository, archive, raw
from . import auth, admin, upload, repository, archive, raw
@app.before_first_request
def setup():
from nomad import infrastructure
from .app import api
if not api.app.config['TESTING']:
infrastructure.setup()
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from flask_restful import abort
from nomad import infrastructure
from nomad.processing import Upload
from .app import app, base_path
# TODO in production this requires authorization
@app.route('%s/admin/<string:operation>' % base_path, methods=['POST'])
def call_admin_operation(operation):
"""
Allows to perform administrative operations on the nomad services. The possible
operations are *repair_uploads*
(cleans incomplete or otherwise unexpectedly failed uploads), *reset* (clears all
databases and resets nomad).
.. :quickref: Allows to perform administrative operations on the nomad services.
:param string operation: the operation to perform
:status 400: unknown operation
:status 200: operation successfully started
:returns: an authentication token that is valid for 10 minutes.
"""
if operation == 'repair_uploads':
Upload.repair_all()
if operation == 'reset':
infrastructure.reset()
else:
abort(400, message='Unknown operation %s' % operation)
return 'done', 200
......@@ -14,37 +14,15 @@
"""
All APIs are served by one Flask app (:py:mod:`nomad.api.app`) under different paths.
Endpoints can use *flask_httpauth* based authentication either with basic HTTP
authentication or access tokens. Currently the authentication is validated against
users and sessions in the NOMAD-coe repository postgres db.
.. autodata:: base_path
There are two authentication "schemes" to authenticate users. First we use
HTTP Basic Authentication (username, password), which also works with username=token,
password=''. Second, there is a curstom HTTP header 'X-Token' that can be used to
give a token. The first precedes the second. The used tokens are given and stored
by the NOMAD-coe repository GUI.
Authenticated user information is available via FLASK's build in flask.g.user object.
It is set to None, if no user information is available.
There are two decorators for FLASK API endpoints that can be used if endpoints require
authenticated user information for authorization or otherwise.
.. autofunction:: login_if_available
.. autofunction:: login_really_required
"""
from flask import Flask, g, request
from flask_restful import Api, abort
from flask import Flask, jsonify
from flask_restful import Api
from flask_cors import CORS
from flask_httpauth import HTTPBasicAuth
from werkzeug.exceptions import HTTPException
import os.path
from nomad import config, infrastructure
from nomad.coe_repo import User
from nomad.processing import Upload
from nomad import config
base_path = config.services.api_base_path
""" Provides the root path of the nomad APIs. """
......@@ -57,112 +35,20 @@ app = Flask(
CORS(app)
app.config['SECRET_KEY'] = config.services.api_secret
auth = HTTPBasicAuth()
api = Api(app)
@app.before_first_request
def setup():
if not api.app.config['TESTING']:
infrastructure.setup()
@auth.verify_password
def verify_password(username_or_token, password):
# first try to authenticate by token
g.user = User.verify_auth_token(username_or_token)
if not g.user:
# try to authenticate with username/password
try:
g.user = User.verify_user_password(username_or_token, password)
except Exception:
return False
if not g.user:
return True # anonymous access
return True
def login_if_available(func):
"""
A decorator for API endpoint implementations that might authenticate users, but
provide limited functionality even without users.
"""
@auth.login_required
def wrapper(*args, **kwargs):
# TODO the cutom X-Token based authentication should be replaced by a real
# Authentication header based token authentication
if not g.user and 'X-Token' in request.headers:
token = request.headers['X-Token']
g.user = User.verify_auth_token(token)
if not g.user:
abort(401, message='Provided access token is not valid or does not exist.')
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
def login_really_required(func):
"""
A decorator for API endpoint implementations that forces user authentication on
endpoints.
"""
@login_if_available
def wrapper(*args, **kwargs):
if g.user is None:
abort(401, message='Anonymous access is forbidden, authorization required')
else:
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
@app.route('%s/token' % base_path)
@login_really_required
def get_auth_token():
"""
Get a token for authenticated users. This is currently disabled and all authentication
matters are solved by the NOMAD-coe repository GUI.
.. :quickref: Get a token to authenticate the user in follow up requests.
:resheader Content-Type: application/json
:status 200: calc successfully retrieved
:returns: an authentication token that is valid for 10 minutes.
"""
assert False, 'All authorization is none via NOMAD-coe repository GUI'
# TODO all authorization is done via NOMAD-coe repository GUI
# token = g.user.generate_auth_token(600)
# return jsonify({'token': token.decode('ascii'), 'duration': 600})
@app.route('%s/admin/<string:operation>' % base_path, methods=['POST'])
def call_admin_operation(operation):
"""
Allows to perform administrative operations on the nomad services. The possible
operations are *repair_uploads*
(cleans incomplete or otherwise unexpectedly failed uploads), *reset* (clears all
databases and resets nomad).
.. :quickref: Allows to perform administrative operations on the nomad services.
:param string operation: the operation to perform
:status 400: unknown operation
:status 200: operation successfully started
:returns: an authentication token that is valid for 10 minutes.
"""
if operation == 'repair_uploads':
Upload.repair_all()
if operation == 'reset':
infrastructure.reset()
else:
abort(400, message='Unknown operation %s' % operation)
return 'done', 200
""" Provides the flask restful api instance """
@app.errorhandler(HTTPException)
def handle(error):
status_code = getattr(error, 'code', 500)
name = getattr(error, 'name', 'Internal Server Error')
description = getattr(error, 'description', None)
data = dict(
code=status_code,
name=name,
description=description)
data.update(getattr(error, 'data', []))
response = jsonify(data)
response.status_code = status_code
return response
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Endpoints can use *flask_httpauth* based authentication either with basic HTTP
authentication or access tokens. Currently the authentication is validated against
users and sessions in the NOMAD-coe repository postgres db.
.. autodata:: base_path
There are two authentication "schemes" to authenticate users. First we use
HTTP Basic Authentication (username, password), which also works with username=token,
password=''. Second, there is a curstom HTTP header 'X-Token' that can be used to
give a token. The first precedes the second. The used tokens are given and stored
by the NOMAD-coe repository GUI.
Authenticated user information is available via FLASK's build in flask.g.user object.
It is set to None, if no user information is available.
There are two decorators for FLASK API endpoints that can be used if endpoints require
authenticated user information for authorization or otherwise.
.. autofunction:: login_if_available
.. autofunction:: login_really_required
"""
from flask import g, request
from flask_restful import abort
from flask_httpauth import HTTPBasicAuth
from nomad import config
from nomad.coe_repo import User
from .app import app, base_path
app.config['SECRET_KEY'] = config.services.api_secret
auth = HTTPBasicAuth()
@auth.verify_password
def verify_password(username_or_token, password):
# first try to authenticate by token
g.user = User.verify_auth_token(username_or_token)
if not g.user:
# try to authenticate with username/password
try:
g.user = User.verify_user_password(username_or_token, password)
except Exception:
return False
if not g.user:
return True # anonymous access
return True
def login_if_available(func):
"""
A decorator for API endpoint implementations that might authenticate users, but
provide limited functionality even without users.
"""
@auth.login_required
def wrapper(*args, **kwargs):
# TODO the cutom X-Token based authentication should be replaced by a real
# Authentication header based token authentication
if not g.user and 'X-Token' in request.headers:
token = request.headers['X-Token']
g.user = User.verify_auth_token(token)
if not g.user:
abort(401, message='Provided access token is not valid or does not exist.')
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
def login_really_required(func):
"""
A decorator for API endpoint implementations that forces user authentication on
endpoints.
"""
@login_if_available
def wrapper(*args, **kwargs):
if g.user is None:
abort(401, message='Anonymous access is forbidden, authorization required')
else:
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
@app.route('%s/token' % base_path)
@login_really_required
def get_auth_token():
"""
Get a token for authenticated users. This is currently disabled and all authentication
matters are solved by the NOMAD-coe repository GUI.
.. :quickref: Get a token to authenticate the user in follow up requests.
:resheader Content-Type: application/json
:status 200: calc successfully retrieved
:returns: an authentication token that is valid for 10 minutes.
"""
assert False, 'All authorization is none via NOMAD-coe repository GUI'
# TODO all authorization is done via NOMAD-coe repository GUI
# token = g.user.generate_auth_token(600)
# return jsonify({'token': token.decode('ascii'), 'duration': 600})
......@@ -23,7 +23,8 @@ from flask_restful import Resource, abort
from nomad.repo import RepoCalc
from .app import api, base_path, login_if_available
from .app import api, base_path
from .auth import login_if_available
class RepoCalcRes(Resource):
......
......@@ -21,7 +21,8 @@ from nomad.files import UploadFile
from nomad.processing import NotAllowedDuringProcessing, Upload
from nomad.utils import get_logger
from .app import api, base_path, login_really_required
from .app import api, base_path
from .auth import login_really_required
"""
The upload API of the nomad@FAIRDI APIs. Provides endpoints to create uploads, upload
......
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This dockerfile creates a limited api container that only runs the raw file api endpoint
# We use slim for the final image
FROM python:3.6-slim as final
# First, build everything in a build image
FROM python:3.6-stretch as build
RUN mkdir /install
WORKDIR /install
# We also install the -dev dependencies, to use this image for test and qa
RUN pip install --upgrade pip
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
# do that after the dependencies to use docker's layer caching
COPY . /install
RUN echo "from .app import app\nfrom . import raw" > /install/nomad/api/__init__.py
RUN pip install .
RUN \
find /usr/local/lib/python3.6/ -name 'tests' ! -path '*/networkx/*' -exec rm -r '{}' + && \
find /usr/local/lib/python3.6/ -name 'test' -exec rm -r '{}' + && \
find /usr/local/lib/python3.6/site-packages/ -name '*.so' -print -exec sh -c 'file "{}" | grep -q "not stripped" && strip -s "{}"' \;
# Second, create a slim final image
FROM final
# copy the sources for tests, coverage, qa, etc.
COPY --from=build /install /app
WORKDIR /app
# transfer installed packages from dependency stage
COPY --from=build /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages
RUN mkdir -p /raw
RUN useradd -ms /bin/bash nomad
RUN chown -R nomad /app
USER nomad
ENV NOMAD_FILES_OBJECTS_DIR /raw
ENV NOMAD_FILES_RAW_BUCKET data
ENV NOMAD_SERVICE rawapi
CMD python -m gunicorn.app.wsgiapp -b 0.0.0.0:8000 nomad.api:app
VOLUME /raw
EXPOSE 8000
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment