Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: implement authorization + CORS #21

Merged
merged 13 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.8", "3.10" ]
python-version: [ "3.10", "3.11" ]
services:
redis:
image: redis
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.05.12
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.07.17

# Run as root in the Dockerfile until we drop down to the service user in the entrypoint
USER root
Expand Down
12 changes: 11 additions & 1 deletion bento_notification_service/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
flask_error_wrap_with_traceback,
flask_internal_server_error,
flask_bad_request_error,
flask_forbidden_error,
flask_not_found_error
)
from flask import Flask
from flask_cors import CORS
from flask_migrate import Migrate
from werkzeug.exceptions import BadRequest, NotFound
from werkzeug.exceptions import BadRequest, Forbidden, NotFound

from .authz import authz_middleware
from .config import Config
from .constants import MIGRATION_DIR, SERVICE_NAME
from .db import db
Expand All @@ -20,6 +23,12 @@ def create_app() -> Flask:
application = Flask(__name__)
application.config.from_object(Config)

# Set up CORS
CORS(application, origins=Config.CORS_ORIGINS)

# Attach authorization middleware to application
authz_middleware.attach(application)

# Initialize SQLAlchemy and migrate the database if necessary
db.init_app(application)
Migrate(application, db, directory=MIGRATION_DIR)
Expand All @@ -37,6 +46,7 @@ def create_app() -> Flask:
)
)
application.register_error_handler(BadRequest, flask_error_wrap(flask_bad_request_error))
application.register_error_handler(Forbidden, flask_error_wrap(flask_forbidden_error))
application.register_error_handler(NotFound, flask_error_wrap(flask_not_found_error))

# Start the event loop, or exit the service if Redis isn't available
Expand Down
15 changes: 15 additions & 0 deletions bento_notification_service/authz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from bento_lib.auth.middleware.flask import FlaskAuthMiddleware
from .config import Config

__all__ = [
"authz_middleware",
"PERMISSION_VIEW_NOTIFICATIONS",
]

authz_middleware = FlaskAuthMiddleware(
Config.AUTHZ_URL,
debug_mode=Config.BENTO_DEBUG,
enabled=Config.AUTHZ_ENABLED,
)

PERMISSION_VIEW_NOTIFICATIONS = "view:notifications"
21 changes: 19 additions & 2 deletions bento_notification_service/config.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import logging
import os

from .constants import APP_DIR, SERVICE_TYPE

from .logger import logger as logger_

__all__ = [
"BASEDIR",
"Config",
]


TRUTH_VALUES = ("true", "1")

# DATABASE is set when deployed inside chord_singularity
BASEDIR = os.environ.get("DATABASE", APP_DIR.parent)


def _get_from_environ_or_fail(var: str, logger: logging.Logger = logger_) -> str:
if (val := os.environ.get(var, "")) == "":
logger.critical(f"{var} must be set")
exit(1)
return val


class Config:
BENTO_DEBUG = os.environ.get("BENTO_DEBUG", "false").strip().lower() in ("1", "true")
BENTO_DEBUG = os.environ.get("BENTO_DEBUG", "false").strip().lower() in TRUTH_VALUES

SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(BASEDIR, "db.sqlite3")
SQLALCHEMY_TRACK_MODIFICATIONS = False
Expand All @@ -23,3 +33,10 @@ class Config:
# Resort to Redis defaults for host/port if not set
REDIS_HOST = os.environ.get("REDIS_HOST") or "localhost"
REDIS_PORT = os.environ.get("REDIS_PORT") or 6379

# Authz
AUTHZ_ENABLED = os.environ.get("AUTHZ_ENABLED", "true").strip().lower() in TRUTH_VALUES
AUTHZ_URL: str = _get_from_environ_or_fail("BENTO_AUTHZ_SERVICE_URL").strip().rstrip("/") if AUTHZ_ENABLED else ""

# CORS
CORS_ORIGINS: list[str] | str = os.environ.get("CORS_ORIGINS", "").split(";") or "*"
10 changes: 10 additions & 0 deletions bento_notification_service/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import logging

__all__ = [
"logger",
]

logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
16 changes: 10 additions & 6 deletions bento_notification_service/routes.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import subprocess
import uuid

from bento_lib.auth.flask_decorators import flask_permissions_owner
from bento_lib.responses.flask_errors import flask_not_found_error
from flask import Blueprint, current_app, jsonify

from bento_notification_service import __version__
from . import __version__
from .authz import authz_middleware, PERMISSION_VIEW_NOTIFICATIONS
from .db import db
from .constants import BENTO_SERVICE_KIND, SERVICE_NAME, SERVICE_TYPE, ORG_C3G
from .models import Notification


RESOURCE_EVERYTHING = {"everything": True}
PERMISSION_SET_VIEW = frozenset({PERMISSION_VIEW_NOTIFICATIONS})

notification_service = Blueprint("notification_service", __name__)


@notification_service.route("/notifications", methods=["GET"])
@flask_permissions_owner
@authz_middleware.deco_require_permissions_on_resource(PERMISSION_SET_VIEW, RESOURCE_EVERYTHING)
def notification_list():
notifications = Notification.query.all()
return jsonify([n.serialize for n in notifications])


@notification_service.route("/notifications/all-read", methods=["PUT"])
@flask_permissions_owner
@authz_middleware.deco_require_permissions_on_resource(PERMISSION_SET_VIEW, RESOURCE_EVERYTHING)
def notification_all_read():
# TODO: This is slow/non-optimal but we shouldn't have enough notifications for it to matter.
# Ideally, it would be done using a bulk update query.
Expand All @@ -36,14 +39,14 @@ def notification_all_read():


@notification_service.route("/notifications/<uuid:n_id>", methods=["GET"])
@flask_permissions_owner
@authz_middleware.deco_require_permissions_on_resource(PERMISSION_SET_VIEW, RESOURCE_EVERYTHING)
def notification_detail(n_id: uuid.UUID):
notification = Notification.query.filter_by(id=str(n_id)).first()
return jsonify(notification.serialize) if notification else flask_not_found_error(f"Notification {n_id} not found")


@notification_service.route("/notifications/<uuid:n_id>/read", methods=["PUT"])
@flask_permissions_owner
@authz_middleware.deco_require_permissions_on_resource(PERMISSION_SET_VIEW, RESOURCE_EVERYTHING)
def notification_read(n_id: uuid.UUID):
notification = Notification.query.filter_by(id=str(n_id)).first()

Expand All @@ -57,6 +60,7 @@ def notification_read(n_id: uuid.UUID):


@notification_service.route("/service-info", methods=["GET"])
@authz_middleware.deco_public_endpoint
def service_info():
bento_debug = current_app.config["BENTO_DEBUG"]

Expand Down
2 changes: 1 addition & 1 deletion dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.05.12
FROM ghcr.io/bento-platform/bento_base_image:python-debian-2023.07.17

SHELL ["/bin/bash", "-c"]

Expand Down
3 changes: 3 additions & 0 deletions entrypoint.bash
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ if [[ -d /env ]]; then
chown -R bento_user:bento_user /env
fi

# Configure git from entrypoint, since we've overwritten the base image entrypoint
gosu bento_user /bin/bash -c '/set_gitconfig.bash'

# Drop into bento_user from root and execute the CMD specified for the image
exec gosu bento_user "$@"
Loading