Skip to content

Conversation

@ahmedxgouda
Copy link
Collaborator

Proposed change

Add the PR description here.

Checklist

  • I've read and followed the contributing guidelines.
  • I've run make check-test locally; all checks and tests passed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

Summary by CodeRabbit

  • New Features
    • No user-facing feature changes.
  • Refactor
    • Centralized the KMS client creation with a thread-safe singleton to reduce overhead and ensure consistent initialization across requests.
  • Bug Fixes
    • Mitigated potential race conditions during encryption/decryption setup under high concurrency, improving reliability.
  • Tests
    • Added tests to verify singleton behavior and preserve existing encryption/decryption functionality.

Walkthrough

Replaces a module-level KMS client with a thread-safe singleton KmsClient, updates callers to use KmsClient() for encrypt/decrypt, removes the global instance, and adds a test asserting singleton behavior.

Changes

Cohort / File(s) Summary
KMS client singleton implementation
backend/apps/nest/clients/kms.py
Added KmsClient.__new__ and class-level _lock to enforce a thread-safe singleton; guarded __init__ to avoid reinitialization; removed global kms_client = KmsClient().
Model fields KMS usage update
backend/apps/common/model_fields.py
Replaced use of the module-level kms_client with per-call KmsClient() invocations for encrypt/decrypt.
Tests for singleton
backend/tests/apps/nest/clients/kms_test.py
Reset singleton state in setup and added test_singleton to assert multiple KmsClient() calls return the same instance and share the underlying client.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r
  • kasya

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 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 or @coderabbit 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.

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.

@ahmedxgouda
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (2)
backend/tests/apps/nest/clients/kms_test.py (1)

41-48: Strengthen singleton test; keep boto3 patched and assert idempotent init

As written, init will run on each instantiation and may hit real boto3 because the patch from the setup fixture ends before the test body. Patch where KmsClient looks up boto3 and assert no new client creations on repeated calls.

 @override_settings(AWS_REGION="us-west-2", AWS_KMS_KEY_ID="test_key_id")
 def test_singleton(self):
     """Test singleton behavior."""
-    instance1 = KmsClient()
-    instance2 = KmsClient()
-    assert instance1 is instance2
-    assert instance1.client is instance2.client
+    # Patch where used to avoid real boto3 and verify idempotent __init__
+    with patch("apps.nest.clients.kms.boto3.client") as mock_client:
+        instance1 = KmsClient()
+        instance2 = KmsClient()
+        assert instance1 is instance2
+        assert instance1.client is instance2.client
+        # No new boto3 client should be created on repeated instantiation
+        assert mock_client.call_count == 0
backend/apps/common/model_fields.py (1)

26-27: Minor cleanup and note on perf coupling to KmsClient init

  • Calling KmsClient() per use is fine so long as KmsClient.init is idempotent (see suggested fix in kms.py) to avoid recreating boto3 clients.
  • The trailing conditional at Line 39 is redundant after the earlier emptiness checks.
-        return KmsClient().decrypt(bytes(value)) if value else None
+        return KmsClient().decrypt(bytes(value))

Also applies to: 39-39

📜 Review details

Configuration used: Path: .coderabbit.yaml

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 06b0527 and b011ff5.

📒 Files selected for processing (3)
  • backend/apps/common/model_fields.py (3 hunks)
  • backend/apps/nest/clients/kms.py (1 hunks)
  • backend/tests/apps/nest/clients/kms_test.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T15:34:29.661Z
Learnt from: ahmedxgouda
PR: OWASP/Nest#2089
File: backend/apps/common/model_fields.py:18-27
Timestamp: 2025-08-19T15:34:29.661Z
Learning: The KmsEncryptedField in backend/apps/common/model_fields.py is designed to be a KMS-only field that requires IS_AWS_KMS_ENABLED=True for any operation, including storing NULL values. This enforces a strict security model where the field cannot be used without proper KMS configuration.

Applied to files:

  • backend/apps/common/model_fields.py
⏰ 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). (3)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
🔇 Additional comments (1)
backend/apps/common/model_fields.py (1)

6-6: Import switch looks good and aligns with singleton usage

Moving to KmsClient is consistent with the new design.

@ahmedxgouda ahmedxgouda marked this pull request as ready for review August 30, 2025 14:27
@ahmedxgouda ahmedxgouda requested a review from arkid15r as a code owner August 30, 2025 14:27
@sonarqubecloud
Copy link

@ahmedxgouda
Copy link
Collaborator Author

ahmedxgouda commented Aug 31, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 31, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

1 similar comment
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 31, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 0

♻️ Duplicate comments (2)
backend/apps/nest/clients/kms.py (2)

26-31: Prefer AWS default credential provider chain; avoid passing static keys.

Rely on instance profiles/IRSA/assumed roles to reduce secret handling in app config and simplify tests.

-        self.client = boto3.client(
-            "kms",
-            aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
-            aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
-            region_name=settings.AWS_REGION,
-        )
+        self.client = boto3.client("kms", region_name=settings.AWS_REGION)

14-21: Fix double-initialization race in init (singleton is created once but initialized twice under concurrency).

Two concurrent calls can both pass the client-presence check and each create a separate boto3 client, racing on the same instance. Guard initialization with a dedicated lock and an _initialized flag.

Apply:

 class KmsClient:
     """AWS KMS Client."""
 
-    _lock = Lock()
+    _lock = Lock()
+    _init_lock = Lock()
@@
-    def __new__(cls):
+    def __new__(cls, *args, **kwargs):
         """Create a new instance of KmsClient."""
         if not hasattr(cls, "instance"):
             with cls._lock:
                 if not hasattr(cls, "instance"):
                     cls.instance = super().__new__(cls)
         return cls.instance
@@
-    def __init__(self):
+    def __init__(self):
         """Initialize the KMS client."""
-        if getattr(self, "client", None) is not None:
-            return
-        self.client = boto3.client(
-            "kms",
-            aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
-            aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
-            region_name=settings.AWS_REGION,
-        )
+        if getattr(self, "_initialized", False):
+            return
+        with self.__class__._init_lock:
+            if getattr(self, "_initialized", False):
+                return
+            self.client = boto3.client(
+                "kms",
+                aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
+                aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+                region_name=settings.AWS_REGION,
+            )
+            self._initialized = True

Also applies to: 24-31

🧹 Nitpick comments (6)
backend/apps/nest/clients/kms.py (3)

14-20: Harden new signature and make the singleton attribute explicit.

  • Accept *args, **kwargs for forward-compat.
  • Declare instance = None at class-level to satisfy linters/static analysis and improve readability.
 class KmsClient:
     """AWS KMS Client."""
 
-    _lock = Lock()
+    _lock = Lock()
+    instance = None  # explicit singleton slot
 
-    def __new__(cls):
+    def __new__(cls, *args, **kwargs):
         """Create a new instance of KmsClient."""

26-31: Add client timeouts and bounded retries via botocore Config.

Prevents hung requests and adds resilient backoff on transient AWS errors.

+from botocore.config import Config
@@
-        self.client = boto3.client("kms", region_name=settings.AWS_REGION)
+        self.client = boto3.client(
+            "kms",
+            region_name=settings.AWS_REGION,
+            config=Config(
+                retries={"max_attempts": 3, "mode": "standard"},
+                read_timeout=5,
+                connect_timeout=2,
+            ),
+        )

22-31: Offer a safe, test-only reset hook instead of mutating class attrs directly in tests.

Keeps tests deterministic and thread-safe.

Add:

@classmethod
def _reset_for_tests(cls):
    with cls._lock:
        if hasattr(cls, "instance"):
            del cls.instance

Want me to push a commit updating tests to use KmsClient._reset_for_tests()?

backend/tests/apps/nest/clients/kms_test.py (3)

17-21: Patch where the symbol is used.

Patch apps.nest.clients.kms.boto3.client instead of boto3.client to avoid accidental leakage if boto3 was imported elsewhere before the patch.

-with patch("boto3.client") as mock_boto3_client:
+with patch("apps.nest.clients.kms.boto3.client") as mock_boto3_client:

21-23: Use the provided reset hook rather than deleting class attributes.

Improves clarity and avoids attribute errors if the singleton slot changes.

-            if hasattr(KmsClient, "instance"):
-                del KmsClient.instance
+            KmsClient._reset_for_tests()

45-50: Strengthen the singleton test by asserting a single boto3 client creation.

Ensures __init__ guarding works as expected.

         instance1 = KmsClient()
         instance2 = KmsClient()
         assert instance1 is instance2
         assert instance1.client is instance2.client
+        assert self.mock_boto3_client.call_count == 1
📜 Review details

Configuration used: Path: .coderabbit.yaml

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 b011ff5 and 1aa0e13.

📒 Files selected for processing (2)
  • backend/apps/nest/clients/kms.py (1 hunks)
  • backend/tests/apps/nest/clients/kms_test.py (2 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). (3)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests

@ahmedxgouda ahmedxgouda changed the title Convert singleton pattern in kms client to classic singleton Use singleton pattern in kms client Aug 31, 2025
@arkid15r arkid15r merged commit 210e207 into OWASP:feature/nestbot-calendar-events Sep 1, 2025
22 checks passed
ahmedxgouda added a commit to ahmedxgouda/Nest that referenced this pull request Sep 7, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Sep 15, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Sep 17, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Sep 19, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Sep 19, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Sep 23, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Sep 28, 2025
* Use singleton

* Add thread locking
arkid15r pushed a commit that referenced this pull request Oct 7, 2025
* Use singleton

* Add thread locking
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants