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

Be more explicit about configs and where they come from #535

Merged
merged 6 commits into from
Oct 29, 2024
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
11 changes: 0 additions & 11 deletions dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
import os

from everett.manager import ConfigManager # type: ignore
from everett.manager import ConfigOSEnv # type: ignore

# -*- coding: utf-8 -*-

"""Mozilla Single Signon Dashboard."""

__author__ = """Andrew Krug"""
Expand All @@ -13,7 +6,3 @@


__all__ = ["app", "auth", "config", "models", "s3", "utils", "vanity"]


def get_config():
return ConfigManager([ConfigOSEnv()])
20 changes: 10 additions & 10 deletions dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

from dashboard import oidc_auth
from dashboard import config
from dashboard import get_config
from dashboard import vanity

from dashboard.csp import DASHBOARD_CSP
Expand All @@ -37,18 +36,19 @@

logging.config.fileConfig("dashboard/logging.ini")

if config.Config(None).settings.DEBUG:
app_config = config.Default()

if app_config.DEBUG:
# Set the log level to DEBUG for all defined loggers
for logger_name in logging.root.manager.loggerDict.keys():
logging.getLogger(logger_name).setLevel("DEBUG")

app = Flask(__name__)
app.config.from_object(app_config)

talisman = Talisman(app, content_security_policy=DASHBOARD_CSP, force_https=False)

app.config.from_object(config.Config(app).settings)

app_list = CDNTransfer(config.Config(app).settings)
app_list = CDNTransfer(app_config)


def session_configure(app: Flask) -> SessionInterface:
Expand Down Expand Up @@ -90,7 +90,7 @@ def session_configure(app: Flask) -> SessionInterface:
# Hack to support serving .svg
mimetypes.add_type("image/svg+xml", ".svg")

oidc_config = config.OIDCConfig()
oidc_config = config.OIDC()
authentication = oidc_auth.OpenIDConnect(oidc_config)
oidc = authentication.get_oidc(app)

Expand All @@ -104,7 +104,7 @@ def favicon():

@app.route("/")
def home():
if config.Config(app).environment == "local":
if app.config["ENVIRONMENT"] == "local":
return redirect("dashboard", code=302)

url = request.url.replace("http://", "https://", 1)
Expand Down Expand Up @@ -213,15 +213,15 @@ def dashboard():
# If an update is found, all gunicorn workers will be reloaded
app_list.sync_config()

user = User(session, config.Config(app).settings)
user = User(session, app.config)
apps = user.apps(Application(app_list.apps_yml).apps)

return render_template("dashboard.html", config=app.config, user=user, apps=apps)


@app.route("/styleguide/dashboard")
def styleguide_dashboard():
user = FakeUser(config.Config(app).settings)
user = FakeUser(app.config)
apps = user.apps(Application(app_list.apps_yml).apps)

return render_template("dashboard.html", config=app.config, user=user, apps=apps)
Expand All @@ -230,7 +230,7 @@ def styleguide_dashboard():
@app.route("/styleguide/notifications")
@oidc.oidc_auth("default")
def styleguide_notifications():
user = FakeUser(config.Config(app).settings)
user = FakeUser(app.config)
return render_template("notifications.html", config=app.config, user=user)


Expand Down
98 changes: 43 additions & 55 deletions dashboard/config.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,67 @@
"""Configuration loader for different environments."""

import os
import base64
import datetime
from dashboard import get_config

CONFIG = get_config()


class Config(object):
def __init__(self, app):
self.app = app

self.environment = CONFIG("environment", default="local")
self.settings = self._init_env()

def _init_env(self):
return DefaultConfig()


class DefaultConfig(object):
class Default:
"""Defaults for the configuration objects."""

DEBUG = bool(CONFIG("debug", namespace="sso-dashboard", parser=bool, default="False"))
TESTING = bool(CONFIG("testing", namespace="sso-dashboard", parser=bool, default="False"))

CSRF_ENABLED = bool(CONFIG("csrf_enabled", parser=bool, default="True"))
PERMANENT_SESSION = bool(CONFIG("permanent_session", namespace="sso-dashboard", parser=bool, default="True"))
seconds = int(CONFIG("permanent_session_lifetime", namespace="sso-dashboard", default="86400"))
PERMANENT_SESSION_LIFETIME = datetime.timedelta(seconds=seconds)
ENVIRONMENT: str = os.environ.get("ENVIRONMENT", "local")
DEBUG: bool = os.environ.get("SSO-DASHBOARD_DEBUG", "True") == "True"
TESTING: bool = os.environ.get("SSO-DASHBOARD_TESTING", "True") == "True"

