Skip to content
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
8 changes: 8 additions & 0 deletions securedrop/config.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ JOURNALIST_KEY = '{{ securedrop_app_gpg_fingerprint }}'
# encrypted submissions.
SECUREDROP_DATA_ROOT = '/var/lib/securedrop'

# Feature flags for journalist interface services, with default production values.
# Only the v2 api is feature-flagged for now.

V2_API_ENABLED = False


# Modify configuration for alternative environments
env = os.environ.get('SECUREDROP_ENV') or 'prod'

Expand All @@ -57,11 +63,13 @@ elif env == 'dev':
# Use MAX_CONTENT_LENGTH to mimic the behavior of Apache's LimitRequestBody
# in the development environment. See #1714.
FlaskConfig.MAX_CONTENT_LENGTH = 524288000
V2_API_ENABLED = True
elif env == 'test':
FlaskConfig.TESTING = True
# Disable CSRF checks to make writing tests easier
FlaskConfig.WTF_CSRF_ENABLED = False
SECUREDROP_DATA_ROOT = '/tmp/securedrop'
V2_API_ENABLED = True

# The following configuration is dependent on SECUREDROP_DATA_ROOT

Expand Down
5 changes: 3 additions & 2 deletions securedrop/journalist_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ def setup_g() -> Optional[Response]:
api_blueprint = api.make_blueprint()
app.register_blueprint(api_blueprint, url_prefix="/api/v1")
csrf.exempt(api_blueprint)
app.register_blueprint(api2.blp, url_prefix="/api/v2")
csrf.exempt(api2.blp)
if config.V2_API_ENABLED:
app.register_blueprint(api2.blp, url_prefix="/api/v2")
csrf.exempt(api2.blp)

return app
5 changes: 5 additions & 0 deletions securedrop/sdconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class SecureDropConfig:

REDIS_PASSWORD: str

V2_API_ENABLED: bool

env: str = "prod"

@property
Expand Down Expand Up @@ -127,6 +129,8 @@ def _parse_config_from_file(config_module_name: str) -> SecureDropConfig:

env = getattr(config_from_local_file, "env", "prod")

final_v2_api_enabled = getattr(config_from_local_file, "V2_API_ENABLED", False)

try:
final_securedrop_root = Path(config_from_local_file.SECUREDROP_ROOT)
except AttributeError:
Expand Down Expand Up @@ -222,4 +226,5 @@ def _parse_config_from_file(config_module_name: str) -> SecureDropConfig:
SESSION_EXPIRATION_MINUTES=final_sess_expiration_mins,
RQ_WORKER_NAME=final_worker_name,
REDIS_PASSWORD=final_redis_password,
V2_API_ENABLED=final_v2_api_enabled,
)
2 changes: 2 additions & 0 deletions securedrop/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def create(
SUPPORTED_LOCALES: Optional[List[str]] = None,
DEFAULT_LOCALE: str = "en_US",
TRANSLATION_DIRS: Path = DEFAULT_SECUREDROP_ROOT / "translations",
V2_API_ENABLED: bool = True,
) -> SecureDropConfig:
"""Create a securedrop config suitable for the unit tests.

Expand Down Expand Up @@ -98,6 +99,7 @@ def create(
JOURNALIST_TEMPLATES_DIR=DEFAULT_SECUREDROP_ROOT / "journalist_templates",
DEFAULT_LOCALE=DEFAULT_LOCALE,
REDIS_PASSWORD=REDIS_PASSWORD,
V2_API_ENABLED=V2_API_ENABLED,
)

# Delete any previous/existing DB and initialize a new one
Expand Down
40 changes: 38 additions & 2 deletions securedrop/tests/test_journalist_api2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@
from copy import deepcopy
from dataclasses import asdict
from datetime import UTC, datetime
from pathlib import Path
from typing import Tuple
from uuid import uuid4

import pytest
from flask import url_for
from flask_sqlalchemy import get_debug_queries
from journalist_app import api2
from journalist_app import api2, create_app
from journalist_app.api2.shared import json_version
from journalist_app.api2.types import Event, EventType, ItemTarget, SourceTarget
from models import Reply, Source, SourceStar, Submission, db
from sqlalchemy.orm.exc import MultipleResultsFound
from tests.utils import ascii_armor, decrypt_as_journalist
from tests.factories import SecureDropConfigFactory
from tests.utils import ascii_armor, decrypt_as_journalist, i18n
from tests.utils.api_helper import get_api_headers
from tests.utils.db_helper import init_source, submit
from werkzeug.routing import BuildError


def filtered_queries():
Expand Down Expand Up @@ -56,6 +61,37 @@ def test_json_version():
assert version1 == version2


@pytest.mark.parametrize(
"endpoint",
[
"api2.index",
"api2.data",
],
)
def test_api2_not_available_when_disabled(
setup_journalist_key_and_gpg_folder: Tuple[str, Path],
setup_rqworker: Tuple[str, str],
endpoint: str,
) -> None:
journalist_key_fingerprint, gpg_key_dir = setup_journalist_key_and_gpg_folder
worker_name, _ = setup_rqworker
config_without_v2api = SecureDropConfigFactory.create(
SECUREDROP_DATA_ROOT=Path(f"/tmp/sd-tests/conftest-{uuid4()}"),
GPG_KEY_DIR=gpg_key_dir,
JOURNALIST_KEY=journalist_key_fingerprint,
SUPPORTED_LOCALES=i18n.get_test_locales(),
RQ_WORKER_NAME=worker_name,
V2_API_ENABLED=False,
)
app = create_app(config_without_v2api)
app.config["SERVER_NAME"] = "localhost.localdomain"

with app.app_context():
with app.test_client() as client_app:
with pytest.raises(BuildError):
client_app.get(url_for(endpoint))


@pytest.mark.parametrize(
("endpoint", "kwargs"),
[
Expand Down
Loading