|
18 | 18 | import enum |
19 | 19 | import socket |
20 | 20 | import weakref |
21 | | -from typing import Any, Generic, Mapping, Optional, Sequence |
| 21 | +from copy import deepcopy |
| 22 | +from typing import Any, Generic, Mapping, Optional, Sequence, Tuple |
22 | 23 |
|
23 | 24 | try: |
24 | 25 | from pymongocrypt.auto_encrypter import AutoEncrypter |
|
39 | 40 | from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument, _inflate_bson |
40 | 41 | from bson.son import SON |
41 | 42 | from pymongo import _csot |
| 43 | +from pymongo.collection import Collection |
42 | 44 | from pymongo.cursor import Cursor |
43 | 45 | from pymongo.daemon import _spawn_daemon |
| 46 | +from pymongo.database import Database |
44 | 47 | from pymongo.encryption_options import AutoEncryptionOpts, RangeOpts |
45 | 48 | from pymongo.errors import ( |
46 | 49 | ConfigurationError, |
@@ -552,6 +555,107 @@ def __init__( |
552 | 555 | # Use the same key vault collection as the callback. |
553 | 556 | self._key_vault_coll = self._io_callbacks.key_vault_coll |
554 | 557 |
|
| 558 | + def create_encrypted_collection( |
| 559 | + self, |
| 560 | + database: Database, |
| 561 | + name: str, |
| 562 | + encrypted_fields: Mapping[str, Any], |
| 563 | + kms_provider: Optional[str] = None, |
| 564 | + master_key: Optional[Mapping[str, Any]] = None, |
| 565 | + key_alt_names: Optional[Sequence[str]] = None, |
| 566 | + key_material: Optional[bytes] = None, |
| 567 | + **kwargs: Any, |
| 568 | + ) -> Tuple[Collection[_DocumentType], Mapping[str, Any]]: |
| 569 | + """Create a collection with encryptedFields. |
| 570 | +
|
| 571 | + .. warning:: |
| 572 | + This function does not update the encryptedFieldsMap in the client's |
| 573 | + AutoEncryptionOpts, thus the user must create a new client after calling this function with |
| 574 | + the encryptedFields returned. |
| 575 | +
|
| 576 | + Normally collection creation is automatic. This method should |
| 577 | + only be used to specify options on |
| 578 | + creation. :class:`~pymongo.errors.EncryptionError` will be |
| 579 | + raised if the collection already exists. |
| 580 | +
|
| 581 | + :Parameters: |
| 582 | + - `name`: the name of the collection to create |
| 583 | + - `encrypted_fields` (dict): **(BETA)** Document that describes the encrypted fields for |
| 584 | + Queryable Encryption. For example:: |
| 585 | +
|
| 586 | + { |
| 587 | + "escCollection": "enxcol_.encryptedCollection.esc", |
| 588 | + "eccCollection": "enxcol_.encryptedCollection.ecc", |
| 589 | + "ecocCollection": "enxcol_.encryptedCollection.ecoc", |
| 590 | + "fields": [ |
| 591 | + { |
| 592 | + "path": "firstName", |
| 593 | + "keyId": Binary.from_uuid(UUID('00000000-0000-0000-0000-000000000000')), |
| 594 | + "bsonType": "string", |
| 595 | + "queries": {"queryType": "equality"} |
| 596 | + }, |
| 597 | + { |
| 598 | + "path": "ssn", |
| 599 | + "keyId": Binary.from_uuid(UUID('04104104-1041-0410-4104-104104104104')), |
| 600 | + "bsonType": "string" |
| 601 | + } |
| 602 | + ] |
| 603 | + } |
| 604 | +
|
| 605 | + The "keyId" may be set to ``None`` to auto-generate the data keys. |
| 606 | + - `kms_provider` (optional): the KMS provider to be used |
| 607 | + - `master_key` (optional): Identifies a KMS-specific key used to encrypt the |
| 608 | + new data key. If the kmsProvider is "local" the `master_key` is |
| 609 | + not applicable and may be omitted. |
| 610 | + - `key_alt_names` (optional): An optional list of string alternate |
| 611 | + names used to reference a key. If a key is created with alternate |
| 612 | + names, then encryption may refer to the key by the unique alternate |
| 613 | + name instead of by ``key_id``. |
| 614 | + - `key_material` (optional): Sets the custom key material to be used |
| 615 | + by the data key for encryption and decryption. |
| 616 | + - `**kwargs` (optional): additional keyword arguments are the same as "create_collection". |
| 617 | +
|
| 618 | + All optional `create collection command`_ parameters should be passed |
| 619 | + as keyword arguments to this method. |
| 620 | + See the documentation for :meth:`~pymongo.database.Database.create_collection` for all valid options. |
| 621 | +
|
| 622 | + .. versionadded:: 4.4 |
| 623 | +
|
| 624 | + .. _create collection command: |
| 625 | + https://mongodb.com/docs/manual/reference/command/create |
| 626 | +
|
| 627 | + """ |
| 628 | + encrypted_fields = deepcopy(encrypted_fields) |
| 629 | + for i, field in enumerate(encrypted_fields["fields"]): |
| 630 | + if isinstance(field, dict) and field.get("keyId") is None: |
| 631 | + try: |
| 632 | + encrypted_fields["fields"][i]["keyId"] = self.create_data_key( |
| 633 | + kms_provider=kms_provider, # type:ignore[arg-type] |
| 634 | + master_key=master_key, |
| 635 | + key_alt_names=key_alt_names, |
| 636 | + key_material=key_material, |
| 637 | + ) |
| 638 | + except EncryptionError as exc: |
| 639 | + raise EncryptionError( |
| 640 | + Exception( |
| 641 | + "Error occurred while creating data key for field %s with encryptedFields=%s" |
| 642 | + % (field["path"], encrypted_fields) |
| 643 | + ) |
| 644 | + ) from exc |
| 645 | + kwargs["encryptedFields"] = encrypted_fields |
| 646 | + kwargs["check_exists"] = False |
| 647 | + try: |
| 648 | + return ( |
| 649 | + database.create_collection(name=name, **kwargs), |
| 650 | + encrypted_fields, |
| 651 | + ) |
| 652 | + except Exception as exc: |
| 653 | + raise EncryptionError( |
| 654 | + Exception( |
| 655 | + f"Error: {str(exc)} occurred while creating collection with encryptedFields={str(encrypted_fields)}" |
| 656 | + ) |
| 657 | + ) from exc |
| 658 | + |
555 | 659 | def create_data_key( |
556 | 660 | self, |
557 | 661 | kms_provider: str, |
|
0 commit comments