Skip to content

Commit 05032fc

Browse files
committed
Code review fixes
- Remove query type helpers - Remove KMS AWS patches - Refactor encrypted fields map creation - Remove unused test code - Doc fixes and updates - Move has_encrypted_fields to model_utils - Renamed showfieldsmap -> createencryptedfieldsmap
1 parent 49be637 commit 05032fc

File tree

16 files changed

+185
-285
lines changed

16 files changed

+185
-285
lines changed

django_mongodb_backend/fields/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
EncryptedTextField,
2424
EncryptedTimeField,
2525
EncryptedURLField,
26-
EqualityQuery,
27-
RangeQuery,
28-
has_encrypted_fields,
2926
)
3027
from .json import register_json_field
3128
from .objectid import ObjectIdField
@@ -55,13 +52,10 @@
5552
"EncryptedTextField",
5653
"EncryptedTimeField",
5754
"EncryptedURLField",
58-
"EqualityQuery",
5955
"ObjectIdAutoField",
6056
"ObjectIdField",
6157
"PolymorphicEmbeddedModelArrayField",
6258
"PolymorphicEmbeddedModelField",
63-
"RangeQuery",
64-
"has_encrypted_fields",
6559
"register_fields",
6660
]
6761

django_mongodb_backend/fields/encryption.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
from django.db import models
22

33

4-
def has_encrypted_fields(model):
5-
return any(getattr(field, "encrypted", False) for field in model._meta.fields)
6-
7-
84
class EncryptedFieldMixin(models.Field):
95
encrypted = True
106

@@ -97,26 +93,3 @@ class EncryptedTextField(EncryptedFieldMixin, models.TextField):
9793

9894
class EncryptedURLField(EncryptedFieldMixin, models.URLField):
9995
pass
100-
101-
102-
class EqualityQuery(dict):
103-
def __init__(self, *, contention=None):
104-
super().__init__(queryType="equality")
105-
if contention is not None:
106-
self["contention"] = contention
107-
108-
109-
class RangeQuery(dict):
110-
def __init__(
111-
self, *, contention=None, max=None, min=None, precision=None, sparsity=None, trimFactor=None
112-
):
113-
super().__init__(queryType="range")
114-
options = {
115-
"contention": contention,
116-
"max": max,
117-
"min": min,
118-
"precision": precision,
119-
"sparsity": sparsity,
120-
"trimFactor": trimFactor,
121-
}
122-
self.update({k: v for k, v in options.items() if v is not None})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from bson import json_util
2+
from django.apps import apps
3+
from django.core.management.base import BaseCommand
4+
from django.db import DEFAULT_DB_ALIAS, connections
5+
6+
from django_mongodb_backend.model_utils import has_encrypted_fields
7+
8+
9+
class Command(BaseCommand):
10+
help = "Generate an `encrypted_fields_map` of encrypted fields for all encrypted"
11+
" models in the database for use with `AutoEncryptionOpts` in"
12+
" client configuration."
13+
14+
def add_arguments(self, parser):
15+
parser.add_argument(
16+
"--database",
17+
default=DEFAULT_DB_ALIAS,
18+
help="Specify the database to use for generating the encrypted"
19+
"fields map. Defaults to the 'default' database.",
20+
)
21+
22+
def handle(self, *args, **options):
23+
db = options["database"]
24+
connection = connections[db]
25+
client = connection.connection
26+
encrypted_fields_map = {}
27+
auto_encryption_opts = getattr(client._options, "auto_encryption_opts", None)
28+
# FIXME:
29+
# TypeError: ConnectionRouter.get_migratable_models() missing 2 required
30+
# positional arguments: 'app_config' and 'db'
31+
# for app_config in router.get_migratable_models():
32+
for app_config in apps.get_app_configs():
33+
for model in app_config.get_models():
34+
if has_encrypted_fields(model):
35+
encrypted_fields_map[model._meta.db_table] = (
36+
connection.schema_editor()._get_encrypted_fields_map(
37+
model, client, auto_encryption_opts
38+
)
39+
)
40+
self.stdout.write(json_util.dumps(encrypted_fields_map, indent=2))

django_mongodb_backend/management/commands/showfieldsmap.py

Lines changed: 0 additions & 52 deletions
This file was deleted.

django_mongodb_backend/model_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# TODO: Move to models.utils
2+
def has_encrypted_fields(model):
3+
return any(getattr(field, "encrypted", False) for field in model._meta.fields)

django_mongodb_backend/routers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from django.core.exceptions import ImproperlyConfigured
33
from django.db.utils import ConnectionRouter
44

5-
from .fields import has_encrypted_fields
5+
from .model_utils import has_encrypted_fields
66

77

88
class MongoRouter:

django_mongodb_backend/schema.py

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from pymongo.encryption import ClientEncryption
66
from pymongo.operations import SearchIndexModel
77

8-
from .fields import EmbeddedModelField, has_encrypted_fields
8+
from .fields import EmbeddedModelField
99
from .indexes import SearchIndex
10+
from .model_utils import has_encrypted_fields
1011
from .query import wrap_database_errors
1112
from .utils import OperationCollector
1213

@@ -432,51 +433,50 @@ def _create_collection(self, model):
432433
db_table = model._meta.db_table
433434
if has_encrypted_fields(model):
434435
client = self.connection.connection
435-
options = client._options
436-
ae = getattr(options, "auto_encryption_opts", None)
437-
if not ae:
436+
auto_encryption_opts = getattr(client._options, "auto_encryption_opts", None)
437+
if not auto_encryption_opts:
438438
raise ImproperlyConfigured(
439-
"Encrypted fields found but the connection does not have "
440-
"auto encryption options set. Please set `auto_encryption_opts` "
439+
"Encrypted fields found but "
440+
"DATABASES[[self.connection.alias}]['OPTIONS'] is missing "
441+
"auto_encryption_opts. Please set `auto_encryption_opts` "
441442
"in the connection settings."
442443
)
443-
encrypted_fields_map = getattr(ae, "_encrypted_fields_map", None)
444+
encrypted_fields_map = getattr(auto_encryption_opts, "_encrypted_fields_map", None)
444445
if not encrypted_fields_map:
445-
encrypted_fields_map = {}
446-
ce = ClientEncryption(
447-
ae._kms_providers,
448-
ae._key_vault_namespace,
449-
client,
450-
client.codec_options,
446+
encrypted_fields_map = self._get_encrypted_fields_map(
447+
model, client, auto_encryption_opts
451448
)
452-
fields = self._get_encrypted_fields_map(model)
453-
kms_provider = router.kms_provider(model)
454-
master_key = self.connection.settings_dict.get("KMS_CREDENTIALS").get(kms_provider)
455-
for field in fields["fields"]:
456-
key_alt_name = f"{db_table}_{field['path']}"
457-
data_key = ce.create_data_key(
458-
kms_provider=kms_provider,
459-
master_key=master_key,
460-
key_alt_names=[key_alt_name],
461-
)
462-
field["keyId"] = data_key
463-
encrypted_fields_map[db_table] = fields
464-
db.create_collection(db_table, encryptedFields=encrypted_fields_map[db_table])
449+
db.create_collection(db_table, encryptedFields=encrypted_fields_map)
465450
else:
466451
db.create_collection(db_table)
467452

468-
def _get_encrypted_fields_map(self, model):
453+
def _get_encrypted_fields_map(self, model, client, auto_encryption_opts):
469454
connection = self.connection
470455
fields = model._meta.fields
471-
472-
return {
473-
"fields": [
474-
{
456+
kms_provider = router.kms_provider(model)
457+
master_key = self.connection.settings_dict.get("KMS_CREDENTIALS").get(kms_provider)
458+
client_encryption = ClientEncryption(
459+
auto_encryption_opts._kms_providers,
460+
auto_encryption_opts._key_vault_namespace,
461+
client,
462+
client.codec_options,
463+
)
464+
db_table = model._meta.db_table
465+
field_list = []
466+
for field in fields:
467+
if getattr(field, "encrypted", False):
468+
key_alt_name = f"{db_table}_{field.column}"
469+
data_key = client_encryption.create_data_key(
470+
kms_provider=kms_provider,
471+
master_key=master_key,
472+
key_alt_names=[key_alt_name],
473+
)
474+
field_dict = {
475475
"bsonType": field.db_type(connection),
476476
"path": field.column,
477-
**({"queries": field.queries} if getattr(field, "queries", None) else {}),
477+
"keyId": data_key,
478478
}
479-
for field in fields
480-
if getattr(field, "encrypted", False)
481-
]
482-
}
479+
if getattr(field, "queries", None):
480+
field_dict["queries"] = field.queries
481+
field_list.append(field_dict)
482+
return {"fields": field_list}

docs/source/faq.rst

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,49 +58,48 @@ see :ref:`configuring-database-routers-setting`.
5858
Queryable Encryption
5959
====================
6060

61-
What about client side configuration?
61+
What about client-side configuration?
6262
-------------------------------------
6363

6464
In the :doc:`Queryable Encryption how-to guide <howto/queryable-encryption>`,
65-
server side Queryable Encryption configuration is covered.
65+
server-side Queryable Encryption configuration is covered.
6666

67-
Client side Queryable Encryption configuration requires that the entire schema
68-
for encrypted fields is known at the time of client connection.
67+
Client side Queryable Encryption configuration requires that the entire
68+
encrypted fields map be known at the time of client connection.
6969

70-
Schema Map
71-
~~~~~~~~~~
70+
Encrypted fields map
71+
~~~~~~~~~~~~~~~~~~~~
7272

7373
In addition to the
7474
:ref:`settings described in the how-to guide <server-side-queryable-encryption-settings>`,
75-
you will need to provide a ``schema_map`` to the ``AutoEncryptionOpts``.
75+
you will need to provide a ``encrypted_fields_map`` to the
76+
``AutoEncryptionOpts``.
7677

7778
Fortunately, this is easy to do with Django MongoDB Backend. You can use
78-
the ``showfieldsmap`` management command to generate the schema map
79+
the ``createencryptedfieldsmap`` management command to generate the schema map
7980
for your encrypted fields, and then use the results in your settings.
8081

81-
To generate the schema map, run the following command in your Django project:
82-
::
82+
To generate the encrypted fields map, run the following command in your Django
83+
project::
8384

84-
python manage.py showfieldsmap
85+
python manage.py createencryptedfieldsmap
8586

86-
.. note:: The ``showfieldsmap`` command is only available if you have the
87-
``django_mongodb_backend`` app included in the :setting:`INSTALLED_APPS`
88-
setting.
87+
.. note:: The ``createencryptedfieldsmap`` command is only available if you
88+
have the ``django_mongodb_backend`` app included in the
89+
:setting:`INSTALLED_APPS` setting.
8990

9091
Settings
9192
~~~~~~~~
9293

93-
Now include the generated schema map in your Django settings.
94-
95-
::
94+
Now include the generated schema map in your Django settings::
9695

9796
9897
DATABASES["encrypted"] = {
9998
10099
"OPTIONS": {
101100
"auto_encryption_opts": AutoEncryptionOpts(
102101
103-
schema_map= {
102+
encrypted_fields_map = {
104103
"encryption__patientrecord": {
105104
"fields": [
106105
{
@@ -119,4 +118,6 @@ Now include the generated schema map in your Django settings.
119118
120119
}
121120

122-
You are now ready to use client side :doc:`Queryable Encryption </topics/queryable-encryption>` in your Django project.
121+
You are now ready to use client-side
122+
:doc:`Queryable Encryption </topics/queryable-encryption>`
123+
in your Django project.

docs/source/howto/queryable-encryption.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Configuring Queryable Encryption in Django is similar to
66
:doc:`manual:core/queryable-encryption/quick-start` but with some additional
77
steps required for Django.
88

9-
.. note:: This section describes how to configure server side Queryable
9+
.. admonition:: Server-side Queryable Encryption
10+
11+
This section describes how to configure server side Queryable
1012
Encryption in Django. For configuration of client side Queryable Encryption,
1113
please refer to this :ref:`FAQ question <queryable-encryption>`.
1214

@@ -19,7 +21,9 @@ you will need to install PyMongo with Queryable Encryption support::
1921

2022
pip install django-mongodb-backend[encryption]
2123

22-
.. note:: You can use Queryable Encryption on a MongoDB 7.0 or later replica
24+
.. admonition:: Queryable Encryption Compatibility
25+
26+
You can use Queryable Encryption on a MongoDB 7.0 or later replica
2327
set or sharded cluster, but not a standalone instance.
2428
:ref:`This table <manual:qe-compatibility-reference>` shows which MongoDB
2529
server products support which Queryable Encryption mechanisms.

docs/source/ref/django-admin.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ Available commands
2828
Defaults to ``default``.
2929

3030

31-
``showfieldsmap``
31+
``createencryptedfieldsmap``
3232
----------------------------
3333

34-
.. django-admin:: showfieldsmap
34+
.. django-admin:: createencryptedfieldsmap
3535

3636
Creates an encrypted fields map that can be used with `encrypted_fields_map`
3737
in :class:`~pymongo.encryption_options.AutoEncryptionOpts` to configure

0 commit comments

Comments
 (0)