atst/atst/app.py
2018-12-20 11:23:01 -05:00

217 lines
6.5 KiB
Python

import os
import re
import pathlib
from configparser import ConfigParser
from flask import Flask, request, g
from flask_session import Session
import redis
from unipath import Path
from flask_wtf.csrf import CSRFProtect
from atst.database import db
from atst.assets import environment as assets_environment
from atst.filters import register_filters
from atst.routes import bp
from atst.routes.workspaces import workspaces_bp as workspace_routes
from atst.routes.requests import requests_bp
from atst.routes.task_orders import task_orders_bp
from atst.routes.dev import bp as dev_routes
from atst.routes.users import bp as user_routes
from atst.routes.errors import make_error_pages
from atst.domain.authnid.crl import CRLCache
from atst.domain.auth import apply_authentication
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
from atst.eda_client import MockEDAClient
from atst.uploader import Uploader
from atst.utils import mailer
from atst.utils.form_cache import FormCache
from atst.queue import queue
ENV = os.getenv("FLASK_ENV", "dev")
def make_app(config):
parent_dir = Path().parent
app = Flask(
__name__,
template_folder=parent_dir.child("templates").absolute(),
static_folder=parent_dir.child("static").absolute(),
)
make_redis(app, config)
csrf = CSRFProtect()
app.config.update(config)
app.config.update({"SESSION_REDIS": app.redis})
make_flask_callbacks(app)
if app.config.get("REQUIRE_CRLS"):
make_crl_validator(app)
register_filters(app)
make_eda_client(app)
make_upload_storage(app)
make_mailer(app)
queue.init_app(app)
db.init_app(app)
csrf.init_app(app)
Session(app)
assets_environment.init_app(app)
make_error_pages(app)
app.register_blueprint(bp)
app.register_blueprint(workspace_routes)
app.register_blueprint(requests_bp)
app.register_blueprint(task_orders_bp)
app.register_blueprint(user_routes)
if ENV != "prod":
app.register_blueprint(dev_routes)
app.form_cache = FormCache(app.redis)
apply_authentication(app)
set_default_headers(app)
return app
def make_flask_callbacks(app):
@app.before_request
def _set_globals():
g.current_user = None
g.dev = os.getenv("FLASK_ENV", "dev") == "dev"
g.matchesPath = lambda href: re.match("^" + href, request.path)
g.modal = request.args.get("modal", None)
g.Authorization = Authorization
g.Permissions = Permissions
@app.after_request
def _cleanup(response):
g.current_user = None
return response
def set_default_headers(app): # pragma: no cover
@app.after_request
def _set_security_headers(response):
response.headers[
"Strict-Transport-Security"
] = "max-age=31536000; includeSubDomains"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "SAMEORIGIN"
response.headers["X-XSS-Protection"] = "1; mode=block"
if ENV == "dev":
response.headers[
"Content-Security-Policy"
] = "default-src 'self' 'unsafe-eval' 'unsafe-inline'; connect-src *"
else:
response.headers[
"Content-Security-Policy"
] = "default-src 'self' 'unsafe-eval' 'unsafe-inline'"
return response
def map_config(config):
return {
**config["default"],
"ENV": config["default"]["ENVIRONMENT"],
"DEBUG": config["default"].getboolean("DEBUG"),
"PORT": int(config["default"]["PORT"]),
"SQLALCHEMY_DATABASE_URI": config["default"]["DATABASE_URI"],
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
"WTF_CSRF_ENABLED": config.getboolean("default", "WTF_CSRF_ENABLED"),
"PERMANENT_SESSION_LIFETIME": config.getint(
"default", "PERMANENT_SESSION_LIFETIME"
),
"REQUIRE_CRLS": config.getboolean("default", "REQUIRE_CRLS"),
"RQ_REDIS_URL": config["default"]["REDIS_URI"],
"RQ_QUEUES": [config["default"]["RQ_QUEUES"]],
}
def make_config():
BASE_CONFIG_FILENAME = os.path.join(os.path.dirname(__file__), "../config/base.ini")
ENV_CONFIG_FILENAME = os.path.join(
os.path.dirname(__file__), "../config/", "{}.ini".format(ENV.lower())
)
OVERRIDE_CONFIG_FILENAME = os.getenv("OVERRIDE_CONFIG_FULLPATH")
config = ConfigParser()
config.optionxform = str
config_files = [BASE_CONFIG_FILENAME, ENV_CONFIG_FILENAME]
if OVERRIDE_CONFIG_FILENAME:
config_files.append(OVERRIDE_CONFIG_FILENAME)
# ENV_CONFIG will override values in BASE_CONFIG.
config.read(config_files)
# Check for ENV variables as a final source of overrides
for confsetting in config.options("default"):
env_override = os.getenv(confsetting.upper())
if env_override:
config.set("default", confsetting, env_override)
# Assemble DATABASE_URI value
database_uri = (
"postgres://"
+ config.get("default", "PGUSER")
+ ":"
+ config.get("default", "PGPASSWORD")
+ "@"
+ config.get("default", "PGHOST")
+ ":"
+ config.get("default", "PGPORT")
+ "/"
+ config.get("default", "PGDATABASE")
)
config.set("default", "DATABASE_URI", database_uri)
return map_config(config)
def make_redis(app, config):
r = redis.Redis.from_url(config["REDIS_URI"])
app.redis = r
def make_crl_validator(app):
crl_locations = []
for filename in pathlib.Path(app.config["CRL_DIRECTORY"]).glob("*.crl"):
crl_locations.append(filename.absolute())
app.crl_cache = CRLCache(app.config["CA_CHAIN"], crl_locations, logger=app.logger)
def make_eda_client(app):
app.eda_client = MockEDAClient()
def make_upload_storage(app):
uploader = Uploader(
provider=app.config.get("STORAGE_PROVIDER"),
container=app.config.get("STORAGE_CONTAINER"),
key=app.config.get("STORAGE_KEY"),
secret=app.config.get("STORAGE_SECRET"),
)
app.uploader = uploader
def make_mailer(app):
if app.config["DEBUG"]:
mailer_connection = mailer.RedisConnection(app.redis)
else:
mailer_connection = mailer.SMTPConnection(
server=app.config.get("MAIL_SERVER"),
port=app.config.get("MAIL_PORT"),
username=app.config.get("MAIL_SENDER"),
password=app.config.get("MAIL_PASSWORD"),
use_tls=app.config.get("MAIL_TLS"),
)
sender = app.config.get("MAIL_SENDER")
app.mailer = mailer.Mailer(mailer_connection, sender)