diff --git a/README.rst b/README.rst index 89e2356..c54a5b8 100644 --- a/README.rst +++ b/README.rst @@ -98,6 +98,11 @@ Quickstart plugins=[MarshmallowPlugin()], ), 'APISPEC_SWAGGER_URL': '/swagger/', + 'APISPEC_AUTH': { + 'ENABLED': True, + 'USERNAME': '', + 'PASSWORD': '' + } }) docs = FlaskApiSpec(app) diff --git a/flask_apispec/extension.py b/flask_apispec/extension.py index c1ef648..1ddd380 100644 --- a/flask_apispec/extension.py +++ b/flask_apispec/extension.py @@ -1,12 +1,29 @@ import flask import functools import types +from flask_httpauth import HTTPBasicAuth from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin from flask_apispec import ResourceMeta from flask_apispec.apidoc import ViewConverter, ResourceConverter +auth = HTTPBasicAuth() +config = None + + +@auth.verify_password +def verify_password(username=None, password=None): + if "APISPEC_AUTH" not in config or \ + not config.get("APISPEC_AUTH", {}).get("ENABLED"): + return True + + basic_auth = config.get("APISPEC_AUTH") + if username == basic_auth.get("USERNAME") and \ + password == basic_auth.get("PASSWORD"): + return True + return False + class FlaskApiSpec: """Flask-apispec extension. @@ -52,6 +69,8 @@ def __init__(self, app=None, document_options=True): def init_app(self, app): self.app = app + global config + config = self.app.config self.spec = self.app.config.get('APISPEC_SPEC') or \ make_apispec(self.app.config.get('APISPEC_TITLE', 'flask-apispec'), self.app.config.get('APISPEC_VERSION', 'v1'), @@ -93,6 +112,7 @@ def add_swagger_routes(self): def swagger_json(self): return flask.jsonify(self.spec.to_dict()) + @auth.login_required def swagger_ui(self): return flask.render_template('swagger-ui.html') diff --git a/setup.py b/setup.py index 2f67f59..ed6ee6e 100755 --- a/setup.py +++ b/setup.py @@ -4,6 +4,7 @@ REQUIRES = [ 'flask>=0.10.1', + 'Flask-HTTPAuth>=4.1.0' 'marshmallow>=3.0.0', 'webargs>=6.0.0', 'apispec>=4.0.0', diff --git a/tests/test_extension.py b/tests/test_extension.py index 5fba9e7..e410add 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -1,4 +1,5 @@ import pytest +import base64 from flask import Blueprint from flask_apispec import doc @@ -87,6 +88,34 @@ def test_serve_swagger_ui_custom_url(self, app, client): FlaskApiSpec(app) client.get('/swagger-ui.html') + def test_serve_swagger_ui_invalid_http_auth(self, app, client): + app.config['APISPEC_AUTH'] = dict( + ENABLED=True, + USERNAME="username", + PASSWORD="password" + ) + FlaskApiSpec(app) + res = client.get('/swagger-ui/', expect_errors=True) + assert res.status_code == 401 + + def test_serve_swagger_ui_valid_http_auth(self, app, client): + app.config['APISPEC_AUTH'] = dict( + ENABLED=True, + USERNAME="username", + PASSWORD="password" + ) + FlaskApiSpec(app) + + valid_credentials = base64.b64encode( + b"username:password" + ).decode("utf-8") + res = client.get( + '/swagger-ui/', + headers={"Authorization": "Basic " + valid_credentials}, + expect_errors=True + ) + assert res.status_code == 200 + def test_apispec_config(self, app): app.config['APISPEC_TITLE'] = 'test-extension' app.config['APISPEC_VERSION'] = '2.1'