SESSION_COOKIE_SAMESITE = CONFIG("session_cookie_samesite", namespace="sso-dashboard", default="lax")
SESSION_COOKIE_HTTPONLY = bool(
CONFIG("session_cookie_httponly", namespace="sso-dashboard", parser=bool, default="True")
CSRF_ENABLED: bool = os.environ.get("SSO-DASHBOARD_CSRF_ENABLED", "True") == "True"
PERMANENT_SESSION: bool = os.environ.get("SSO-DASHBOARD_PERMANENT_SESSION", "True") == "True"
PERMANENT_SESSION_LIFETIME: datetime.timedelta = datetime.timedelta(
seconds=int(os.environ.get("SSO-DASHBOARD_PERMANENT_SESSION_LIFETIME", "86400"))
)

SECRET_KEY = CONFIG("secret_key", namespace="sso-dashboard")
SERVER_NAME = CONFIG("server_name", namespace="sso-dashboard", default="localhost:8000")
SESSION_COOKIE_NAME = SERVER_NAME + "_session"
SESSION_COOKIE_SAMESITE: str = os.environ.get("SSO-DASHBOARD_SESSION_COOKIE_SAMESITE", "lax")
SESSION_COOKIE_HTTPONLY: bool = os.environ.get("SSO-DASHBOARD_SESSION_COOKIE_HTTPONLY", "True") == "True"

S3_BUCKET = CONFIG("s3_bucket", namespace="sso-dashboard")
SECRET_KEY: str
SERVER_NAME: str = os.environ.get("SSO-DASHBOARD_SERVER_NAME", "localhost:8000")
SESSION_COOKIE_NAME: str
CDN: str

CDN = CONFIG(
"cdn",
namespace="sso-dashboard",
default="https://cdn.{SERVER_NAME}".format(SERVER_NAME=SERVER_NAME),
)
S3_BUCKET: str
FORBIDDEN_PAGE_PUBLIC_KEY: bytes

FORBIDDEN_PAGE_PUBLIC_KEY = base64.b64decode(CONFIG("forbidden_page_public_key", namespace="sso-dashboard"))
PREFERRED_URL_SCHEME: str = os.environ.get("SSO-DASHBOARD_PREFERRED_URL_SCHEME", "https")
REDIS_CONNECTOR: str

PREFERRED_URL_SCHEME = CONFIG("preferred_url_scheme", namespace="sso-dashboard", default="https")

REDIS_CONNECTOR = CONFIG("redis_connector", namespace="sso-dashboard")
def __init__(self):
self.SESSION_COOKIE_NAME = f"{self.SERVER_NAME}_session"
self.CDN = os.environ.get("SSO-DASHBOARD_CDN", f"https://cdn.{self.SERVER_NAME}")
self.REDIS_CONNECTOR = os.environ["SSO-DASHBOARD_REDIS_CONNECTOR"]
self.SECRET_KEY = os.environ["SSO-DASHBOARD_SECRET_KEY"]
self.S3_BUCKET = os.environ["SSO-DASHBOARD_S3_BUCKET"]
self.FORBIDDEN_PAGE_PUBLIC_KEY = base64.b64decode(os.environ["SSO-DASHBOARD_FORBIDDEN_PAGE_PUBLIC_KEY"])


class OIDCConfig(object):
class OIDC:
"""Convienience Object for returning required vars to flask."""

OIDC_DOMAIN: str
OIDC_CLIENT_ID: str
OIDC_CLIENT_SECRET: str
OIDC_REDIRECT_URI: str
LOGIN_URL: str

def __init__(self):
"""General object initializer."""
CONFIG = get_config()
self.OIDC_DOMAIN = CONFIG("oidc_domain", namespace="sso-dashboard")
self.OIDC_CLIENT_ID = CONFIG("oidc_client_id", namespace="sso-dashboard")
self.OIDC_CLIENT_SECRET = CONFIG("oidc_client_secret", namespace="sso-dashboard")
self.LOGIN_URL = "https://{DOMAIN}/login?client={CLIENT_ID}".format(
DOMAIN=self.OIDC_DOMAIN, CLIENT_ID=self.OIDC_CLIENT_ID
)
self.OIDC_DOMAIN = os.environ["SSO-DASHBOARD_OIDC_DOMAIN"]
self.OIDC_CLIENT_ID = os.environ["SSO-DASHBOARD_OIDC_CLIENT_ID"]
self.OIDC_CLIENT_SECRET = os.environ["SSO-DASHBOARD_OIDC_CLIENT_SECRET"]
# Check for a prefixed environment variable, otherwise fallback to the
# unprefixed one.
if redirect_uri := os.environ.get("SSO-DASHBOARD_OIDC_REDIRECT_URI"):
self.OIDC_REDIRECT_URI = redirect_uri
else:
self.OIDC_REDIRECT_URI = os.environ["OIDC_REDIRECT_URI"]
self.LOGIN_URL = f"https://{self.OIDC_DOMAIN}/login?client={self.OIDC_CLIENT_ID}"

@property
def client_id(self):
Expand All @@ -73,12 +70,3 @@ def client_id(self):
@property
def client_secret(self):
return self.OIDC_CLIENT_SECRET

def auth_endpoint(self):
return "https://{DOMAIN}/authorize".format(DOMAIN=self.OIDC_DOMAIN)

def token_endpoint(self):
return "https://{DOMAIN}/oauth/token".format(DOMAIN=self.OIDC_DOMAIN)

def userinfo_endpoint(self):
return "https://{DOMAIN}/userinfo".format(DOMAIN=self.OIDC_DOMAIN)
4 changes: 0 additions & 4 deletions dashboard/oidc_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,6 @@ def connection(self) -> Optional[str]:
def preferred_connection_name(self) -> Optional[str]:
return self.jws_data.get("preferred_connection_name")

@property
def redirect_uri(self) -> str:
return self.jws_data.get("redirect_uri", "https://sso.mozilla.com")

def signed(self) -> bool:
"""
By the time we get here we've got a valid key, and a properly
Expand Down
53 changes: 41 additions & 12 deletions env.example
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
### This file is a sample environment file for use with the project.
# To see these in action see the `clouddeploy` directory.

# This should be random in production deployment used in session security.
SECRET_KEY="this is a secret key"
# Easy way to generate:
# openssl rand -hex 64
SSO-DASHBOARD_SECRET_KEY="this is a secret key"

# Development or production.
ENVIRONMENT="Development"
# local, development, staging, or production.
ENVIRONMENT="local"

# OpenID Connect Specific Parameters
OIDC_DOMAIN="auth0.example.com"
OIDC_CLIENT_ID=""
OIDC_CLIENT_SECRET=""
# We use Auth0, the configs for this would be under the Application "Settings"
# page.
SSO-DASHBOARD_OIDC_DOMAIN="auth0.example.com"
SSO-DASHBOARD_OIDC_CLIENT_ID=""
SSO-DASHBOARD_OIDC_CLIENT_SECRET=""

# User avatar parameters
MOZILLIANS_API_URL='https://mozillians.org/api/v2/users/'
MOZILLIANS_API_KEY=''
# Yes, this one is not namespaced.
# DEBT(bhee): standardize at some point.
OIDC_REDIRECT_URI='https://localhost.localdomain:8000/redirect_uri'

SERVER_NAME="localhost:5000"
PERMANENT_SESSION=True
PERMANENT_SESSION_LIFETIME=900
# Controls the logging levels
SSO-DASHBOARD_DEBUG=True

# Unused right now.
SSO-DASHBOARD_TESTING=False

# Reasonable for local development, you'll want to change these in production
# though.
SSO-DASHBOARD_CSRF_ENABLED=True
SSO-DASHBOARD_PERMANENT_SESSION=True
SSO-DASHBOARD_PERMANENT_SESSION_LIFETIME=86400
SSO-DASHBOARD_SESSION_COOKIE_HTTPONLY=True
SSO-DASHBOARD_SERVER_NAME=localhost.localdomain:8000
SSO-DASHBOARD_PREFERRED_URL_SCHEME=http

# You'll need a running redis.
# See compose.yml.
SSO-DASHBOARD_REDIS_CONNECTOR=redis:6379

# Where we publish the `apps.yml` file.
SSO-DASHBOARD_CDN=https://cdn.sso.mozilla.com
SSO-DASHBOARD_S3_BUCKET=sso-dashboard.configuration

# Used to decode the JWS from Auth0 (e.g. for if we redirect back and there's some
# context Auth0 passes us, like the error code.)
# Base64 + PEM encoded.
SSO-DASHBOARD_FORBIDDEN_PAGE_PUBLIC_KEY=""
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ cryptography==43.0.1
cssmin==0.2.0
defusedxml==0.7.1
distlib==0.3.8
everett==3.3.0
Faker==26.1.0
filelock==3.15.4
Flask==3.0.3
Expand Down
Loading