Skip to content

Commit f4c9363

Browse files
committed
Add --create to showencryptedfieldsmap
1 parent 5682d18 commit f4c9363

File tree

5 files changed

+62
-27
lines changed

5 files changed

+62
-27
lines changed

django_mongodb_backend/management/commands/createencryptedfieldsmap.py renamed to django_mongodb_backend/management/commands/showencryptedfieldsmap.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ def add_arguments(self, parser):
1818
help="Specify the database to use for generating the encrypted"
1919
"fields map. Defaults to the 'default' database.",
2020
)
21+
parser.add_argument(
22+
"--create",
23+
action="store_true",
24+
help="Create the encrypted fields map.",
25+
)
2126

2227
def handle(self, *args, **options):
2328
db = options["database"]
29+
create = options.get("create", False)
2430
connection = connections[db]
2531
client = connection.connection
2632
encrypted_fields_map = {}
@@ -32,9 +38,9 @@ def handle(self, *args, **options):
3238
for app_config in apps.get_app_configs():
3339
for model in app_config.get_models():
3440
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-
)
41+
from_db = not create
42+
fields = connection.schema_editor()._get_encrypted_fields_map(
43+
model, client, auto_encryption_opts, from_db=from_db
3944
)
45+
encrypted_fields_map[model._meta.db_table] = fields
4046
self.stdout.write(json_util.dumps(encrypted_fields_map, indent=2))

django_mongodb_backend/schema.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ def _create_collection(self, model):
450450
else:
451451
db.create_collection(db_table)
452452

453-
def _get_encrypted_fields_map(self, model, client, auto_encryption_opts):
453+
def _get_encrypted_fields_map(self, model, client, auto_encryption_opts, from_db=False):
454454
connection = self.connection
455455
fields = model._meta.fields
456456
kms_provider = router.kms_provider(model)
@@ -461,16 +461,25 @@ def _get_encrypted_fields_map(self, model, client, auto_encryption_opts):
461461
client,
462462
client.codec_options,
463463
)
464+
# Parse key vault namespace
465+
key_vault_db, key_vault_coll = auto_encryption_opts._key_vault_namespace.split(".", 1)
466+
key_vault_collection = client[key_vault_db][key_vault_coll]
464467
db_table = model._meta.db_table
465468
field_list = []
466469
for field in fields:
467470
if getattr(field, "encrypted", False):
468471
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-
)
472+
if from_db:
473+
key_doc = key_vault_collection.find_one({"keyAltNames": key_alt_name})
474+
if not key_doc:
475+
raise ValueError(f"No key found in keyvault for keyAltName={key_alt_name}")
476+
data_key = key_doc["_id"]
477+
else:
478+
data_key = client_encryption.create_data_key(
479+
kms_provider=kms_provider,
480+
master_key=master_key,
481+
key_alt_names=[key_alt_name],
482+
)
474483
field_dict = {
475484
"bsonType": field.db_type(connection),
476485
"path": field.column,

docs/source/howto/queryable-encryption.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,15 @@ you will need to provide a ``encrypted_fields_map`` to the
109109
``AutoEncryptionOpts``.
110110

111111
Fortunately, this is easy to do with Django MongoDB Backend. You can use
112-
the ``createencryptedfieldsmap`` management command to generate the schema map
112+
the ``showencryptedfieldsmap`` management command to generate the schema map
113113
for your encrypted fields, and then use the results in your settings.
114114

115115
To generate the encrypted fields map, run the following command in your Django
116116
project::
117117

118-
python manage.py createencryptedfieldsmap
118+
python manage.py showencryptedfieldsmap
119119

120-
.. note:: The ``createencryptedfieldsmap`` command is only available if you
120+
.. note:: The ``showencryptedfieldsmap`` command is only available if you
121121
have the ``django_mongodb_backend`` app included in the
122122
:setting:`INSTALLED_APPS` setting.
123123

docs/source/ref/django-admin.rst

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

3030

31-
``createencryptedfieldsmap``
31+
``showencryptedfieldsmap``
3232
----------------------------
3333

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

36-
Creates an encrypted fields map that can be used with `encrypted_fields_map`
37-
in :class:`~pymongo.encryption_options.AutoEncryptionOpts` to configure
38-
client-side Queryable Encryption.
36+
Show mapping of models and their encrypted fields.
3937

4038
.. django-admin-option:: --database DATABASE
4139

4240
Specifies the database to use.
4341
Defaults to ``default``.
42+
43+
.. django-admin-option:: --create
44+
45+
If specified, creates the data keys instead of getting them from the
46+
database.

tests/encryption_/test_management.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,25 +107,42 @@
107107
INSTALLED_APPS={"prepend": "django_mongodb_backend"},
108108
)
109109
@override_settings(DATABASE_ROUTERS=[TestEncryptedRouter()])
110-
class EncryptedFieldTests(TransactionTestCase):
110+
class EncryptedFieldsManagementCommandTests(TransactionTestCase):
111111
databases = {"default", "encrypted"}
112112
available_apps = ["django_mongodb_backend", "encryption_"]
113113

114+
def _compare_json(self, json1, json2):
115+
# Remove keyIds since they are different for each run.
116+
for table in json2:
117+
for field in json2[table]["fields"]:
118+
del field["keyId"]
119+
# TODO: probably we don't need to test the entire mapping, otherwise it
120+
# requires updates every time a new model or field is added!
121+
self.assertEqual(json1, json2)
122+
123+
def test_show_encrypted_fields_map(self):
124+
self.maxDiff = None
125+
out = StringIO()
126+
call_command(
127+
"showencryptedfieldsmap",
128+
"--database",
129+
"encrypted",
130+
verbosity=0,
131+
stdout=out,
132+
)
133+
output_json = json_util.loads(out.getvalue())
134+
self._compare_json(EXPECTED_ENCRYPTED_FIELDS_MAP, output_json)
135+
114136
def test_create_encrypted_fields_map(self):
115137
self.maxDiff = None
116138
out = StringIO()
117139
call_command(
118-
"createencryptedfieldsmap",
140+
"showencryptedfieldsmap",
119141
"--database",
120142
"encrypted",
143+
"--create",
121144
verbosity=0,
122145
stdout=out,
123146
)
124-
# Remove keyIds since they are different for each run.
125147
output_json = json_util.loads(out.getvalue())
126-
for table in output_json:
127-
for field in output_json[table]["fields"]:
128-
del field["keyId"]
129-
# TODO: probably we don't need to test the entire mapping, otherwise it
130-
# requires updates every time a new model or field is added!
131-
self.assertEqual(EXPECTED_ENCRYPTED_FIELDS_MAP, output_json)
148+
self._compare_json(EXPECTED_ENCRYPTED_FIELDS_MAP, output_json)

0 commit comments

Comments
 (0)