Skip to content

Conversation

SoulPancake
Copy link

@SoulPancake SoulPancake commented Aug 20, 2025

This fixes #207

Description

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Added support for OAuth2 scopes in client credentials.
    • Accepts scopes as a string or list; scopes are included in token requests.
    • No breaking changes to existing authentication flows.
  • Documentation

    • Updated credential configuration docs to describe the new scopes option.
  • Tests

    • Added unit tests covering scopes provided as a list and as a space-delimited string.
    • Verified correct serialization of scopes in token requests and token handling behavior.

@SoulPancake SoulPancake requested a review from a team as a code owner August 20, 2025 07:25
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds optional OAuth2 scopes to CredentialConfiguration and includes the scope parameter in OAuth2 client_credentials token requests (async and sync). Updates tests to cover scopes provided as list or string.

Changes

Cohort / File(s) Summary
Credential configuration: scopes support
openfga_sdk/credentials.py
Add scopes to constructor (str
OAuth2 token request (async)
openfga_sdk/oauth2.py
Include scope in token request body when configuration.scopes is set; join list with spaces or pass string as-is.
OAuth2 token request (sync)
openfga_sdk/sync/oauth2.py
Same as async: conditionally add scope to client_credentials request body.
Unit tests: credentials
test/credentials_test.py
Add tests ensuring scopes accepted as list or string in client_credentials configuration.
Unit tests: OAuth2 token acquisition
test/sync/oauth2_test.py
Add tests verifying scope serialized to form body ("read write admin") and token handling unaffected.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App
  participant OAuth2Client
  participant AuthServer as OAuth2 Token Endpoint

  App->>OAuth2Client: get_authentication() with client_credentials
  OAuth2Client->>OAuth2Client: Build form: client_id, client_secret, audience, grant_type
  alt scopes provided
    OAuth2Client->>OAuth2Client: If list -> join with spaces; else use string
    OAuth2Client->>AuthServer: POST /oauth/token (form incl. scope)
  else no scopes
    OAuth2Client->>AuthServer: POST /oauth/token (form without scope)
  end
  AuthServer-->>OAuth2Client: 200 OK { access_token, expires_in }
  OAuth2Client-->>App: Authorization: Bearer <token>
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Support passing scopes in CredentialConfiguration for client_credentials (#207)
Serialize scopes (list or string) into space-delimited scope parameter (#207)
Include scope in OAuth2 client_credentials token request (async/sync) (#207)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@SoulPancake
Copy link
Author

@evansims Can you please point me to the relevant documentation section for this so I can make changes there?

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (6)
openfga_sdk/oauth2.py (1)

82-88: Normalize and only include non-empty scopes; support tuples as well

Current logic sends scope="" for empty list/string and doesn’t trim/normalize whitespace. Some providers reject empty scope. Normalize and include only when non-empty; allow tuples too.

-        # Add scope parameter if scopes are configured
-        if configuration.scopes is not None:
-            if isinstance(configuration.scopes, list):
-                post_params["scope"] = " ".join(configuration.scopes)
-            else:
-                post_params["scope"] = configuration.scopes
+        # Add scope parameter if scopes are configured (normalize and skip empty)
+        scopes = configuration.scopes
+        if scopes is not None:
+            if isinstance(scopes, (list, tuple)):
+                scope_str = " ".join(s for s in scopes if s)
+            else:
+                scope_str = " ".join(str(scopes).split())
+            if scope_str:
+                post_params["scope"] = scope_str
openfga_sdk/sync/oauth2.py (2)

82-88: Normalize and only include non-empty scopes; support tuples as well

Same concern as async path: avoid sending scope="" and normalize whitespace; allow tuples.

-        # Add scope parameter if scopes are configured
-        if configuration.scopes is not None:
-            if isinstance(configuration.scopes, list):
-                post_params["scope"] = " ".join(configuration.scopes)
-            else:
-                post_params["scope"] = configuration.scopes
+        # Add scope parameter if scopes are configured (normalize and skip empty)
+        scopes = configuration.scopes
+        if scopes is not None:
+            if isinstance(scopes, (list, tuple)):
+                scope_str = " ".join(s for s in scopes if s)
+            else:
+                scope_str = " ".join(str(scopes).split())
+            if scope_str:
+                post_params["scope"] = scope_str

73-74: Prefer using _parse_issuer to construct token_url (handles scheme and custom paths)

Hardcoding https://{issuer}/oauth/token can break when api_issuer already includes a scheme or a full oauth endpoint. Using _parse_issuer (as in the async client) avoids duplication and handles inputs robustly.

-        token_url = f"https://{configuration.api_issuer}/oauth/token"
+        token_url = self._credentials._parse_issuer(configuration.api_issuer)
test/credentials_test.py (1)

110-145: Add edge-case tests for empty scopes

Consider adding:

  • scopes=[]
  • scopes="" (and possibly " " with extra spaces)

Depending on desired behavior, assert either normalization to None (preferred) or that scope isn’t included in the token request.

openfga_sdk/credentials.py (1)

122-135: Normalize scopes in the setter; reject invalid types early

Normalizing here simplifies callers and avoids sending empty/invalid values to OAuth servers. Recommend trimming, collapsing whitespace, dropping empty items, and accepting list/tuple; raise on invalid types.

-    @scopes.setter
-    def scopes(self, value):
-        """
-        Update the scopes
-        """
-        self._scopes = value
+    @scopes.setter
+    def scopes(self, value):
+        """
+        Update the scopes. Accepts a string or a list/tuple of strings.
+        Normalizes whitespace and drops empty items; sets to None if empty.
+        """
+        if value is None:
+            self._scopes = None
+            return
+        if isinstance(value, (list, tuple)):
+            normalized = [str(s).strip() for s in value if s and str(s).strip()]
+            self._scopes = normalized if normalized else None
+            return
+        if isinstance(value, str):
+            s = " ".join(value.split()).strip()
+            self._scopes = s if s else None
+            return
+        raise ApiValueError("scopes must be a string or a list/tuple of strings")
test/sync/oauth2_test.py (1)

164-219: Close REST client at the end of the test

The previous test closes rest_client; this one doesn’t. Add rest_client.close() to avoid leaking resources.

         mock_request.assert_called_once_with(
@@
             },
         )
+        rest_client.close()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b0ac3e and bf589cf.

📒 Files selected for processing (5)
  • openfga_sdk/credentials.py (3 hunks)
  • openfga_sdk/oauth2.py (1 hunks)
  • openfga_sdk/sync/oauth2.py (1 hunks)
  • test/credentials_test.py (1 hunks)
  • test/sync/oauth2_test.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
test/credentials_test.py (1)
openfga_sdk/credentials.py (16)
  • method (153-157)
  • method (160-164)
  • configuration (167-171)
  • configuration (174-178)
  • CredentialConfiguration (25-134)
  • client_id (53-57)
  • client_id (60-64)
  • client_secret (67-71)
  • client_secret (74-78)
  • api_issuer (95-99)
  • api_issuer (102-106)
  • api_audience (81-85)
  • api_audience (88-92)
  • scopes (123-127)
  • scopes (130-134)
  • validate_credentials_config (207-238)
test/sync/oauth2_test.py (3)
test/oauth2_test.py (1)
  • mock_response (27-30)
openfga_sdk/credentials.py (16)
  • Credentials (137-238)
  • method (153-157)
  • method (160-164)
  • configuration (167-171)
  • configuration (174-178)
  • CredentialConfiguration (25-134)
  • client_id (53-57)
  • client_id (60-64)
  • client_secret (67-71)
  • client_secret (74-78)
  • api_issuer (95-99)
  • api_issuer (102-106)
  • api_audience (81-85)
  • api_audience (88-92)
  • scopes (123-127)
  • scopes (130-134)
openfga_sdk/sync/oauth2.py (2)
  • OAuth2Client (45-161)
  • get_authentication_header (153-161)
openfga_sdk/sync/oauth2.py (1)
openfga_sdk/credentials.py (4)
  • configuration (167-171)
  • configuration (174-178)
  • scopes (123-127)
  • scopes (130-134)
openfga_sdk/oauth2.py (1)
openfga_sdk/credentials.py (4)
  • configuration (167-171)
  • configuration (174-178)
  • scopes (123-127)
  • scopes (130-134)
🔇 Additional comments (3)
test/credentials_test.py (1)

110-145: LGTM: verifies scopes accepted as list and string

Tests correctly assert preservation of the provided scopes value/type under client_credentials.

openfga_sdk/credentials.py (1)

33-51: CredentialConfiguration: scopes parameter addition — LGTM

Docstring, init signature, and internal storage look good and backward compatible.

test/sync/oauth2_test.py (1)

107-163: LGTM: verifies scope serialization (list -> space-delimited)

Solid assertions on request payload and headers; mirrors the base client_credentials test well.

Comment on lines +82 to +88
# Add scope parameter if scopes are configured
if configuration.scopes is not None:
if isinstance(configuration.scopes, list):
post_params["scope"] = " ".join(configuration.scopes)
else:
post_params["scope"] = configuration.scopes

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add async tests for scopes handling

Sync path has good coverage; async path does not appear to be tested for scope serialization. Please add a mirror test in test/oauth2_test.py that asserts scope is form-encoded as a space-delimited string for list and string inputs.


🏁 Script executed:

#!/bin/bash
# Look for async OAuth2 tests that exercise scope serialization
rg -n -C2 -P 'from\s+openfga_sdk\.oauth2\s+import\s+OAuth2Client' test || true
# Check whether scope assertions exist in async tests
rg -n -C3 -P '\bscope\b' test/oauth2_test.py || true

Length of output: 415


Add synchronous and asynchronous tests for OAuth2Client scope serialization

A search of test/oauth2_test.py found no existing assertions for the “scope” parameter. Please extend that file with:

  • Sync tests
    • Verify that passing configuration.scopes as a list yields a space-delimited "scope" form field
    • Verify that passing it as a single string is encoded unchanged
  • Async tests (pytest.mark.asyncio)
    • Mirror the two sync tests against the async token-request method

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.

Scope parameter not supported for OAuth client credentials
1 participant