Uploaded image for project: 'Project Quay'
  1. Project Quay
  2. PROJQUAY-10557

New UI shows success notification when LDAP restricted user fails to create organization

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • None
    • quay-v3.16.2
    • quay-ui

      Summary:

      When an LDAP user with LDAP_RESTRICTED_USER_FILTER attempts to create a new organization, the backend correctly returns HTTP 403 Forbidden, but the new UI incorrectly displays a success notification saying "Successfully created organization <name>" even though the organization was not created.

      This causes confusion for users who believe they successfully created an organization when the operation actually failed due to access restrictions.

      Environment:

      • FEATURE_RESTRICTED_USERS: true
      • LDAP_RESTRICTED_USER_FILTER: (postalCode=3000)
      • Authentication: LDAP

      Steps to Reproduce:

      1. Configure Quay with LDAP authentication and LDAP_RESTRICTED_USER_FILTER
      2. Login as an LDAP user matching the restricted user filter (e.g., postalCode=3000)
      3. Navigate to Organizations page in new UI
      4. Click "Create New Organization"
      5. Enter organization name and email
      6. Click "Create" button

      Expected Behavior:

      • Error notification: "Unable to create organization" with appropriate error message
      • Modal remains open to allow user to correct issue
      • Organization is NOT created

      Actual Behavior:

      • Success notification: "Successfully created organization <name>"
      • Modal closes
      • Organization is NOT created (backend correctly blocks it)
      • User believes organization was created successfully

      Root Cause Analysis:

      Backend (Working Correctly)

      File: endpoints/api/organization.py:155-174

      The backend correctly validates restricted users and returns HTTP 403:

      @require_user_admin(disallow_for_restricted_users=features.RESTRICTED_USERS)
      @nickname("createOrganization")
      @validate_json_request("NewOrg")
      def post(self):
          # Lines 172-174: Restricted user check
          if not usermanager.is_superuser(user.username):
              if features.RESTRICTED_USERS and usermanager.is_restricted_user(user.username):
                  raise Unauthorized()  # Returns HTTP 403
      

      Frontend Bug

      File: web/src/hooks/UseOrganizations.ts:256-257

      The bug is in the createOrganization function which uses .mutate() instead of .mutateAsync():

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml// BUG: Uses .mutate() which returns void immediately
      createOrganization: async (name: string, email: string) =>
        createOrganizationMutator.mutate({name, email}),
      

      File: web/src/routes/OrganizationsList/CreateOrganizationModal.tsx:80-99

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlconst createOrganizationHandler = async () => {
        try {
          await createOrganization(organizationName, organizationEmail);  // Line 82
          
          // BUG: This ALWAYS executes because mutate() returns void
          addAlert({
            variant: AlertVariant.Success,
            title: `Successfully created organization ${organizationName}`,
          });
          props.handleModalToggle();
        } catch (err) {
          // This catch block NEVER executes
          const errorMessage = addDisplayError("Unable to create organization", err);
          setErr(errorMessage);
          addAlert({
            variant: AlertVariant.Failure,
            title: "Unable to create organization",
            message: errorMessage,
          });
        }
      };
      

      Why the Bug Occurs

      React Query .mutate() vs .mutateAsync():

      • .mutate() - Returns void immediately, does NOT throw errors (requires onError callback)
      • .mutateAsync() - Returns Promise, throws errors on failure (works with try-catch)

      Timeline of events:

      1. User clicks "Create" button
      2. createOrganization() calls .mutate() which returns void immediately
      3. await completes instantly (nothing to await)
      4. Success alert shown BEFORE API call completes
      5. Modal closes
      6. (Background) API request sent to /api/v1/organization/
      7. (Background) Backend returns HTTP 403 Forbidden
      8. (Background) Error silently ignored (no onError callback defined)

      Recommended Fix:

      File: web/src/hooks/UseOrganizations.ts:257

      Change one line to use .mutateAsync() instead of .mutate():

      Unable to find source-code formatter for language: diff. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlcreateOrganization: async (name: string, email: string) =>
      -  createOrganizationMutator.mutate({name, email}),
      +  createOrganizationMutator.mutateAsync({name, email}),
      

      This makes it consistent with the delete operations (line 259) which already use .mutateAsync():

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamldeleteOrganizations: async (names: string[]) =>
        deleteOrganizationMutator.mutateAsync(names),
      

      Impact:

      • Severity: Medium
      • User confusion - users believe organization was created when it was not
      • Affects all LDAP users with LDAP_RESTRICTED_USER_FILTER configured
      • Regression in new UI compared to old UI (old UI showed correct error)
      • Potential support tickets from confused users

      Affected Files:

      • web/src/hooks/UseOrganizations.ts:257 - Bug location (one line fix)
      • web/src/routes/OrganizationsList/CreateOrganizationModal.tsx:80-99 - Error handling that cannot work due to bug

              lzha1981 luffy zhang
              lzha1981 luffy zhang
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: