Skip to content

INTPYTHON-527 Add Queryable Encryption support #329

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

aclark4life
Copy link
Collaborator

@aclark4life aclark4life commented Jun 27, 2025

Previous attempts and additional context here:

@aclark4life

This comment was marked as resolved.

@timgraham

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@timgraham

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@timgraham

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@timgraham

This comment was marked as resolved.

@aclark4life

This comment was marked as resolved.

@timgraham

This comment was marked as resolved.

Comment on lines 91 to 102
Encrypted fields map
~~~~~~~~~~~~~~~~~~~~

In addition to the :ref:`settings described in the how-to guide
<queryable-encryption-settings>` you will need to provide a
``encrypted_fields_map`` to the ``AutoEncryptionOpts``.

You can use the :djadmin:`showencryptedfieldsmap` management command to generate
the schema map for your encrypted fields and then use the results in your
settings::

python manage.py showencryptedfieldsmap --database=encrypted
Copy link
Collaborator

Choose a reason for hiding this comment

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

We're missing the step on defining your models (consolidate from topic guide?), then makemigrations and migrate. Otherwise showencryptedfieldsmap may crash without any keyIds to retrieve?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed and no, it should return "no keys found"

aclark4life and others added 9 commits August 13, 2025 12:08
QE Doc flow

- Three top level (or close to it) docs
 - How to guide on QE configuration
 - QE field guide (moved out of model field ref to its own section)
 - Topic guide on QE usage
- Planning to finish QE setup in the how-to doc then switch back to the
  topic doc to explain the workflow.
In the QE topics guide I'm going to explain how to develop a patient model
similar to the Python QE tutorial and the encryption test suite.

If embedding were possible now, I could present the same example. Since
embedded models are a WIP I may either:

- design the guide to work around that issue
- design the guide to anticipate resolution of that issue

In ether case, I'll try to wrap this 24-48 hour docs jam having covered
everything I know about QE and Django and having presented it to Django
folks in a way we can support.
- Configuring the encrypted_fields_map section
- Configuring the Automatic Encryption Shared Library
Copilot

This comment was marked as resolved.

aclark4life and others added 4 commits August 13, 2025 20:16
…map.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@aclark4life aclark4life requested a review from Copilot August 14, 2025 00:23
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements comprehensive Queryable Encryption support for the Django MongoDB Backend, enabling developers to use encrypted fields in Django models while maintaining the ability to query on encrypted data.

Key changes:

  • Added encrypted field types that extend Django's standard fields with encryption capabilities
  • Implemented automatic encrypted collection creation with proper schema generation
  • Added management command for viewing and managing encrypted field mappings

Reviewed Changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
django_mongodb_backend/fields/encryption.py Implements EncryptedFieldMixin and all encrypted field variants
django_mongodb_backend/schema.py Adds encrypted collection creation and encrypted fields map generation
django_mongodb_backend/management/commands/showencryptedfieldsmap.py Management command for displaying encrypted field mappings
django_mongodb_backend/features.py Adds feature detection for Queryable Encryption support
tests/encryption_/ Comprehensive test suite for encrypted fields and functionality
docs/source/ Complete documentation for setup and usage

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

options = client._options
auto_encryption_opts = options.auto_encryption_opts
kms_provider = router.kms_provider(model)
master_key = self.connection.settings_dict.get("KMS_CREDENTIALS").get(kms_provider)
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

This code will raise an AttributeError if 'KMS_CREDENTIALS' is not in settings_dict, as .get() returns None and then .get() is called on None. Should use: master_key = self.connection.settings_dict.get("KMS_CREDENTIALS", {}).get(kms_provider)

Suggested change
master_key = self.connection.settings_dict.get("KMS_CREDENTIALS").get(kms_provider)
master_key = self.connection.settings_dict.get("KMS_CREDENTIALS", {}).get(kms_provider)

Copilot uses AI. Check for mistakes.

if not model_name:
return None
try:
model = apps.get_model(app_label, model_name)
except LookupError:
return None
return False if issubclass(model, EmbeddedModel) else None


Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

This function is defined at module level but references 'self' as if it's a method. It should either be a standalone function without 'self' parameter or be properly defined as a method within a class.

Suggested change
# This function is intended to be monkey-patched as a method of ConnectionRouter.

Copilot uses AI. Check for mistakes.

def _compare_output(self, json1, json2):
# Remove keyIds since they are different for each run.
for field in json2["fields"]:
del field["keyId"]
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

Using del on a dictionary key that might not exist will raise a KeyError. Should use field.pop("keyId", None) to safely remove the key if it exists, similar to line 62 in test_schema.py.

Suggested change
del field["keyId"]
field.pop("keyId", None)

Copilot uses AI. Check for mistakes.

else:
db.create_collection(db_table)

def _get_encrypted_fields_map(self, model, client, create_data_keys=False):
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The method signature is inconsistent with its usage. At line 448, it's called with auto_encryption_opts as the third parameter, but the method signature expects create_data_keys as the third parameter. This will cause incorrect behavior.

Copilot uses AI. Check for mistakes.


class Patient(models.Model):
name = models.CharField(max_length=255)
ssn = models.EncryptedCharField(max_length=11, queries={"queryType": "equality"})
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

Incorrect import reference. Should be EncryptedCharField from django_mongodb_backend.fields, not models.EncryptedCharField. The import at line 71 shows the correct import path.

Suggested change
ssn = models.EncryptedCharField(max_length=11, queries={"queryType": "equality"})
ssn = EncryptedCharField(max_length=11, queries={"queryType": "equality"})

Copilot uses AI. Check for mistakes.

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.

6 participants