diff --git a/configs/config.yaml b/configs/config.yaml index 4414400..893001c 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -178,8 +178,10 @@ iris_plan_integration: # Configures whether Slack settings will appear in the frontend slack_instance: foobar -# setting to toggle login splash screen. This will require users to log in to be able to use the app. Set to True to enable and False to disable. -force_login_splash: False +# Setting to determine whether the Oncall API/frontend is available unauthenticated. +# Set to True to force login splash page and authentication on API calls and False to allow read +# APIs without authentication +require_auth: False ########################## ### Oncall user management diff --git a/src/oncall/app.py b/src/oncall/app.py index ed5124d..7d83f60 100644 --- a/src/oncall/app.py +++ b/src/oncall/app.py @@ -11,7 +11,7 @@ import re from beaker.middleware import SessionMiddleware from falcon_cors import CORS -from . import db, constants, iris +from . import db, constants, iris, auth import logging logger = logging.getLogger('oncall.app') @@ -47,17 +47,35 @@ class ReqBodyMiddleware(object): req.context['body'] = req.stream.read() +class AuthMiddleware(object): + def process_resource(self, req, resp, resource, params): + try: + if resource.allow_no_auth: + return + except AttributeError: + pass + + auth_token = req.get_header('AUTHORIZATION') + if auth_token: + auth.authenticate_application(auth_token, req) + else: + auth.authenticate_user(req) + + application = None def init_falcon_api(config): global application cors = CORS(allow_origins_list=config.get('allow_origins_list', [])) - application = falcon.API(middleware=[ + middlewares = [ SecurityHeaderMiddleware(), ReqBodyMiddleware(), cors.middleware - ]) + ] + if config.get('require_auth'): + middlewares.append(AuthMiddleware()) + application = falcon.API(middleware=middlewares) application.req_options.auto_parse_form_urlencoded = False application.set_error_serializer(json_error_serializer) from .auth import init as init_auth diff --git a/src/oncall/auth/__init__.py b/src/oncall/auth/__init__.py index f8fbdd4..11bcbbe 100644 --- a/src/oncall/auth/__init__.py +++ b/src/oncall/auth/__init__.py @@ -241,8 +241,9 @@ def init(application, config): check_calendar_auth_by_id = lambda x, y: True debug_only = lambda function: function - if config.get('docs'): + if config.get('docs') or config.get('require_auth'): # Replace login_required decorator with identity function for autodoc generation + # Also replace if require_auth is True, since AuthMiddleware already handles login for us global login_required login_required = lambda x: x else: diff --git a/src/oncall/auth/login.py b/src/oncall/auth/login.py index b78d94c..804ecb5 100644 --- a/src/oncall/auth/login.py +++ b/src/oncall/auth/login.py @@ -11,6 +11,8 @@ from oncall import db from random import SystemRandom from . import auth_manager +allow_no_auth = True + def on_post(req, resp): login_info = uri.parse_query_string(req.context['body']) diff --git a/src/oncall/ui/__init__.py b/src/oncall/ui/__init__.py index a72e4c9..f511853 100644 --- a/src/oncall/ui/__init__.py +++ b/src/oncall/ui/__init__.py @@ -73,12 +73,12 @@ SLACK_INSTANCE = None HEADER_COLOR = None IRIS_PLAN_SETTINGS = None USERCONTACT_UI_READONLY = None -LOGIN_SPLASH = None +LOGIN_REQUIRED = None def index(req, resp): user = req.env.get('beaker.session', {}).get('user') - if str(user) == 'None' and LOGIN_SPLASH: + if user is None and LOGIN_REQUIRED: resp.content_type = 'text/html' resp.body = jinja2_env.get_template('loginsplash.html').render() else: @@ -110,6 +110,8 @@ def secure_filename(filename): class StaticResource(object): + allow_no_auth = True + def __init__(self, path): self.path = path.lstrip('/') @@ -135,12 +137,12 @@ def init(application, config): global HEADER_COLOR global IRIS_PLAN_SETTINGS global USERCONTACT_UI_READONLY - global LOGIN_SPLASH + global LOGIN_REQUIRED SLACK_INSTANCE = config.get('slack_instance') HEADER_COLOR = config.get('header_color', '#3a3a3a') IRIS_PLAN_SETTINGS = config.get('iris_plan_integration') USERCONTACT_UI_READONLY = config.get('usercontact_ui_readonly', True) - LOGIN_SPLASH = config.get('force_login_splash') + LOGIN_REQUIRED = config.get('require_auth', True) application.add_sink(index, '/') application.add_route('/static/bundles/{filename}',