Skip to content

Require authorization on PreferencesHttpHandlerInternal endpoints - #16192

Open
herdiyana256 wants to merge 2 commits into
cdapio:developfrom
herdiyana256:fix-preferences-internal-missing-authz
Open

Require authorization on PreferencesHttpHandlerInternal endpoints#16192
herdiyana256 wants to merge 2 commits into
cdapio:developfrom
herdiyana256:fix-preferences-internal-missing-authz

Conversation

@herdiyana256

Copy link
Copy Markdown

PreferencesHttpHandlerInternal exposes instance, namespace, application, and program preferences with no authorization check anywhere in its call chain: PreferencesService (its only dependency for reads) has no ContextAccessEnforcer/AccessEnforcer of its own. Its public sibling, PreferencesHttpHandler, calls accessEnforcer.enforce(entity, principal, StandardPermission.GET) before every equivalent read at every one of those same scopes, so this is a direct authorization gap between the two, not an intentional design difference.

Since this handler is reachable the same way as the rest of the appfabric.http.handler set (see #16191, the FileFetcherHttpHandlerInternal fix, for the full reachability argument: same HTTP service, same discoverable service, same Router path-matching as public v3 handlers, no v3Internal-specific authorization anywhere in RouterPathLookup or AuthenticationHandler), any principal with a valid CDAP access token can read another namespace's, application's, or program's preferences, which frequently carry pipeline runtime arguments and connection configuration.

This adds the same enforce() calls PreferencesHttpHandler already makes at each scope, plus a dedicated authorization test mirroring ConfigHandlerAuthorizationTest.

PreferencesHttpHandlerInternal exposes instance, namespace, application,
and program preferences with no authorization check anywhere in its call
chain: PreferencesService (its only dependency for reads) has no
ContextAccessEnforcer/AccessEnforcer of its own. Its public sibling,
PreferencesHttpHandler, calls accessEnforcer.enforce(entity,
principal, StandardPermission.GET) before every equivalent read at every
one of those same scopes, so this is a direct authorization gap between
the two, not an intentional design difference.

Since this handler is reachable the same way as the rest of the
appfabric.http.handler set (see the FileFetcherHttpHandlerInternal fix
for the full reachability argument: same HTTP service, same discoverable
service, same Router path-matching as public v3 handlers, no
v3Internal-specific authorization anywhere in RouterPathLookup or
AuthenticationHandler), any principal with a valid CDAP access token can
read another namespace's, application's, or program's preferences,
which frequently carry pipeline runtime arguments and connection
configuration.

This adds the same enforce() calls PreferencesHttpHandler already makes
at each scope, plus a dedicated authorization test mirroring
ConfigHandlerAuthorizationTest.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request secures the internal preferences HTTP handler by introducing authorization checks via AccessEnforcer across instance, namespace, application, and program preference endpoints, and adds a corresponding authorization test suite. The review feedback correctly identifies compilation errors due to unhandled checked exceptions (AccessException) thrown by the new enforcement calls, requiring throws declarations in both the handler and test methods. Additionally, the feedback suggests expanding test coverage to include authorized application preferences and program-level preference checks.

Comment on lines +80 to +81
accessEnforcer.enforce(new InstanceId(""), authenticationContext.getPrincipal(),
StandardPermission.GET);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The accessEnforcer.enforce(...) call throws AccessException, which is a checked exception. Since this exception is not caught, the enclosing method getInstancePreferences (and similarly getNamespacePreferences and getApplicationPreferences) must declare throws Exception (or throws AccessException) to avoid compilation errors. This matches the pattern used in PreferencesHttpHandler.java.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b5f64c9, added throws Exception to the method signature.

Comment on lines +98 to +99
@Test
public void testGetInstancePreferencesUnauthorized() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since getInstancePreferences throws a checked exception, this test method must declare throws Exception to compile successfully.

Suggested change
@Test
public void testGetInstancePreferencesUnauthorized() {
@Test
public void testGetInstancePreferencesUnauthorized() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b5f64c9, added throws Exception to the method signature.

Comment on lines +110 to +111
@Test
public void testGetInstancePreferencesAuthorized() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since getInstancePreferences throws a checked exception, this test method must declare throws Exception to compile successfully.

Suggested change
@Test
public void testGetInstancePreferencesAuthorized() {
@Test
public void testGetInstancePreferencesAuthorized() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b5f64c9, added throws Exception to the method signature.

Comment on lines +121 to +122
@Test
public void testGetNamespacePreferencesUnauthorized() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since getNamespacePreferences throws a checked exception, this test method must declare throws Exception to compile successfully.

Suggested change
@Test
public void testGetNamespacePreferencesUnauthorized() {
@Test
public void testGetNamespacePreferencesUnauthorized() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b5f64c9, added throws Exception to the method signature.

Comment on lines +133 to +134
@Test
public void testGetNamespacePreferencesAuthorized() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since getNamespacePreferences throws a checked exception, this test method must declare throws Exception to compile successfully.

Suggested change
@Test
public void testGetNamespacePreferencesAuthorized() {
@Test
public void testGetNamespacePreferencesAuthorized() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b5f64c9, added throws Exception to the method signature.

Comment on lines +144 to +145
@Test
public void testGetApplicationPreferencesUnauthorized() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since getApplicationPreferences throws a checked exception, this test method must declare throws Exception to compile successfully.

Suggested change
@Test
public void testGetApplicationPreferencesUnauthorized() {
@Test
public void testGetApplicationPreferencesUnauthorized() throws Exception {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b5f64c9, added throws Exception to the method signature.

Comment on lines +155 to +156
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure comprehensive test coverage of the new authorization checks, consider adding tests for authorized application preferences and both authorized/unauthorized program preferences.

  }

  @Test
  public void testGetApplicationPreferencesAuthorized() throws Exception {
    AuthenticationTestContext.actAsPrincipal(MASTER_PRINCIPAL);
    try {
      preferencesHandler.getApplicationPreferences(request, responder, OTHER_NAMESPACE.getNamespace(),
          "some-app", false);
    } catch (UnauthorizedException e) {
      exceptionThrown = e;
    }
    Assert.assertNull("a caller with access to the application's namespace must not be rejected", exceptionThrown);
  }

  @Test
  public void testGetProgramPreferencesUnauthorized() throws Exception {
    AuthenticationTestContext.actAsPrincipal(UNPRIVILEGED_PRINCIPAL);
    try {
      preferencesHandler.getProgramPreferences(request, responder, OTHER_NAMESPACE.getNamespace(),
          "some-app", "workflows", "some-program", false);
    } catch (UnauthorizedException e) {
      exceptionThrown = e;
    }
    Assert.assertNotNull("an unprivileged caller must not be able to read another "
        + "namespace's program preferences via the internal endpoint", exceptionThrown);
  }

  @Test
  public void testGetProgramPreferencesAuthorized() throws Exception {
    AuthenticationTestContext.actAsPrincipal(MASTER_PRINCIPAL);
    try {
      preferencesHandler.getProgramPreferences(request, responder, OTHER_NAMESPACE.getNamespace(),
          "some-app", "workflows", "some-program", false);
    } catch (UnauthorizedException e) {
      exceptionThrown = e;
    }
    Assert.assertNull("a caller with access to the program's namespace must not be rejected", exceptionThrown);
  }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in b5f64c9: testGetApplicationPreferencesAuthorized, testGetProgramPreferencesUnauthorized, testGetProgramPreferencesAuthorized. The authorized cases needed grants scoped to the specific ApplicationId/ProgramId (not just the namespace), since InMemoryAccessController does exact-entity matching rather than hierarchical, so I added those grants and the corresponding PreferencesService mock stubs too.

…est coverage

Per gemini-code-assist review on cdapio#16192: accessEnforcer.enforce() throws a
checked AccessException, so getInstancePreferences/getNamespacePreferences/
getApplicationPreferences needed throws Exception on the method signature
(matching the existing pattern in PreferencesHttpHandler and the untouched
getProgramPreferences), and the corresponding test methods needed it too.
Also added the application- and program-level authorized-access test cases
Gemini suggested, with grants scoped to the specific ApplicationId/ProgramId
since InMemoryAccessController does exact-entity matching, not hierarchical.
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