Skip to content

Add @requires_view decorator and enforce permission checks for Thrift methods#4854

Open
bishara74 wants to merge 3 commits into
Ericsson:masterfrom
bishara74:refactor/thrift-permission-decorator
Open

Add @requires_view decorator and enforce permission checks for Thrift methods#4854
bishara74 wants to merge 3 commits into
Ericsson:masterfrom
bishara74:refactor/thrift-permission-decorator

Conversation

@bishara74
Copy link
Copy Markdown
Contributor

Summary

Each Thrift API method in ThriftRequestHandler previously started with a literal self.__require_view() call to enforce its permission requirement. Nothing prevented a new method from being added without that call, which would expose it to unprivileged users.

This PR introduces a @requires_view decorator that hoists the permission check into the method's signature, migrates the existing call sites, and adds a static enforcement mechanism that makes it impossible for an unprotected Thrift method to slip past review or boot the server.

Changes

api/common: add requires_view decorator for Thrift methods

Introduces @requires_view in api/common.py, alongside the existing @exc_to_thrift_reqfail. The decorator looks up the handler's __require_view() helper via getattr on the name-mangled attribute, so the underlying helper does not need to be renamed and the change to report_server.py is purely additive. Applied to getRunData in this commit as a design proof.

report_server: apply @requires_view to remaining Thrift methods

Migrates the remaining 42 Thrift methods in ThriftRequestHandler from starting with self.__require_view() to using the decorator. Done mechanically; net diff is symmetric (42 insertions, 42 deletions).

api: enforce permission check on every Thrift API method

Adds permission_coverage.py, a static check that every method declared in the Thrift service codeCheckerDBAccess_v6 has a detectable permission check on the implementing handler class. A method is considered protected if any of:

  • It has the @requires_view decorator.
  • One of its first few statements calls a self.__require_*() permission helper (__require_admin, __require_access, __require_store, __require_permission, etc.).
  • It is listed in _DELEGATED_PROTECTION, in which case the check verifies that the named delegate helper itself performs a permission check. This handles massStoreRun and massStoreRunAsynchronous, both of which pass the request through __massStoreRun_common — which calls self.__require_store().

The check runs in two places using the same code path, so they cannot disagree:

  • As a unit test (test_thrift_permission_coverage.py), so a PR that introduces an unprotected Thrift method fails CI before being merged.
  • As a startup assertion in start_server(), so even if such a PR landed in master, the server refuses to start rather than serve an exploitable endpoint.

Thrift method names are discovered by introspecting the generated Iface class in codechecker_api.codeCheckerDBAccess_v6, which ships with both development and deployed installations — no dependency on the .thrift source files at runtime.

Verification

Beyond the unit test (32 passing, was 31), the following was verified manually:

  • The decorator calls __require_view() before the wrapped method and propagates its return value (smoke-tested against a stand-in handler class).
  • assert_all_thrift_methods_protected() returns silently against the real report_server.py.
  • The same check correctly raises a readable RuntimeError when @requires_view is removed from a Thrift method (simulated regression).
  • Starting the server with a deliberately broken permission_coverage.py causes startup to fail with a traceback ending in the RuntimeError, before the listener is bound. Confirms the startup hook is effective and prevents an exploitable instance from running.
  • After restoring permission_coverage.py, the server boots normally and serves Thrift requests for decorated methods such as getRunData.

Scope notes

  • The decorator covers __require_view only. The other permission helpers (__require_admin, __require_access, __require_store, __require_permission, plus the variants in product_server.py / authentication.py) remain as plain method calls. The enforcement check accepts either style, so security coverage is complete today; extending the decorator pattern to the other permissions can be done in a follow-up if preferred.
  • Only codeCheckerDBAccess_v6 is covered by the enforcement check. Extending to the other Thrift services (products, authentication, configuration, tasks, server_info) is a mechanical addition once the design here is approved.

bishara74 added 2 commits May 18, 2026 21:16
Introduces a @requires_view decorator in api/common.py that wraps a
Thrift API method's view-permission check into the method's decorator
chain, so the permission requirement is visible in the method's
signature rather than buried in the first line of the body.

The decorator looks up the handler's existing __require_view() helper
via getattr on the name-mangled attribute, so the helper does not need
to be renamed and the change is purely additive in report_server.py.

This commit applies the decorator to ThriftRequestHandler.getRunData
as a design proof. A follow-up commit will migrate the remaining
self.__require_view() call sites in report_server.py to use the
decorator.
Migrates the remaining 42 Thrift API method bodies in
ThriftRequestHandler from starting with a literal self.__require_view()
call to using the @requires_view decorator introduced in the prior
commit. The decorator inserts the permission check before the method
body executes, so behavior is unchanged but the permission requirement
is now visible at the method's signature.

Performed mechanically: each call site was located, the enclosing def
was identified, @requires_view was inserted above the def at matching
indentation, and the call line was removed. Net diff is symmetric
(42 insertions, 42 deletions). All 31 server unit tests still pass.
@bishara74 bishara74 requested review from bruntib and vodorok as code owners May 19, 2026 01:32
Adds a static check that every method declared in the Thrift service
codeCheckerDBAccess_v6 has a detectable permission check on the
implementing ThriftRequestHandler. A method is considered protected if
any of the following holds:

  * It has the @requires_view decorator (introduced earlier in this
    branch).
  * One of its first few statements calls a self.__require_*() or
    self._require_*() permission helper (admin, access, store,
    permission, etc.).
  * It is listed in _DELEGATED_PROTECTION, in which case the check
    verifies that the named delegate helper itself satisfies one of
    the above conditions. This handles methods such as massStoreRun
    that pass the request through __massStoreRun_common, which in
    turn calls self.__require_store().

The check is wired into two places, both using the same logic so they
cannot disagree:

  * As a unit test (test_thrift_permission_coverage.py), so a PR that
    introduces an unprotected method fails CI before being merged.

  * As a startup assertion in start_server(), so even if such a PR
    landed in master, the server would refuse to start instead of
    serving an exploitable endpoint.

Thrift method names are discovered by introspecting the generated
Iface class in codechecker_api.codeCheckerDBAccess_v6, which is
present in both development and deployed installations -- no
dependency on the .thrift source files.

The __require_view helper on ThriftRequestHandler is renamed to
_require_view (single underscore). The @requires_view decorator now
calls self._require_view() directly. Pylint's unused-private-member
checker cannot see through the previous getattr indirection that
worked around Python's name mangling, so the original double-
underscore form was flagged as dead code despite being live.
@bishara74 bishara74 force-pushed the refactor/thrift-permission-decorator branch from 247a23c to d824b7b Compare May 19, 2026 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant