-
Notifications
You must be signed in to change notification settings - Fork 1
fix: Check org membership to determine external PR #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds an abstract organization-membership method to the VCS Adapter, implements it in the GitHub adapter, updates pull_request handling to compute the "external" flag differently for organization-owned repositories, and adds a unit test for membership checks. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Webhook as GitHub Webhook
participant Adapter as GitHub Adapter
participant API as GitHub API
rect rgba(235,245,255,0.6)
Webhook->>Adapter: pull_request payload
Adapter->>Adapter: isOrgRepository(repo.owner)
alt owner is Organization
Adapter->>API: isUserMemberOfOrganization(username, org)
API-->>Adapter: isMember (bool)
Adapter->>Adapter: external = !isMember
else owner is User
Adapter->>Adapter: external = pr.head.user.login != repo.owner.login
end
Adapter-->>Webhook: proceed handling PR with computed external
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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. Comment |
|
||
public function isUserMemberOfOrganization(string $username, string $organization): bool | ||
{ | ||
$url = "/orgs/{$organization}/memberships/{$username}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API needs "Org: read member" permission for the GitHub app, so that's something we need to change for Appwrite's GH app before deploying this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/VCS/Adapter/Git/GitHub.php (1)
696-701
: Tighten membership evaluation to “active” state and handle non-200 explicitly.Relying on any 2xx can misclassify pending/invited states. Use 200 + state=active.
- public function isUserMemberOfOrganization(string $username, string $organization): bool - { - $url = "/orgs/{$organization}/memberships/{$username}"; - $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]); - return $response['headers']['status-code'] >= 200 && $response['headers']['status-code'] < 300; - } + public function isUserMemberOfOrganization(string $username, string $organization): bool + { + $url = "/orgs/{$organization}/memberships/{$username}"; + $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]); + $status = (int)($response['headers']['status-code'] ?? 0); + if ($status !== 200) { + return false; + } + return strtolower($response['body']['state'] ?? '') === 'active'; + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/VCS/Adapter.php
(1 hunks)src/VCS/Adapter/Git/GitHub.php
(2 hunks)tests/VCS/Adapter/GitHubTest.php
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/VCS/Adapter.php (1)
src/VCS/Adapter/Git/GitHub.php (1)
isUserMemberOfOrganization
(696-701)
tests/VCS/Adapter/GitHubTest.php (2)
src/VCS/Adapter.php (1)
isUserMemberOfOrganization
(262-262)src/VCS/Adapter/Git/GitHub.php (1)
isUserMemberOfOrganization
(696-701)
src/VCS/Adapter/Git/GitHub.php (1)
src/VCS/Adapter.php (2)
isUserMemberOfOrganization
(262-262)call
(278-387)
🪛 GitHub Actions: Tests
tests/VCS/Adapter/GitHubTest.php
[error] 347-347: Utopia\Tests\VCS\Adapter\GitHubTest::testIsUserMemberOfOrganization: Failed asserting that false is true.
🔇 Additional comments (1)
src/VCS/Adapter/Git/GitHub.php (1)
696-701
: Ensure GitHub App has “Organization members: read” permission.This endpoint requires org membership read for the app installation token; otherwise, it returns 403/404 and you’ll treat all as external.
/** | ||
* Check if user is a member of an organization | ||
* | ||
* @param string $username Username of the user | ||
* @param string $organization Name of the organization | ||
* @return bool True if user is a member of the organization, false otherwise | ||
*/ | ||
abstract public function isUserMemberOfOrganization(string $username, string $organization): bool; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
New abstract contract looks good; verify all concrete adapters implement it.
Ensure no other non-abstract adapters are missing this method.
Run to confirm coverage:
🏁 Script executed:
#!/bin/bash
# Find concrete adapters that might need to implement the new abstract method
rg -nP --type=php -C2 'class\s+\w+\s+extends\s+Utopia\\VCS\\Adapter\\\w+' || true
rg -nP --type=php -C2 '\bfunction\s+isUserMemberOfOrganization\s*\('
Length of output: 669
🏁 Script executed:
#!/bin/bash
# Check each concrete adapter for implementation of isUserMemberOfOrganization
find src/VCS/Adapter -type f -name '*.php' ! -name 'Adapter.php' | while read -r file; do
if grep -qP 'function\s+isUserMemberOfOrganization\s*\(' "$file"; then
echo "$file: implements"
else
echo "$file: MISSING isUserMemberOfOrganization"
fi
done
Length of output: 102
Implement isUserMemberOfOrganization in Git adapter
src/VCS/Adapter/Git.php is missing the new abstract method isUserMemberOfOrganization(…)
; add it to satisfy the Adapter contract.
🤖 Prompt for AI Agents
In src/VCS/Adapter/Git.php around where other Adapter methods are implemented
(add near existing user/org checks), implement the new abstract method
isUserMemberOfOrganization(string $username, string $organization): bool to
satisfy the Adapter contract; the method should call the underlying Git provider
API/client to check whether the given username is a member of the specified
organization and return true or false, gracefully handle API errors (catch
exceptions and return false or propagate according to existing error-handling
patterns used in this adapter), and include appropriate logging consistent with
other methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/VCS/Adapter/Git/GitHub.php (1)
644-650
: Guard against missing usernames/org to avoid bad API calls.If either
authorUsername
orowner
is empty, default to external and skip the API call.Apply this diff:
- if ($isOrgRepository) { - $external = !$this->isUserMemberOfOrganization($authorUsername, $owner); - } else { - $external = $authorUsername !== $owner; - } + if ($isOrgRepository) { + if (empty($authorUsername) || empty($owner)) { + $external = true; + } else { + $external = !$this->isUserMemberOfOrganization($authorUsername, $owner); + } + } else { + $external = ($authorUsername === '') ? true : ($authorUsername !== $owner); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/VCS/Adapter/Git/GitHub.php
(2 hunks)tests/VCS/Adapter/GitHubTest.php
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/VCS/Adapter/GitHubTest.php
🧰 Additional context used
🧬 Code graph analysis (1)
src/VCS/Adapter/Git/GitHub.php (1)
src/VCS/Adapter.php (2)
isUserMemberOfOrganization
(262-262)call
(278-387)
🔇 Additional comments (2)
src/VCS/Adapter/Git/GitHub.php (2)
644-650
: Good fix: membership-based external check for org repos.You now correctly derive
authorUsername
and decideexternal
by checking org membership. This addresses the prior logic bug.
697-702
: GitHub App permission requirement (read org members).This endpoint needs the app permission “Organization members: Read”. Ensure it’s enabled before rollout; otherwise membership checks will 404/403 and everything becomes “external” (safe default).
public function isUserMemberOfOrganization(string $username, string $organization): bool | ||
{ | ||
$url = "/orgs/{$organization}/memberships/{$username}"; | ||
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]); | ||
return $response['headers']['status-code'] >= 200 && $response['headers']['status-code'] < 300; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security bug: treating pending org invites as internal.
The memberships API returns 2xx for both active and pending memberships. You must ensure state === 'active'. Also, avoid failing event parsing on transient API errors; default to “not a member”.
Apply this diff:
- public function isUserMemberOfOrganization(string $username, string $organization): bool
- {
- $url = "/orgs/{$organization}/memberships/{$username}";
- $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]);
- return $response['headers']['status-code'] >= 200 && $response['headers']['status-code'] < 300;
- }
+ public function isUserMemberOfOrganization(string $username, string $organization): bool
+ {
+ if ($username === '' || $organization === '') {
+ return false;
+ }
+ try {
+ $url = "/orgs/{$organization}/memberships/{$username}";
+ $response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]);
+ $status = $response['headers']['status-code'] ?? 0;
+ if ($status >= 200 && $status < 300) {
+ return (($response['body']['state'] ?? '') === 'active');
+ }
+ return false;
+ } catch (\Throwable $e) {
+ return false;
+ }
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public function isUserMemberOfOrganization(string $username, string $organization): bool | |
{ | |
$url = "/orgs/{$organization}/memberships/{$username}"; | |
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]); | |
return $response['headers']['status-code'] >= 200 && $response['headers']['status-code'] < 300; | |
} | |
public function isUserMemberOfOrganization(string $username, string $organization): bool | |
{ | |
if ($username === '' || $organization === '') { | |
return false; | |
} | |
try { | |
$url = "/orgs/{$organization}/memberships/{$username}"; | |
$response = $this->call(self::METHOD_GET, $url, ['Authorization' => "Bearer $this->accessToken"]); | |
$status = $response['headers']['status-code'] ?? 0; | |
if ($status >= 200 && $status < 300) { | |
return (($response['body']['state'] ?? '') === 'active'); | |
} | |
return false; | |
} catch (\Throwable $e) { | |
return false; | |
} | |
} |
Noticed that we don't check org membership when determining external pull request contributions.
Related task: https://linear.app/appwrite/issue/SER-355/fix-external-vcs-deployments
Summary by CodeRabbit
New Features
Bug Fixes
Tests