UN-2776 [FIX] Fixed GDrive connector showing always authenticated in workflow settings#1508
Conversation
Summary by CodeRabbit
WalkthroughAdds per-connector OAuth status and cache scoping on the frontend, passes an Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant ConfigureDs
participant OAuthDs
participant GoogleBtn as GoogleOAuthButton
participant Browser as localStorage/sessionStorage
participant Google as OAuth Provider
participant Backend as Backend API
participant Cache as OAuth Cache
User->>ConfigureDs: Open connector (selectedSourceId)
ConfigureDs->>OAuthDs: Render with isExistingConnector, selectedSourceId
OAuthDs->>Browser: Read oauth-status-${id}
Note over OAuthDs,Browser: Subscribes to storage events for oauth-status-${id}
User->>GoogleBtn: Click OAuth
GoogleBtn->>OAuthDs: handleOAuth()
OAuthDs->>Browser: sessionStorage set oauth-current-connector = ${id}
OAuthDs->>Google: Open OAuth authorize window
Google-->>Backend: OAuth callback with code
Backend->>Cache: Store creds under oauth-cachekey-${id}
Backend-->>Browser: Update localStorage oauth-status-${id} = success
Note over Browser,OAuthDs: storage event updates status in component and parent
User->>ConfigureDs: Submit connector (create/update)
ConfigureDs->>Backend: POST/PUT with oauth_key
Backend->>Cache: Read creds (delete_key=False)
Backend-->>ConfigureDs: 2xx response
Backend->>Cache: Cleanup creds (delete_key=True)
ConfigureDs->>Browser: Remove oauth-cachekey-${id}, oauth-status-${id}
opt Unmount
ConfigureDs->>Browser: Cleanup oauth keys for ${id}
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Cache: Disabled due to Reviews > Disable Cache setting Knowledge Base: Disabled due to Reviews > Disable Knowledge Base setting 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/oauth-ds/oauth-status/OAuthStatus.jsx (1)
14-22: Use localStorage (not sessionStorage) for cross-window context; popup cannot read parent sessionStoragesessionStorage is scoped per-tab/window and isn’t available to the OAuth popup. As a result, OAuthStatus will often find null currentConnector and never persist the status. Switch to localStorage for the "oauth-current-connector" handshake so the popup can read and clear it. Also fix the comment to avoid confusion.
- // Set status for the workflow-connector that initiated OAuth (stored in sessionStorage for callback) - const currentConnector = sessionStorage.getItem("oauth-current-connector"); + // Set status for the workflow-connector that initiated OAuth (stored in localStorage for cross-window callback) + const currentConnector = localStorage.getItem("oauth-current-connector"); if (currentConnector) { - // currentConnector now contains workflowId-connectorType-sourceId format + // currentConnector contains workflowId-connectorType-sourceId format const statusKey = `oauth-status-${currentConnector}`; localStorage.setItem(statusKey, status); - // Clear the session storage after use - sessionStorage.removeItem("oauth-current-connector"); + // Clear the local storage after use + localStorage.removeItem("oauth-current-connector"); }
🧹 Nitpick comments (2)
frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx (2)
31-39: Filter storage events by key to avoid unnecessary readsListen only to oauthStatusKey changes; use the event payload instead of rereading localStorage.
- useEffect(() => { - const handleStorageChange = () => { - // Listen for changes to our specific workflow-connector combination only - const updatedOAuthStatus = localStorage.getItem(oauthStatusKey); + useEffect(() => { + const handleStorageChange = (e) => { + // Listen only for our specific workflow-connector status key + if (e.key !== oauthStatusKey) return; + const updatedOAuthStatus = e.newValue; if (updatedOAuthStatus) { setOAuthStatus(updatedOAuthStatus); setStatus(updatedOAuthStatus); } };
115-122: Constrain connectorType PropType to known valuesThis tightens the contract and prevents silent key drift.
selectedSourceId: PropTypes.string.isRequired, - workflowId: PropTypes.string.isRequired, - connectorType: PropTypes.string.isRequired, + workflowId: PropTypes.string.isRequired, + connectorType: PropTypes.oneOf(["source", "destination"]).isRequired,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge Base: Disabled due to Reviews > Disable Knowledge Base setting
📒 Files selected for processing (3)
frontend/src/components/input-output/configure-ds/ConfigureDs.jsx(4 hunks)frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx(3 hunks)frontend/src/components/oauth-ds/oauth-status/OAuthStatus.jsx(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx (2)
22-25: Good scoping of OAuth keys to workflow + connector type + sourceIdThis should resolve cross-connector contamination in a workflow.
49-54: LGTM on initial status bootstrappingReading the workflow/connector-scoped status on mount matches the new key strategy.
frontend/src/components/input-output/configure-ds/ConfigureDs.jsx (3)
53-57: Per-workflow/per-connector keys: goodKey structure matches OAuthDs and should prevent cross-connector contamination.
361-371: Passing workflowId and connectorType down is correctThis aligns ConfigureDs with the new OAuthDs interface.
41-41: Confirm whether projectId should be aliased to workflowId
The workflow store only exposes projectId; ensure this aligns with your intended workflow scoping or introduce a dedicated workflowId to avoid state collisions.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/input-output/configure-ds/ConfigureDs.jsx (1)
193-199: Bug: oauth-key attached only to connector_metadata.Adapter OAuth tests will miss the key. Use the correct metadata bucket based on isConnector.
- if (oAuthProvider?.length > 0) { - body["connector_metadata"] = { - ...body["connector_metadata"], - ...{ "oauth-key": cacheKey }, - }; - } + if (oAuthProvider?.length > 0) { + const metaKey = isConnector ? "connector_metadata" : "adapter_metadata"; + body[metaKey] = { + ...body[metaKey], + "oauth-key": cacheKey, + }; + }
♻️ Duplicate comments (1)
frontend/src/components/input-output/configure-ds/ConfigureDs.jsx (1)
54-58: Key scope outside workflows: verify selectedSourceId is always present.If selectedSourceId is empty when adding from outside a workflow, localStorage keys become shared (e.g., oauth-cachekey-undefined). Provide a fallback scope.
- const isExistingConnector = Boolean(editItemId || hasOAuthCredentials); + const isExistingConnector = Boolean(editItemId || hasOAuthCredentials); + // Ensure per-connector key scoping even when selectedSourceId is absent + const keyScope = + selectedSourceId || + `${(type || 'unknown').toLowerCase()}-${oAuthProvider || 'oauth'}`; + const oauthCacheKey = `oauth-cachekey-${keyScope}`; + const oauthStatusKey = `oauth-status-${keyScope}`;Run to check call sites pass a non-empty selectedSourceId in non-workflow flows:
#!/bin/bash # Inspect ConfigureDs usages and props passed for selectedSourceId rg -nP -C2 '<ConfigureDs\b' --type=js --type=jsx --type=ts --type=tsx
🧹 Nitpick comments (5)
frontend/src/components/oauth-ds/google/GoogleOAuthButton.jsx (3)
8-8: Fix label typo: "Sign in" (with space) for consistency.Default should read "Sign in" per UX copy elsewhere and PR description.
-const GoogleOAuthButton = ({ handleOAuth, status, buttonText = "Signin" }) => { +const GoogleOAuthButton = ({ handleOAuth, status, buttonText = "Sign in" }) => {
10-16: Remove derived state; compute label inline.Avoiding useState/useEffect here simplifies and prevents stale text.
- const [text, setText] = useState(""); - useEffect(() => { - if (status === "success") { - setText("Authenticated"); - return; - } - setText(buttonText); - }, [status, buttonText]); + const text = status === "success" ? "Authenticated" : buttonText;Additionally, if desired, disable the button when authenticated:
- <GoogleLoginButton onClick={handleOAuth}> + <GoogleLoginButton onClick={handleOAuth} disabled={status === "success"}>
30-30: Optional: tighten PropTypes for status.If statuses are finite, consider oneOf([...]) to catch regressions.
-GoogleOAuthButton.propTypes = { - handleOAuth: PropTypes.func.isRequired, - status: PropTypes.string, - buttonText: PropTypes.string, -}; +GoogleOAuthButton.propTypes = { + handleOAuth: PropTypes.func.isRequired, + status: PropTypes.string, // e.g., one of: '', 'success', 'error', 'pending' + buttonText: PropTypes.string, +};frontend/src/components/input-output/configure-ds/ConfigureDs.jsx (1)
352-358: Avoid mutating session adapters array.Mutating store-derived arrays can cause subtle state bugs; create a new array.
- const adaptersList = sessionDetails?.adapters; - if (adaptersList && !adaptersList.includes(adapterType)) { - adaptersList.push(adapterType); - const adaptersListInSession = { adapters: adaptersList }; - updateSessionDetails(adaptersListInSession); - } + const current = Array.isArray(sessionDetails?.adapters) + ? sessionDetails.adapters + : []; + if (!current.includes(adapterType)) { + updateSessionDetails({ adapters: [...current, adapterType] }); + }backend/connector_v2/views.py (1)
89-89: Reduce log duplication.“Fetching oauth data …” (Line 83) and “Using OAuth cache key …” communicate similar info. Keep one (ideally the latter) or lower the first to debug.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge Base: Disabled due to Reviews > Disable Knowledge Base setting
📒 Files selected for processing (4)
backend/connector_v2/views.py(3 hunks)frontend/src/components/input-output/configure-ds/ConfigureDs.jsx(5 hunks)frontend/src/components/oauth-ds/google/GoogleOAuthButton.jsx(2 hunks)frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (6)
frontend/src/components/input-output/configure-ds/ConfigureDs.jsx (3)
7-9: LGTM: imports added for analytics and URL util.
336-339: LGTM: clearing OAuth keys after successful submit.Matches the PR objective; prevents leakage across connectors.
369-370: Confirm downstream usage of isExistingConnector for button label.Ensure OAuthDs maps isExistingConnector → buttonText ("Reauthenticate" vs "Sign in") and propagates it to GoogleOAuthButton.
backend/connector_v2/views.py (3)
136-138: LGTM: cleanup runs only after a successful update.Assuming
_cleanup_oauth_cacheis hardened as above, this won’t impact the happy path even if the cache is already cleared.
155-157: LGTM: cleanup runs only after a successful create.Matches the front-end flow; ensures single-use cache keys.
85-89: Use provider-agnostic error text in MissingParamExceptionApply this diff in backend/connector_v2/views.py (lines 85–89):
- if not oauth_key: - raise MissingParamException( - "OAuth authentication required. Please sign in with Google first." - ) + if not oauth_key: + raise MissingParamException( + "OAuth authentication required. Please complete OAuth sign-in first." + )Confirm that clients aren’t relying on a separate param field in the error payload; if they do, continue using the
param=kwarg (e.g.MissingParamException(param="oauth_key")) or adjust the exception’s signature accordingly.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx (1)
74-81: Guard missing cache_key before proceedingIf the API doesn’t return a valid
cache_key, the flow will open a broken URL and set bad state.-const cacheKey = response?.data?.cache_key; -const encodedCacheKey = encodeURIComponent(cacheKey); -setCacheKey(cacheKey); +const cacheKey = response?.data?.cache_key; +if (!cacheKey || typeof cacheKey !== "string") { + throw new Error("OAuth cache key missing from response"); +} +const encodedCacheKey = encodeURIComponent(cacheKey); +setCacheKey(cacheKey); // Persist cache key to localStorage localStorage.setItem(oauthCacheKey, cacheKey);
♻️ Duplicate comments (3)
backend/connector_v2/views.py (2)
85-89: Make error message provider-agnostic.Remove "Google" to keep copy generic and future-proof.
- if not oauth_key: - raise MissingParamException( - "OAuth authentication required. Please sign in with Google first." - ) + if not oauth_key: + raise MissingParamException( + "OAuth authentication required. Please sign in first." + )
89-95: Handle cache miss explicitly; avoid relying on None-check only.Wrap the cache fetch to surface a friendly, consistent error and keep a defensive None guard.
- logger.info(f"Using OAuth cache key for {connector_id}") - connector_metadata = ConnectorAuthHelper.get_oauth_creds_from_cache( - cache_key=oauth_key, - delete_key=False, # Don't delete yet - wait for successful operation - ) - if connector_metadata is None: - raise MissingParamException(param=ConnectorAuthKey.OAUTH_KEY) + logger.info(f"Using OAuth cache key for {connector_id}") + try: + connector_metadata = ConnectorAuthHelper.get_oauth_creds_from_cache( + cache_key=oauth_key, + delete_key=False, # Don't delete yet - wait for successful operation + ) + except CacheMissException: + raise CacheMissException("OAuth session expired. Please sign in again.") + if connector_metadata is None: # Defensive in case the helper returns None + raise CacheMissException("OAuth session expired. Please sign in again.")frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx (1)
66-68: Use localStorage for cross-window OAuth context (popup cannot read sessionStorage)The OAuth popup cannot access
sessionStorageof the opener tab; uselocalStorageso the popup can read/clear the context.-// Store connector context in sessionStorage for OAuth callback (survives window.open) -sessionStorage.setItem("oauth-current-connector", selectedSourceId); +// Store connector context in localStorage so the popup can read it +localStorage.setItem("oauth-current-connector", selectedSourceId);Optional: include provider (and workflow/type if available) for stronger disambiguation:
-localStorage.setItem("oauth-current-connector", selectedSourceId); +localStorage.setItem("oauth-current-connector", `${oAuthProvider}-${selectedSourceId}`);
🧹 Nitpick comments (5)
frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx (5)
21-24: Key names: confirm uniqueness; consider scoping by providerRight now keys are scoped by
selectedSourceIdonly. If IDs aren’t globally unique across workflows/types, collisions can still occur. Consider includingoAuthProvider(and, if available, workflow/connector type) in the key.Example change:
-const oauthCacheKey = `oauth-cachekey-${selectedSourceId}`; -const oauthStatusKey = `oauth-status-${selectedSourceId}`; +const oauthCacheKey = `oauth-cachekey-${oAuthProvider}-${selectedSourceId}`; +const oauthStatusKey = `oauth-status-${oAuthProvider}-${selectedSourceId}`;
25-26: Copy: align with PR wordingPR says: new connector should show “Sign in”. Suggest:
-const buttonText = isExistingConnector ? "Reauthenticate" : "Authenticate with Google"; +const buttonText = isExistingConnector ? "Reauthenticate" : "Sign in with Google";
51-56: Initialize status unconditionally to reflect cleared stateEnsure cleared/absent status resets the UI.
-const connectorStatus = localStorage.getItem(oauthStatusKey); -if (connectorStatus) { - setStatus(connectorStatus); - setOAuthStatus(connectorStatus); -} +const connectorStatus = localStorage.getItem(oauthStatusKey); +setStatus(connectorStatus); +setOAuthStatus(connectorStatus);
86-91: Optional: add noopener for the popupMitigates reverse tabnabbing. Use if the popup doesn’t rely on
window.opener.-window.open( +window.open( url, "_blank", - "toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=500,height=600" + "toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=500,height=600,noopener" );
112-118: Tighten PropTypes (required where used)
oAuthProvider,setCacheKey, andsetStatusappear mandatory for correct behavior.-OAuthDs.propTypes = { - oAuthProvider: PropTypes.string, - setCacheKey: PropTypes.func, - setStatus: PropTypes.func, - selectedSourceId: PropTypes.string.isRequired, - isExistingConnector: PropTypes.bool, -}; +OAuthDs.propTypes = { + oAuthProvider: PropTypes.string.isRequired, + setCacheKey: PropTypes.func.isRequired, + setStatus: PropTypes.func.isRequired, + selectedSourceId: PropTypes.string.isRequired, + isExistingConnector: PropTypes.bool, +};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to Reviews > Disable Cache setting
Knowledge Base: Disabled due to Reviews > Disable Knowledge Base setting
📒 Files selected for processing (3)
backend/connector_v2/views.py(3 hunks)frontend/src/components/oauth-ds/google/GoogleOAuthButton.jsx(2 hunks)frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/oauth-ds/google/GoogleOAuthButton.jsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (5)
backend/connector_v2/views.py (3)
100-120: Good: resilient, idempotent OAuth cache cleanup.Catches cache-miss and swallows failures after logging; aligns with post-success cleanup semantics.
142-144: Good: cleanup after update.Ensures the cache key is cleared only after successful update.
161-163: Good: cleanup after create.Clears the cache post-success to prevent phantom “authenticated” states.
frontend/src/components/oauth-ds/oauth-ds/OAuthDs.jsx (2)
7-7: Good: centralized exception handlingImporting and using
useExceptionHandlerfor OAuth errors is a solid improvement.
100-104: GoogleOAuthButton wiring looks correctPassing
statusandbuttonTextis consistent and clear.
|
|
The AgenticProject descriptor in SHAREABLE_RESOURCES hardcoded name_field="project_name"/id_field="project_id", copied from the CustomTool tool_id/tool_name pattern. The real cloud model (agentic_studio_v1.AgenticProject, cloud PR #1508) uses ORM fields "id" (UUID PK) and "name"; project_id/ project_name exist only as dict keys in get_pipeline_status(). On cloud this made the group blast-radius view run values_list("project_id","project_name") -> FieldError/500. Pure OSS was unaffected (app absent, skipped via LookupError). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* init changes. User's Group's
* fix migration M2M. Agenctic Prompt Studio align with other resources
* Resource Share Modal UI fix
* users can't access a resource, if admin has removed that user from a group, and the resource has been shared to that group
* Removing idp group import, UI Fix
* Removing idp group import
* Sonar fix
* UN-2977 [FIX] address Greptile/CodeRabbit review on group sharing
- group list: validate ?member= as int (400 not 500)
- group views: reference IsOrgAdminForWrite.message, not list index
- sharing_helpers: cast UUID ids in list_resources_shared_with_group
(fixes GET /groups/{pk}/resources/ crash); treat org-less user as
non-admin without error-logging noise
- share serializer mixin: make model save + group-share write atomic
- adapter: move default-adapter cleanup to the share action where
unsharing actually happens (shared_users is read-only on PATCH)
- frontend: guard group id on edit, orgId before replaceAll, TopBar
search key for non-user rows, /groups nav active state, keep share
modal open on failure
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] validate remove_member user_id + configurable TopBar placeholder
- group_views.remove_member: guard non-numeric user_id (400 not 500), mirroring the list action
- TopBar: searchPlaceholder prop (default "Search Users"); Groups passes "Search Groups"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* UN-2977 [FIX] Restrict group resources endpoint to org admins
The /groups/{pk}/resources/ action is the delete blast-radius view used
only by the admin delete-confirm flow, but IsOrgAdminForWrite permits GET
for any authenticated org member. That let non-members enumerate the
names/UUIDs of resources shared with groups they are not in, contradicting
the model where org admin has no implicit resource access. Gate the action
to org admins, mirroring the existing members POST / remove_member guards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Drop stale IdP group-sync leftovers from sample.env
Group sharing is manual-only after the 2026-05-25 reversal; there is no
IdP group sync. Remove the dead IDP_GROUP_SYNC_INTERVAL_MIN var and the
"LOCAL + IDP combined" wording so sample.env stops documenting a feature
that no longer exists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Harden group-share signals + correct member_count and guards
Addresses self-review findings on PR #1986:
- member_count: count via a decoupled Subquery so the optional ?member
filter on the same relation no longer collapses it to 1.
- Add a post_delete receiver purging ResourceGroupShare rows when a
shareable resource is deleted (object_id is varchar, no FK/cascade).
- Wrap cleanup_user_org_access in transaction.atomic() with per-model
error handling that logs and re-raises, so a partial purge can't
re-open the rejoin backdoor.
- Skip-and-log malformed object_id UUIDs instead of 500ing the list.
- Error-handle the post-commit adapter default-cleanup save.
- Comment accuracy: shared_groups is polymorphic (not M2M); drop the
duplicate service-account docstring bullet; clarify the mixin's
writable vs read-only usage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Restore close-on-success in share modals
This PR had moved the share-modal close into .finally() (close on
success or failure), discarding the user's selection on a 400/403.
Revert useShareModal, ListOfTools and Workflows to close only on
success — restoring pre-PR behavior and matching ToolSettings — so a
rejected share keeps the modal open for the user to review and retry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Remove dead SharedGroupsSerializerMixin
All 7 resource serializers (6 OSS + cloud AgenticProject) declare
shared_groups as read_only, so DRF strips it from validated_data and the
mixin's create/update never wrote group shares — every group write flows
through ShareAuthorizationService._commit via the POST /share/ action.
The mixin was therefore dead code with a misleading docstring and a
latent org-scope footgun if a field were ever made writable without
re-adding validation. Delete it and unwire it from the 6 serializers;
the read_only shared_groups declarations stay.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Return 403 not 404 on shared-user mutation of tool config and LLM profile
DRF runs get_queryset() before has_object_permission(); when a sub-resource's
queryset filtered out rows reachable via parent-resource sharing, shared users
got HTTP 404 on PATCH/DELETE instead of the expected 403.
Strictly additive fix on read visibility (post-fix row set is a superset of
pre-fix for every caller class):
- permissions/permission.py: add IsParentWorkflowOwner — mirrors IsOwner but
reads obj.workflow.created_by; admits org admin and service account.
- tool_instance_v2/views.py: broaden get_queryset to union prior per-creator
rows with rows under workflows the user can access; gate update /
partial_update / destroy on IsParentWorkflowOwner.
- prompt_studio/prompt_profile_manager_v2/models.py: extend
ProfileManagerModelManager.for_user with a parent-CustomTool branch
alongside the existing created_by | shared_users | shared_to_org branches.
Viewset's IsOwner gate is unchanged.
The only mutate-side narrowing: a shared user can no longer mutate a
ToolInstance they themselves created under a non-owned workflow (was an
unintended path that let shared users inject + mutate tool config; matches
the user-stated requirement that shared users shouldn't change tool config).
Verified: pre-commit clean, identical mypy error counts vs HEAD on all three
files (no new errors introduced).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Consolidate shareable-resource registry and harden membership cleanup
Address review feedback on PR #1986:
- Add a single SHAREABLE_RESOURCES registry (tenant_account_v2/
shareable_resources.py) and drive both the org-membership cleanup signals
and the group blast-radius view from it, so the two lists can no longer
drift. AgenticProject is now included, fixing the under-reported group
delete count on cloud.
- Purge shared_users via the unscoped M2M through table scoped by the
resource's organization, instead of the UserContext-org-scoped default
manager which matched zero rows outside an HTTP request (e.g. tests /
management commands), silently skipping the rejoin-backdoor cleanup.
- Tighten the adapter get_permissions comment and correct the pipeline
shared_groups comment to match the other resource models.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* UN-2977 [FIX] Correct AgenticProject field names in shareable registry
The AgenticProject descriptor in SHAREABLE_RESOURCES hardcoded
name_field="project_name"/id_field="project_id", copied from the CustomTool
tool_id/tool_name pattern. The real cloud model (agentic_studio_v1.AgenticProject,
cloud PR #1508) uses ORM fields "id" (UUID PK) and "name"; project_id/
project_name exist only as dict keys in get_pipeline_status(). On cloud this made
the group blast-radius view run values_list("project_id","project_name") ->
FieldError/500. Pure OSS was unaffected (app absent, skipped via LookupError).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* UN-2977 [FIX] Make share-modal user revoke type-agnostic
handleDeleteUser compared a numeric item.id against selectedUsers, which
holds a mix of stringified (pre-loaded) and numeric (in-session) IDs, so
"Revoke Access" was a silent no-op for already-shared users. Normalize both
sides with String() so removal works regardless of ID type.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* UN-2977 [FIX] Address review: reorder authz, effective-member adapter cleanup, tenant hardening + tests
- Gate tool-instance reorder on workflow ownership; scope helper lookup
- Clear default adapter on effective-access loss (group/org), not just direct
- Harden group-share cleanup signal + skip models lacking shared_users
- Org-scope sharing read helpers + reject cross-org group writes
- Add registry drift system check; fix groups-service route; restore StrictMode
- useShareModal fallback + GroupMemberManager shape normalization
- Add group-sharing test suite (15 tests: matrix, signals, visibility, registry)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* UN-2977 [FIX] Replace hard-coded test password with generated secret (SonarCloud)
Resolves SonarCloud 'Credentials should not be hard-coded' on tests.py;
the literal is never used to authenticate. Flips New-Code Security Rating
to A and passes the Quality Gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* UN-2977 [FIX] Preserve adapter owner's default on shared_to_org toggle-off
Effective-member diff in the adapter share action counted the owner as an
org member while shared_to_org=True; toggling it off dropped the owner into
the removed set and wiped their UserDefaultAdapter. The owner always retains
access via created_by, so exclude them from cleanup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Rahul Johny <116638720+johnyrahul@users.noreply.github.com>



What
Why
oauth-cachekeycookie to local storageoauth-cachekeybased on workflow-id and connector typeHow
Frontend change:
For new connector:
For existing connector:
For both connectors:
Backend change:
Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
Database Migrations
Env Config
Relevant Docs
Related Issues or PRs
Dependencies Versions
Notes on Testing
Screenshots
Checklist
I have read and understood the Contribution Guidelines.