From be4009e5963bd826d540d82a54e96504eb80833d Mon Sep 17 00:00:00 2001 From: Jonathan Sick Date: Mon, 14 Mar 2022 13:15:54 -0400 Subject: [PATCH] Move root and auth endpoints out of v1 blueprint This enables the root "/" and auth "/token" endpoints to always be accessible, even if the rest of the "v1" API is disabled. --- docs/auth.rst | 2 +- keeper/api/__init__.py | 2 -- keeper/api/_models.py | 50 +----------------------------- keeper/apiroot/__init__.py | 5 +++ keeper/apiroot/_models.py | 55 +++++++++++++++++++++++++++++++++ keeper/apiroot/_urls.py | 0 keeper/{api => apiroot}/auth.py | 4 +-- keeper/{api => apiroot}/root.py | 16 ++++++---- keeper/appfactory.py | 4 +++ 9 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 keeper/apiroot/__init__.py create mode 100644 keeper/apiroot/_models.py create mode 100644 keeper/apiroot/_urls.py rename keeper/{api => apiroot}/auth.py (95%) rename keeper/{api => apiroot}/root.py (63%) diff --git a/docs/auth.rst b/docs/auth.rst index 13ebc04d..5e2ddbdf 100644 --- a/docs/auth.rst +++ b/docs/auth.rst @@ -3,4 +3,4 @@ Authentication - `/token` ######################### .. autoflask:: keeper:flask_app - :endpoints: api.get_auth_token + :endpoints: apiroot.get_auth_token diff --git a/keeper/api/__init__.py b/keeper/api/__init__.py index a921b574..080e7349 100644 --- a/keeper/api/__init__.py +++ b/keeper/api/__init__.py @@ -3,7 +3,6 @@ api = Blueprint("api", __name__) from . import ( - auth, builds, dashboards, editions, @@ -11,5 +10,4 @@ post_products_builds, products, queue, - root, ) diff --git a/keeper/api/_models.py b/keeper/api/_models.py index 0caf9617..5c88502b 100644 --- a/keeper/api/_models.py +++ b/keeper/api/_models.py @@ -5,7 +5,7 @@ import datetime from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional -from pydantic import BaseModel, Field, HttpUrl, SecretStr, validator +from pydantic import BaseModel, Field, HttpUrl, validator from keeper.editiontracking import EditionTrackingModes from keeper.exceptions import ValidationError @@ -28,54 +28,6 @@ from keeper.models import Build, Edition, Product -class AuthTokenResponse(BaseModel): - """The auth token resource.""" - - token: SecretStr - """Token string. Use this token in the basic auth "username" field.""" - - class Config: - json_encoders = { - SecretStr: lambda v: v.get_secret_value() if v else None, - } - - -class RootLinks(BaseModel): - """Sub-resource containing links to APIs.""" - - self_url: HttpUrl - """The URL of this resource.""" - - token: HttpUrl - """The URL of the authorization endpoint to obtain a token.""" - - products: HttpUrl - """The endpoint for the products listing.""" - - -class RootData(BaseModel): - """Sub-resource providing metadata about the service.""" - - server_version: str - """The service vesion.""" - - documentation: HttpUrl - """The URL of the service's documentation.""" - - message: str - """Description of the service.""" - - -class RootResponse(BaseModel): - """The root endpoint resources provides metadata and links for the - service. - """ - - data: RootData - - links: RootLinks - - class PresignedPostUrl(BaseModel): """An S3 presigned post URL and associated metadata.""" diff --git a/keeper/apiroot/__init__.py b/keeper/apiroot/__init__.py new file mode 100644 index 00000000..b551386d --- /dev/null +++ b/keeper/apiroot/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +apiroot = Blueprint("apiroot", __name__) + +from . import auth, root diff --git a/keeper/apiroot/_models.py b/keeper/apiroot/_models.py new file mode 100644 index 00000000..743ae399 --- /dev/null +++ b/keeper/apiroot/_models.py @@ -0,0 +1,55 @@ +"""Pydantic Models for the root API endpoints.""" + +from __future__ import annotations + +from typing import Optional + +from pydantic import BaseModel, HttpUrl, SecretStr + + +class AuthTokenResponse(BaseModel): + """The auth token resource.""" + + token: SecretStr + """Token string. Use this token in the basic auth "username" field.""" + + class Config: + json_encoders = { + SecretStr: lambda v: v.get_secret_value() if v else None, + } + + +class RootLinks(BaseModel): + """Sub-resource containing links to APIs.""" + + self_url: HttpUrl + """The URL of this resource.""" + + token: HttpUrl + """The URL of the authorization endpoint to obtain a token.""" + + products: Optional[HttpUrl] = None + """The endpoint for the products listing.""" + + +class RootData(BaseModel): + """Sub-resource providing metadata about the service.""" + + server_version: str + """The service vesion.""" + + documentation: HttpUrl + """The URL of the service's documentation.""" + + message: str + """Description of the service.""" + + +class RootResponse(BaseModel): + """The root endpoint resources provides metadata and links for the + service. + """ + + data: RootData + + links: RootLinks diff --git a/keeper/apiroot/_urls.py b/keeper/apiroot/_urls.py new file mode 100644 index 00000000..e69de29b diff --git a/keeper/api/auth.py b/keeper/apiroot/auth.py similarity index 95% rename from keeper/api/auth.py rename to keeper/apiroot/auth.py index 2f765e32..df62299f 100644 --- a/keeper/api/auth.py +++ b/keeper/apiroot/auth.py @@ -5,14 +5,14 @@ from flask import g from flask_accept import accept_fallback -from keeper.api import api +from keeper.apiroot import apiroot from keeper.auth import password_auth from keeper.logutils import log_route from ._models import AuthTokenResponse -@api.route("/token") +@apiroot.route("/token") @accept_fallback @log_route() @password_auth.login_required diff --git a/keeper/api/root.py b/keeper/apiroot/root.py similarity index 63% rename from keeper/api/root.py rename to keeper/apiroot/root.py index 9315226f..f5428d92 100644 --- a/keeper/api/root.py +++ b/keeper/apiroot/root.py @@ -1,16 +1,16 @@ """Root API route (GET /).""" -from flask import url_for +from flask import current_app, url_for from flask_accept import accept_fallback -from keeper.api import api +from keeper.apiroot import apiroot from keeper.logutils import log_route from keeper.version import get_version from ._models import RootData, RootLinks, RootResponse -@api.route("/", methods=["GET"]) +@apiroot.route("/", methods=["GET"]) @accept_fallback @log_route() def get_root() -> str: @@ -25,9 +25,13 @@ def get_root() -> str: ), ) links = RootLinks( - self_url=url_for("api.get_root", _external=True), - token=url_for("api.get_auth_token", _external=True), - products=url_for("api.get_products", _external=True), + self_url=url_for("apiroot.get_root", _external=True), + token=url_for("apiroot.get_auth_token", _external=True), + products=( + url_for("api.get_products", _external=True) + if current_app.config["ENABLE_V1_API"] + else None + ), ) response = RootResponse(data=root_data, links=links) return response.json(by_alias=True) diff --git a/keeper/appfactory.py b/keeper/appfactory.py index 6270bda8..b1036b0b 100644 --- a/keeper/appfactory.py +++ b/keeper/appfactory.py @@ -57,6 +57,10 @@ def create_flask_app(profile: Optional[str] = None) -> Flask: ) # for sqlite; safe for other servers # Register blueprints + from keeper.apiroot import apiroot as apiroot_blueprint + + app.register_blueprint(apiroot_blueprint, url_prefix=None) + if app.config["ENABLE_V1_API"]: from keeper.api import api as api_blueprint