• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla-releng / balrog / #4660

30 Jun 2025 12:35PM UTC coverage: 89.388%. First build
#4660

Pull #3435

circleci

jcristau
web: rename app to flask_app

We have a Connexion app which wraps a Flask app, so having something
called `app` can get confusing.
Pull Request #3435: web: rename app to flask_app

3155 of 3675 branches covered (85.85%)

Branch coverage included in aggregate %.

32 of 34 new or added lines in 2 files covered. (94.12%)

5689 of 6219 relevant lines covered (91.48%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.82
/src/auslib/web/admin/base.py
1
import logging
1✔
2
import re
1✔
3
from os import path
1✔
4

5
import connexion
1✔
6
from flask import request
1✔
7
from sentry_sdk import capture_exception
1✔
8
from specsynthase.specbuilder import SpecBuilder
1✔
9

10
import auslib
1✔
11
from auslib.dockerflow import create_dockerflow_endpoints
1✔
12
from auslib.errors import BlobValidationError, PermissionDeniedError, ReadOnlyError, SignoffRequiredError
1✔
13
from auslib.util.auth import verified_userinfo
1✔
14
from auslib.web.admin.views.problem import problem
1✔
15
from auslib.web.admin.views.validators import BalrogRequestBodyValidator
1✔
16

17
log = logging.getLogger(__name__)
1✔
18

19
current_dir = path.dirname(__file__)
1✔
20
web_dir = path.dirname(auslib.web.__file__)
1✔
21

22
spec = (
1✔
23
    SpecBuilder()
24
    .add_spec(path.join(current_dir, "swagger/api.yml"))
25
    .add_spec(path.join(web_dir, "common/swagger/definitions.yml"))
26
    .add_spec(path.join(web_dir, "common/swagger/parameters.yml"))
27
    .add_spec(path.join(web_dir, "common/swagger/responses.yml"))
28
)
29

30
validator_map = {"body": BalrogRequestBodyValidator}
1✔
31

32
connexion_app = connexion.App(__name__, debug=False, options={"swagger_ui": False})
1✔
33
connexion_app.add_api(spec, validator_map=validator_map, strict_validation=True)
1✔
34
connexion_app.add_api(path.join(current_dir, "swagger", "api_v2.yml"), base_path="/v2", strict_validation=True, validate_responses=True)
1✔
35
flask_app = connexion_app.app
1✔
36

37
create_dockerflow_endpoints(flask_app)
1✔
38

39

40
@flask_app.before_request
1✔
41
def setup_request():
1✔
42
    if request.full_path.startswith("/v2"):
1✔
43
        from auslib.global_state import dbo
1✔
44

45
        request.transaction = dbo.begin()
1✔
46

47
        if request.method in ("POST", "PUT", "DELETE"):
1✔
48
            username = verified_userinfo(request, flask_app.config["AUTH_DOMAIN"], flask_app.config["AUTH_AUDIENCE"])["email"]
1✔
49
            if not username:
1!
50
                log.warning("Login Required")
×
51
                return problem(401, "Unauthenticated", "Login Required")
×
52
            # Machine to machine accounts are identified by uninformative clientIds
53
            # In order to keep Balrog permissions more readable, we map them to
54
            # more useful usernames, which are stored in the app config.
55
            if "@" not in username:
1!
56
                username = flask_app.config["M2M_ACCOUNT_MAPPING"].get(username, username)
1✔
57
            # Even if the user has provided a valid access token, we don't want to assume
58
            # that person should be able to access Balrog (in case auth0 is not configured
59
            # to be restrictive enough.
60
            elif not dbo.isKnownUser(username):
×
61
                log.warning("Authorization Required")
×
62
                return problem(403, "Forbidden", "Authorization Required")
×
63

64
            request.username = username
1✔
65

66

67
@flask_app.after_request
1✔
68
def complete_request(response):
1✔
69
    if hasattr(request, "transaction"):
1✔
70
        try:
1✔
71
            if response.status_code >= 400:
1✔
72
                request.transaction.rollback()
1✔
73
            else:
74
                request.transaction.commit()
1✔
75
        finally:
76
            request.transaction.close()
1✔
77

78
    return response
1✔
79

80

81
@flask_app.errorhandler(BlobValidationError)
1✔
82
def blob_validation_error(error):
1✔
83
    return problem(400, "Bad Request", "Invalid Blob", ext={"exception": error.errors})
1✔
84

85

86
@flask_app.errorhandler(SignoffRequiredError)
1✔
87
def signoff_required_error(error):
1✔
88
    return problem(400, "Bad Request", "Signoff Required", ext={"exception": f"{error}"})
1✔
89

90

91
@flask_app.errorhandler(ReadOnlyError)
1✔
92
def read_only_error(error):
1✔
93
    return problem(400, "Bad Request", "Read only", ext={"exception": f"{error}"})
1✔
94

95

96
@flask_app.errorhandler(PermissionDeniedError)
1✔
97
def permission_denied_error(error):
1✔
98
    return problem(403, "Forbidden", "Permission Denied", ext={"exception": f"{error}"})
1✔
99

100

101
@flask_app.errorhandler(ValueError)
1✔
102
def value_error(error):
1✔
103
    return problem(400, "Bad Request", "Unknown error", ext={"exception": f"{error}"})
1✔
104

105

106
# Connexion's error handling sometimes breaks when parameters contain
107
# unicode characters (https://github.com/zalando/connexion/issues/604).
108
# To work around, we catch them and return a 400 (which is what Connexion
109
# would do if it didn't hit this error).
110
@flask_app.errorhandler(UnicodeEncodeError)
1✔
111
def unicode(error):
1✔
112
    return problem(400, "Unicode Error", "Connexion was unable to parse some unicode data correctly.")
×
113

114

115
@flask_app.errorhandler(Exception)
1✔
116
def ise(error):
1✔
117
    capture_exception(error)
1✔
118
    log.exception("Caught ISE 500 error: %r", error)
1✔
119
    log.debug("Request path is: %s", request.path)
1✔
120
    log.debug("Request environment is: %s", request.environ)
1✔
121
    log.debug("Request headers are: %s", request.headers)
1✔
122
    return problem(500, "Internal Server Error", "Internal Server Error")
1✔
123

124

125
@flask_app.after_request
1✔
126
def add_security_headers(response):
1✔
127
    response.headers["X-Frame-Options"] = "DENY"
1✔
128
    response.headers["X-Content-Type-Options"] = "nosniff"
1✔
129
    response.headers["Strict-Transport-Security"] = flask_app.config.get("STRICT_TRANSPORT_SECURITY", "max-age=31536000;")
1✔
130
    response.headers["Access-Control-Allow-Headers"] = "Authorization, Content-Type"
1✔
131
    response.headers["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST, PUT, DELETE"
1✔
132
    if "*" in flask_app.config["CORS_ORIGINS"]:
1!
133
        response.headers["Access-Control-Allow-Origin"] = "*"
1✔
NEW
134
    elif "Origin" in request.headers and request.headers["Origin"] in flask_app.config["CORS_ORIGINS"]:
×
135
        response.headers["Access-Control-Allow-Origin"] = request.headers["Origin"]
×
136
    if re.match("^/ui/", request.path):
1!
137
        # This enables swagger-ui to dynamically fetch and
138
        # load the swagger specification JSON file containing API definition and examples.
139
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
×
140
    else:
141
        response.headers["Content-Security-Policy"] = flask_app.config.get("CONTENT_SECURITY_POLICY", "default-src 'none'; frame-ancestors 'none'")
1✔
142
    return response
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc