|
| 1 | +import contextlib |
| 2 | + |
1 | 3 | from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
2 | 4 | from django.db.models import Index, UniqueConstraint
|
| 5 | +from pymongo.encryption import EncryptedCollectionError |
3 | 6 | from pymongo.operations import SearchIndexModel
|
4 | 7 |
|
5 |
| -from django_mongodb_backend.indexes import SearchIndex |
6 |
| - |
| 8 | +from .encryption import get_encrypted_client |
7 | 9 | from .fields import EmbeddedModelField
|
| 10 | +from .indexes import SearchIndex |
8 | 11 | from .query import wrap_database_errors
|
9 | 12 | from .utils import OperationCollector
|
10 | 13 |
|
@@ -41,7 +44,7 @@ def get_database(self):
|
41 | 44 | @wrap_database_errors
|
42 | 45 | @ignore_embedded_models
|
43 | 46 | def create_model(self, model):
|
44 |
| - self.get_database().create_collection(model._meta.db_table) |
| 47 | + self._create_collection(model) |
45 | 48 | self._create_model_indexes(model)
|
46 | 49 | # Make implicit M2M tables.
|
47 | 50 | for field in model._meta.local_many_to_many:
|
@@ -418,3 +421,45 @@ def _field_should_have_unique(self, field):
|
418 | 421 | db_type = field.db_type(self.connection)
|
419 | 422 | # The _id column is automatically unique.
|
420 | 423 | return db_type and field.unique and field.column != "_id"
|
| 424 | + |
| 425 | + def _supports_encryption(self, model): |
| 426 | + """ |
| 427 | + Check for `supports_encryption` feature and `auto_encryption_opts` |
| 428 | + and `embedded_fields_map`. If `supports_encryption` is True and |
| 429 | + `auto_encryption_opts` is in the cached connection settings and |
| 430 | + the model has an embedded_fields_map property, then encryption |
| 431 | + is supported. |
| 432 | + """ |
| 433 | + return ( |
| 434 | + self.connection.features.supports_encryption |
| 435 | + and self.connection._settings_dict.get("OPTIONS", {}).get("auto_encryption_opts") |
| 436 | + and hasattr(model, "encrypted_fields_map") |
| 437 | + ) |
| 438 | + |
| 439 | + def _create_collection(self, model): |
| 440 | + """ |
| 441 | + Create a collection or, if encryption is supported, create |
| 442 | + an encrypted connection then use it to create an encrypted |
| 443 | + client then use that to create an encrypted collection. |
| 444 | + """ |
| 445 | + |
| 446 | + if self._supports_encryption(model): |
| 447 | + auto_encryption_opts = self.connection._settings_dict.get("OPTIONS", {}).get( |
| 448 | + "auto_encryption_opts" |
| 449 | + ) |
| 450 | + # Use the cached settings dict to create a new connection |
| 451 | + encrypted_connection = self.connection.get_new_connection( |
| 452 | + self.connection._settings_dict |
| 453 | + ) |
| 454 | + # Use the encrypted connection and auto_encryption_opts to create an encrypted client |
| 455 | + encrypted_client = get_encrypted_client(auto_encryption_opts, encrypted_connection) |
| 456 | + |
| 457 | + with contextlib.suppress(EncryptedCollectionError): |
| 458 | + encrypted_client.create_encrypted_collection( |
| 459 | + encrypted_connection[self.connection.database.name], |
| 460 | + model._meta.db_table, |
| 461 | + model.encrypted_fields_map, |
| 462 | + "local", # TODO: KMS provider should be configurable |
| 463 | + ) |
| 464 | + else: |
| 465 | + self.get_database().create_collection(model._meta.db_table) |
0 commit comments