Esempio n. 1
0
    def execute(self, write_concern):
        """Execute operations.
        """
        if not self.ops:
            raise InvalidOperation('No operations to execute')
        if self.executed:
            raise InvalidOperation('Bulk operations can '
                                   'only be executed once.')
        self.executed = True
        write_concern = (WriteConcern(**write_concern)
                         if write_concern else self.collection.write_concern)

        if self.ordered:
            generator = self.gen_ordered()
        else:
            generator = self.gen_unordered()

        client = self.collection.database.client
        with client._socket_for_writes() as sock_info:
            if sock_info.max_wire_version < 5 and self.uses_collation:
                raise ConfigurationError(
                    'Must be connected to MongoDB 3.4+ to use a collation.')
            if not write_concern.acknowledged:
                if self.uses_collation:
                    raise ConfigurationError(
                        'Collation is unsupported for unacknowledged writes.')
                self.execute_no_results(sock_info, generator)
            elif sock_info.max_wire_version > 1:
                return self.execute_command(sock_info, generator,
                                            write_concern)
            else:
                return self.execute_legacy(sock_info, generator, write_concern)
    def __init__(self, w=None, wtimeout=None, j=None, fsync=None):
        self.__document = {}
        self.__acknowledged = True

        if wtimeout is not None:
            if not isinstance(wtimeout, integer_types):
                raise TypeError("wtimeout must be an integer")
            self.__document["wtimeout"] = wtimeout

        if j is not None:
            if not isinstance(j, bool):
                raise TypeError("j must be True or False")
            self.__document["j"] = j

        if fsync is not None:
            if not isinstance(fsync, bool):
                raise TypeError("fsync must be True or False")
            if j and fsync:
                raise ConfigurationError("Can't set both j "
                                         "and fsync at the same time")
            self.__document["fsync"] = fsync

        if self.__document and w == 0:
            raise ConfigurationError("Can not use w value "
                                     "of 0 with other options")
        if w is not None:
            if isinstance(w, integer_types):
                self.__acknowledged = w > 0
            elif not isinstance(w, string_type):
                raise TypeError("w must be an integer or string")
            self.__document["w"] = w
def make_read_preference(mode, tag_sets, max_staleness=-1):
    if mode == _PRIMARY:
        if tag_sets not in (None, [{}]):
            raise ConfigurationError("Read preference primary "
                                     "cannot be combined with tags")
        if max_staleness != -1:
            raise ConfigurationError("Read preference primary cannot be "
                                     "combined with maxStalenessSeconds")
        return Primary()
    return _ALL_READ_PREFERENCES[mode](tag_sets, max_staleness)
Esempio n. 4
0
    def __init__(self, database, collection="fs"):
        """Create a new instance of :class:`GridFS`.

        Raises :class:`TypeError` if `database` is not an instance of
        :class:`~pymongo.database.Database`.

        :Parameters:
          - `database`: database to use
          - `collection` (optional): root collection to use

        .. versionchanged:: 3.1
           Indexes are only ensured on the first write to the DB.

        .. versionchanged:: 3.0
           `database` must use an acknowledged
           :attr:`~pymongo.database.Database.write_concern`

        .. mongodoc:: gridfs
        """
        if not isinstance(database, Database):
            raise TypeError("database must be an instance of Database")

        if not database.write_concern.acknowledged:
            raise ConfigurationError('database must use '
                                     'acknowledged write_concern')

        self.__database = database
        self.__collection = database[collection]
        self.__files = self.__collection.files
        self.__chunks = self.__collection.chunks
Esempio n. 5
0
    def __init__(self,
                 seeds=None,
                 replica_set_name=None,
                 pool_class=None,
                 pool_options=None,
                 monitor_class=None,
                 condition_class=None,
                 local_threshold_ms=LOCAL_THRESHOLD_MS,
                 server_selection_timeout=SERVER_SELECTION_TIMEOUT,
                 heartbeat_frequency=common.HEARTBEAT_FREQUENCY):
        """Represent MongoClient's configuration.

        Take a list of (host, port) pairs and optional replica set name.
        """
        if heartbeat_frequency < common.MIN_HEARTBEAT_INTERVAL:
            raise ConfigurationError(
                "heartbeatFrequencyMS cannot be less than %d" %
                common.MIN_HEARTBEAT_INTERVAL * 1000)

        self._seeds = seeds or [('localhost', 27017)]
        self._replica_set_name = replica_set_name
        self._pool_class = pool_class or pool.Pool
        self._pool_options = pool_options or PoolOptions()
        self._monitor_class = monitor_class or monitor.Monitor
        self._condition_class = condition_class or threading.Condition
        self._local_threshold_ms = local_threshold_ms
        self._server_selection_timeout = server_selection_timeout
        self._heartbeat_frequency = heartbeat_frequency
        self._direct = (len(self._seeds) == 1 and not replica_set_name)
        self._topology_id = ObjectId()
Esempio n. 6
0
def _parse_ssl_options(options):
    """Parse ssl options."""
    use_ssl = options.get('ssl')
    if use_ssl is not None:
        validate_boolean('ssl', use_ssl)

    certfile = options.get('ssl_certfile')
    keyfile = options.get('ssl_keyfile')
    passphrase = options.get('ssl_pem_passphrase')
    ca_certs = options.get('ssl_ca_certs')
    cert_reqs = options.get('ssl_cert_reqs')
    match_hostname = options.get('ssl_match_hostname', True)
    crlfile = options.get('ssl_crlfile')

    ssl_kwarg_keys = [k for k in options
                      if k.startswith('ssl_') and options[k]]
    if use_ssl == False and ssl_kwarg_keys:
        raise ConfigurationError("ssl has not been enabled but the "
                                 "following ssl parameters have been set: "
                                 "%s. Please set `ssl=True` or remove."
                                 % ', '.join(ssl_kwarg_keys))

    if ssl_kwarg_keys and use_ssl is None:
        # ssl options imply ssl = True
        use_ssl = True

    if use_ssl is True:
        ctx = get_ssl_context(
            certfile, keyfile, passphrase, ca_certs, cert_reqs, crlfile)
        return ctx, match_hostname
    return None, match_hostname
Esempio n. 7
0
    def add_user(self, name, password=None, read_only=None, **kwargs):
        """Create user `name` with password `password`.

        Add a new user with permissions for this :class:`Database`.

        .. note:: Will change the password if user `name` already exists.

        :Parameters:
          - `name`: the name of the user to create
          - `password` (optional): the password of the user to create. Can not
            be used with the ``userSource`` argument.
          - `read_only` (optional): if ``True`` the user will be read only
          - `**kwargs` (optional): optional fields for the user document
            (e.g. ``userSource``, ``otherDBRoles``, or ``roles``). See
            `<http://docs.mongodb.org/manual/reference/privilege-documents>`_
            for more information.

        .. note:: The use of optional keyword arguments like ``userSource``,
           ``otherDBRoles``, or ``roles`` requires MongoDB >= 2.4.0

        .. versionchanged:: 2.5
           Added kwargs support for optional fields introduced in MongoDB 2.4

        .. versionchanged:: 2.2
           Added support for read only users
        """
        if not isinstance(name, string_type):
            raise TypeError("name must be an "
                            "instance of %s" % (string_type.__name__, ))
        if password is not None:
            if not isinstance(password, string_type):
                raise TypeError("password must be an "
                                "instance of %s" % (string_type.__name__, ))
            if len(password) == 0:
                raise ValueError("password can't be empty")
        if read_only is not None:
            read_only = common.validate_boolean('read_only', read_only)
            if 'roles' in kwargs:
                raise ConfigurationError("Can not use "
                                         "read_only and roles together")

        try:
            uinfo = self.command("usersInfo", name)
            # Create the user if not found in uinfo, otherwise update one.
            self._create_or_update_user((not uinfo["users"]), name, password,
                                        read_only, **kwargs)
        except OperationFailure as exc:
            # MongoDB >= 2.5.3 requires the use of commands to manage
            # users.
            if exc.code in common.COMMAND_NOT_FOUND_CODES:
                self._legacy_add_user(name, password, read_only, **kwargs)
                return
            # Unauthorized. Attempt to create the user in case of
            # localhost exception.
            elif exc.code == 13:
                self._create_or_update_user(True, name, password, read_only,
                                            **kwargs)
            else:
                raise
Esempio n. 8
0
def validate_auth_option(option, value):
    """Validate optional authentication parameters.
    """
    lower, value = validate(option, value)
    if lower not in _AUTH_OPTIONS:
        raise ConfigurationError('Unknown '
                                 'authentication option: %s' % (option, ))
    return lower, value
Esempio n. 9
0
def _authenticate_x509(credentials, sock_info):
    """Authenticate using MONGODB-X509.
    """
    query = SON([('authenticate', 1), ('mechanism', 'MONGODB-X509')])
    if credentials.username is not None:
        query['user'] = credentials.username
    elif sock_info.max_wire_version < 5:
        raise ConfigurationError(
            "A username is required for MONGODB-X509 authentication "
            "when connected to MongoDB versions older than 3.4.")
    sock_info.command('$external', query)
Esempio n. 10
0
def validate_timeout_or_zero(option, value):
    """Validates a timeout specified in milliseconds returning
    a value in floating point seconds for the case where None is an error
    and 0 is valid. Setting the timeout to nothing in the URI string is a
    config error.
    """
    if value is None:
        raise ConfigurationError("%s cannot be None" % (option, ))
    if value == 0 or value == "0":
        return 0
    return validate_positive_float(option, value) / 1000.0
Esempio n. 11
0
 def __new__(cls,
             strict_number_long=False,
             datetime_representation=DatetimeRepresentation.LEGACY,
             strict_uuid=False,
             json_mode=JSONMode.LEGACY,
             *args,
             **kwargs):
     kwargs["tz_aware"] = kwargs.get("tz_aware", True)
     if kwargs["tz_aware"]:
         kwargs["tzinfo"] = kwargs.get("tzinfo", utc)
     if datetime_representation not in (DatetimeRepresentation.LEGACY,
                                        DatetimeRepresentation.NUMBERLONG,
                                        DatetimeRepresentation.ISO8601):
         raise ConfigurationError(
             "JSONOptions.datetime_representation must be one of LEGACY, "
             "NUMBERLONG, or ISO8601 from DatetimeRepresentation.")
     self = super(JSONOptions, cls).__new__(cls, *args, **kwargs)
     if not _HAS_OBJECT_PAIRS_HOOK and self.document_class != dict:
         raise ConfigurationError(
             "Support for JSONOptions.document_class on Python 2.6 "
             "requires simplejson "
             "(https://pypi.python.org/pypi/simplejson) to be installed.")
     if json_mode not in (JSONMode.LEGACY, JSONMode.RELAXED,
                          JSONMode.CANONICAL):
         raise ConfigurationError(
             "JSONOptions.json_mode must be one of LEGACY, RELAXED, "
             "or CANONICAL from JSONMode.")
     self.json_mode = json_mode
     if self.json_mode == JSONMode.RELAXED:
         self.strict_number_long = False
         self.datetime_representation = DatetimeRepresentation.ISO8601
         self.strict_uuid = True
     elif self.json_mode == JSONMode.CANONICAL:
         self.strict_number_long = True
         self.datetime_representation = DatetimeRepresentation.NUMBERLONG
         self.strict_uuid = True
     else:
         self.strict_number_long = strict_number_long
         self.datetime_representation = datetime_representation
         self.strict_uuid = strict_uuid
     return self
Esempio n. 12
0
    def __init__(self,
                 db,
                 bucket_name="fs",
                 chunk_size_bytes=DEFAULT_CHUNK_SIZE,
                 write_concern=None,
                 read_preference=None):
        """Create a new instance of :class:`GridFSBucket`.

        Raises :exc:`TypeError` if `database` is not an instance of
        :class:`~pymongo.database.Database`.

        Raises :exc:`~pymongo.errors.ConfigurationError` if `write_concern`
        is not acknowledged.

        :Parameters:
          - `database`: database to use.
          - `bucket_name` (optional): The name of the bucket. Defaults to 'fs'.
          - `chunk_size_bytes` (optional): The chunk size in bytes. Defaults
            to 255KB.
          - `write_concern` (optional): The
            :class:`~pymongo.write_concern.WriteConcern` to use. If ``None``
            (the default) db.write_concern is used.
          - `read_preference` (optional): The read preference to use. If
            ``None`` (the default) db.read_preference is used.

        .. versionadded:: 3.1

        .. mongodoc:: gridfs
        """
        if not isinstance(db, Database):
            raise TypeError("database must be an instance of Database")

        wtc = write_concern if write_concern is not None else db.write_concern
        if not wtc.acknowledged:
            raise ConfigurationError('write concern must be acknowledged')

        self._db = db
        self._bucket_name = bucket_name
        self._collection = db[bucket_name]

        self._chunks = self._collection.chunks.with_options(
            write_concern=write_concern, read_preference=read_preference)

        self._files = self._collection.files.with_options(
            write_concern=write_concern, read_preference=read_preference)

        self._chunk_size_bytes = chunk_size_bytes
Esempio n. 13
0
    def _create_or_update_user(self, create, name, password, read_only,
                               **kwargs):
        """Use a command to create (if create=True) or modify a user.
        """
        opts = {}
        if read_only or (create and "roles" not in kwargs):
            warnings.warn(
                "Creating a user with the read_only option "
                "or without roles is deprecated in MongoDB "
                ">= 2.6", DeprecationWarning)

            opts["roles"] = [self._default_role(read_only)]

        elif read_only:
            warnings.warn(
                "The read_only option is deprecated in MongoDB "
                ">= 2.6, use 'roles' instead", DeprecationWarning)

        if password is not None:
            # We always salt and hash client side.
            if "digestPassword" in kwargs:
                raise ConfigurationError("The digestPassword option is not "
                                         "supported via add_user. Please use "
                                         "db.command('createUser', ...) "
                                         "instead for this option.")
            opts["pwd"] = auth._password_digest(name, password)
            opts["digestPassword"] = False

        # Don't send {} as writeConcern.
        if self.write_concern.acknowledged and self.write_concern.document:
            opts["writeConcern"] = self.write_concern.document
        opts.update(kwargs)

        if create:
            command_name = "createUser"
        else:
            command_name = "updateUser"

        self.command(command_name, name, **opts)
Esempio n. 14
0
def _build_credentials_tuple(mech, source, user, passwd, extra):
    """Build and return a mechanism specific credentials tuple.
    """
    user = _unicode(user) if user is not None else None
    password = passwd if passwd is None else _unicode(passwd)
    if mech == 'GSSAPI':
        properties = extra.get('authmechanismproperties', {})
        service_name = properties.get('SERVICE_NAME', 'mongodb')
        canonicalize = properties.get('CANONICALIZE_HOST_NAME', False)
        service_realm = properties.get('SERVICE_REALM')
        props = GSSAPIProperties(service_name=service_name,
                                 canonicalize_host_name=canonicalize,
                                 service_realm=service_realm)
        # Source is always $external.
        return MongoCredential(mech, '$external', user, password, props)
    elif mech == 'MONGODB-X509':
        # user can be None.
        return MongoCredential(mech, '$external', user, None, None)
    else:
        if passwd is None:
            raise ConfigurationError("A password is required.")
        return MongoCredential(mech, source, user, password, None)
Esempio n. 15
0
 def validate_cert_reqs(option, dummy):
     """No ssl module, raise ConfigurationError."""
     raise ConfigurationError("The value of %s is set but can't be "
                              "validated. The ssl module is not available"
                              % (option,))
Esempio n. 16
0
    def command(self,
                dbname,
                spec,
                slave_ok=False,
                read_preference=ReadPreference.PRIMARY,
                codec_options=DEFAULT_CODEC_OPTIONS,
                check=True,
                allowable_errors=None,
                check_keys=False,
                read_concern=DEFAULT_READ_CONCERN,
                write_concern=None,
                parse_write_concern_error=False,
                collation=None):
        """Execute a command or raise ConnectionFailure or OperationFailure.

        :Parameters:
          - `dbname`: name of the database on which to run the command
          - `spec`: a command document as a dict, SON, or mapping object
          - `slave_ok`: whether to set the SlaveOkay wire protocol bit
          - `read_preference`: a read preference
          - `codec_options`: a CodecOptions instance
          - `check`: raise OperationFailure if there are errors
          - `allowable_errors`: errors to ignore if `check` is True
          - `check_keys`: if True, check `spec` for invalid keys
          - `read_concern`: The read concern for this command.
          - `write_concern`: The write concern for this command.
          - `parse_write_concern_error`: Whether to parse the
            ``writeConcernError`` field in the command response.
          - `collation`: The collation for this command.
        """
        if self.max_wire_version < 4 and not read_concern.ok_for_legacy:
            raise ConfigurationError(
                'read concern level of %s is not valid '
                'with a max wire version of %d.' %
                (read_concern.level, self.max_wire_version))
        if not (write_concern is None or write_concern.acknowledged
                or collation is None):
            raise ConfigurationError(
                'Collation is unsupported for unacknowledged writes.')
        if self.max_wire_version >= 5 and write_concern:
            spec['writeConcern'] = write_concern.document
        elif self.max_wire_version < 5 and collation is not None:
            raise ConfigurationError(
                'Must be connected to MongoDB 3.4+ to use a collation.')
        try:
            return command(self.sock,
                           dbname,
                           spec,
                           slave_ok,
                           self.is_mongos,
                           read_preference,
                           codec_options,
                           check,
                           allowable_errors,
                           self.address,
                           check_keys,
                           self.listeners,
                           self.max_bson_size,
                           read_concern,
                           parse_write_concern_error=parse_write_concern_error,
                           collation=collation)
        except OperationFailure:
            raise
        # Catch socket.error, KeyboardInterrupt, etc. and close ourselves.
        except BaseException as error:
            self._raise_connection_failure(error)
Esempio n. 17
0
def _authenticate_gssapi(credentials, sock_info):
    """Authenticate using GSSAPI.
    """
    if not HAVE_KERBEROS:
        raise ConfigurationError('The "kerberos" module must be '
                                 'installed to use GSSAPI authentication.')

    try:
        username = credentials.username
        password = credentials.password
        props = credentials.mechanism_properties
        # Starting here and continuing through the while loop below - establish
        # the security context. See RFC 4752, Section 3.1, first paragraph.
        host = sock_info.address[0]
        if props.canonicalize_host_name:
            host = socket.getfqdn(host)
        service = props.service_name + '@' + host
        if props.service_realm is not None:
            service = service + '@' + props.service_realm

        if password is not None:
            if _USE_PRINCIPAL:
                # Note that, though we use unquote_plus for unquoting URI
                # options, we use quote here. Microsoft's UrlUnescape (used
                # by WinKerberos) doesn't support +.
                principal = ":".join((quote(username), quote(password)))
                result, ctx = kerberos.authGSSClientInit(
                    service, principal, gssflags=kerberos.GSS_C_MUTUAL_FLAG)
            else:
                if '@' in username:
                    user, domain = username.split('@', 1)
                else:
                    user, domain = username, None
                result, ctx = kerberos.authGSSClientInit(
                    service,
                    gssflags=kerberos.GSS_C_MUTUAL_FLAG,
                    user=user,
                    domain=domain,
                    password=password)
        else:
            result, ctx = kerberos.authGSSClientInit(
                service, gssflags=kerberos.GSS_C_MUTUAL_FLAG)

        if result != kerberos.AUTH_GSS_COMPLETE:
            raise OperationFailure('Kerberos context failed to initialize.')

        try:
            # pykerberos uses a weird mix of exceptions and return values
            # to indicate errors.
            # 0 == continue, 1 == complete, -1 == error
            # Only authGSSClientStep can return 0.
            if kerberos.authGSSClientStep(ctx, '') != 0:
                raise OperationFailure('Unknown kerberos '
                                       'failure in step function.')

            # Start a SASL conversation with mongod/s
            # Note: pykerberos deals with base64 encoded byte strings.
            # Since mongo accepts base64 strings as the payload we don't
            # have to use bson.binary.Binary.
            payload = kerberos.authGSSClientResponse(ctx)
            cmd = SON([('saslStart', 1), ('mechanism', 'GSSAPI'),
                       ('payload', payload), ('autoAuthorize', 1)])
            response = sock_info.command('$external', cmd)

            # Limit how many times we loop to catch protocol / library issues
            for _ in range(10):
                result = kerberos.authGSSClientStep(ctx,
                                                    str(response['payload']))
                if result == -1:
                    raise OperationFailure('Unknown kerberos '
                                           'failure in step function.')

                payload = kerberos.authGSSClientResponse(ctx) or ''

                cmd = SON([('saslContinue', 1),
                           ('conversationId', response['conversationId']),
                           ('payload', payload)])
                response = sock_info.command('$external', cmd)

                if result == kerberos.AUTH_GSS_COMPLETE:
                    break
            else:
                raise OperationFailure('Kerberos '
                                       'authentication failed to complete.')

            # Once the security context is established actually authenticate.
            # See RFC 4752, Section 3.1, last two paragraphs.
            if kerberos.authGSSClientUnwrap(ctx, str(
                    response['payload'])) != 1:
                raise OperationFailure('Unknown kerberos '
                                       'failure during GSS_Unwrap step.')

            if kerberos.authGSSClientWrap(
                    ctx, kerberos.authGSSClientResponse(ctx), username) != 1:
                raise OperationFailure('Unknown kerberos '
                                       'failure during GSS_Wrap step.')

            payload = kerberos.authGSSClientResponse(ctx)
            cmd = SON([('saslContinue', 1),
                       ('conversationId', response['conversationId']),
                       ('payload', payload)])
            sock_info.command('$external', cmd)

        finally:
            kerberos.authGSSClientClean(ctx)

    except kerberos.KrbError as exc:
        raise OperationFailure(str(exc))
Esempio n. 18
0
 def get_ssl_context(*dummy):
     """No ssl module, raise ConfigurationError."""
     raise ConfigurationError("The ssl module is not available.")
Esempio n. 19
0
    def __init__(self, root_collection, **kwargs):
        """Write a file to GridFS

        Application developers should generally not need to
        instantiate this class directly - instead see the methods
        provided by :class:`~gridfs.GridFS`.

        Raises :class:`TypeError` if `root_collection` is not an
        instance of :class:`~pymongo.collection.Collection`.

        Any of the file level options specified in the `GridFS Spec
        <http://dochub.mongodb.org/core/gridfsspec>`_ may be passed as
        keyword arguments. Any additional keyword arguments will be
        set as additional fields on the file document. Valid keyword
        arguments include:

          - ``"_id"``: unique ID for this file (default:
            :class:`~bson.objectid.ObjectId`) - this ``"_id"`` must
            not have already been used for another file

          - ``"filename"``: human name for the file

          - ``"contentType"`` or ``"content_type"``: valid mime-type
            for the file

          - ``"chunkSize"`` or ``"chunk_size"``: size of each of the
            chunks, in bytes (default: 255 kb)

          - ``"encoding"``: encoding used for this file. In Python 2,
            any :class:`unicode` that is written to the file will be
            converted to a :class:`str`. In Python 3, any :class:`str`
            that is written to the file will be converted to
            :class:`bytes`.

        :Parameters:
          - `root_collection`: root collection to write to
          - `**kwargs` (optional): file level options (see above)

        .. versionchanged:: 3.0
           `root_collection` must use an acknowledged
           :attr:`~pymongo.collection.Collection.write_concern`
        """
        if not isinstance(root_collection, Collection):
            raise TypeError("root_collection must be an "
                            "instance of Collection")

        # With w=0, 'filemd5' might run before the final chunks are written.
        if not root_collection.write_concern.acknowledged:
            raise ConfigurationError('root_collection must use '
                                     'acknowledged write_concern')

        # Handle alternative naming
        if "content_type" in kwargs:
            kwargs["contentType"] = kwargs.pop("content_type")
        if "chunk_size" in kwargs:
            kwargs["chunkSize"] = kwargs.pop("chunk_size")

        coll = root_collection.with_options(
            read_preference=ReadPreference.PRIMARY)

        kwargs['md5'] = md5()
        # Defaults
        kwargs["_id"] = kwargs.get("_id", ObjectId())
        kwargs["chunkSize"] = kwargs.get("chunkSize", DEFAULT_CHUNK_SIZE)
        object.__setattr__(self, "_coll", coll)
        object.__setattr__(self, "_chunks", coll.chunks)
        object.__setattr__(self, "_file", kwargs)
        object.__setattr__(self, "_buffer", StringIO())
        object.__setattr__(self, "_position", 0)
        object.__setattr__(self, "_chunk_number", 0)
        object.__setattr__(self, "_closed", False)
        object.__setattr__(self, "_ensured_index", False)
Esempio n. 20
0
def raise_config_error(key, dummy):
    """Raise ConfigurationError with the given key name."""
    raise ConfigurationError("Unknown option %s" % (key, ))
Esempio n. 21
0
    def send_message_with_response(self,
                                   operation,
                                   set_slave_okay,
                                   all_credentials,
                                   listeners,
                                   exhaust=False):
        """Send a message to MongoDB and return a Response object.

        Can raise ConnectionFailure.

        :Parameters:
          - `operation`: A _Query or _GetMore object.
          - `set_slave_okay`: Pass to operation.get_message.
          - `all_credentials`: dict, maps auth source to MongoCredential.
          - `listeners`: Instance of _EventListeners or None.
          - `exhaust` (optional): If True, the socket used stays checked out.
            It is returned along with its Pool in the Response.
        """
        with self.get_socket(all_credentials, exhaust) as sock_info:

            duration = None
            publish = listeners.enabled_for_commands
            if publish:
                start = datetime.now()

            use_find_cmd = False
            if sock_info.max_wire_version >= 4:
                if not exhaust:
                    use_find_cmd = True
            elif (isinstance(operation, _Query)
                  and not operation.read_concern.ok_for_legacy):
                raise ConfigurationError(
                    'read concern level of %s is not valid '
                    'with a max wire version of %d.' %
                    (operation.read_concern.level, sock_info.max_wire_version))
            if (isinstance(operation, _Query)
                    and sock_info.max_wire_version < 5
                    and operation.collation is not None):
                raise ConfigurationError(
                    'Specifying a collation is unsupported with a max wire '
                    'version of %d.' % (sock_info.max_wire_version, ))
            message = operation.get_message(set_slave_okay,
                                            sock_info.is_mongos, use_find_cmd)
            request_id, data, max_doc_size = self._split_message(message)

            if publish:
                encoding_duration = datetime.now() - start
                cmd, dbn = operation.as_command()
                listeners.publish_command_start(cmd, dbn, request_id,
                                                sock_info.address)
                start = datetime.now()

            try:
                sock_info.send_message(data, max_doc_size)
                response_data = sock_info.receive_message(1, request_id)
            except Exception as exc:
                if publish:
                    duration = (datetime.now() - start) + encoding_duration
                    failure = _convert_exception(exc)
                    listeners.publish_command_failure(duration, failure,
                                                      next(iter(cmd)),
                                                      request_id,
                                                      sock_info.address)
                raise

            if publish:
                duration = (datetime.now() - start) + encoding_duration

            if exhaust:
                return ExhaustResponse(data=response_data,
                                       address=self._description.address,
                                       socket_info=sock_info,
                                       pool=self._pool,
                                       duration=duration,
                                       request_id=request_id,
                                       from_command=use_find_cmd)
            else:
                return Response(data=response_data,
                                address=self._description.address,
                                duration=duration,
                                request_id=request_id,
                                from_command=use_find_cmd)
Esempio n. 22
0
 def get_ssl_context(*args):
     """Create and return an SSLContext object."""
     certfile, keyfile, passphrase, ca_certs, cert_reqs, crlfile = args
     # Note PROTOCOL_SSLv23 is about the most misleading name imaginable.
     # This configures the server and client to negotiate the
     # highest protocol version they both support. A very good thing.
     # PROTOCOL_TLS_CLIENT was added in CPython 3.6, deprecating
     # PROTOCOL_SSLv23.
     ctx = SSLContext(
         getattr(ssl, "PROTOCOL_TLS_CLIENT", ssl.PROTOCOL_SSLv23))
     # SSLContext.check_hostname was added in CPython 2.7.9 and 3.4.
     # PROTOCOL_TLS_CLIENT enables it by default. Using it
     # requires passing server_hostname to wrap_socket, which we already
     # do for SNI support. To support older versions of Python we have to
     # call match_hostname directly, so we disable check_hostname explicitly
     # to avoid calling match_hostname twice.
     if hasattr(ctx, "check_hostname"):
         ctx.check_hostname = False
     if hasattr(ctx, "options"):
         # Explicitly disable SSLv2, SSLv3 and TLS compression. Note that
         # up to date versions of MongoDB 2.4 and above already disable
         # SSLv2 and SSLv3, python disables SSLv2 by default in >= 2.7.7
         # and >= 3.3.4 and SSLv3 in >= 3.4.3. There is no way for us to do
         # any of this explicitly for python 2.6 or 2.7 before 2.7.9.
         ctx.options |= getattr(ssl, "OP_NO_SSLv2", 0)
         ctx.options |= getattr(ssl, "OP_NO_SSLv3", 0)
         # OpenSSL >= 1.0.0
         ctx.options |= getattr(ssl, "OP_NO_COMPRESSION", 0)
     if certfile is not None:
         try:
             if passphrase is not None:
                 vi = sys.version_info
                 # Since python just added a new parameter to an existing method
                 # this seems to be about the best we can do.
                 if (vi[0] == 2 and vi < (2, 7, 9) or
                         vi[0] == 3 and vi < (3, 3)):
                     raise ConfigurationError(
                         "Support for ssl_pem_passphrase requires "
                         "python 2.7.9+ (pypy 2.5.1+) or 3.3+")
                 ctx.load_cert_chain(certfile, keyfile, passphrase)
             else:
                 ctx.load_cert_chain(certfile, keyfile)
         except ssl.SSLError as exc:
             raise ConfigurationError(
                 "Private key doesn't match certificate: %s" % (exc,))
     if crlfile is not None:
         if not hasattr(ctx, "verify_flags"):
             raise ConfigurationError(
                 "Support for ssl_crlfile requires "
                 "python 2.7.9+ (pypy 2.5.1+) or  3.4+")
         # Match the server's behavior.
         ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF
         ctx.load_verify_locations(crlfile)
     if ca_certs is not None:
         ctx.load_verify_locations(ca_certs)
     elif cert_reqs != ssl.CERT_NONE:
         # CPython >= 2.7.9 or >= 3.4.0, pypy >= 2.5.1
         if hasattr(ctx, "load_default_certs"):
             ctx.load_default_certs()
         # Python >= 3.2.0, useless on Windows.
         elif (sys.platform != "win32" and
               hasattr(ctx, "set_default_verify_paths")):
             ctx.set_default_verify_paths()
         elif sys.platform == "win32" and HAVE_WINCERTSTORE:
             with _WINCERTSLOCK:
                 if _WINCERTS is None:
                     _load_wincerts()
             ctx.load_verify_locations(_WINCERTS.name)
         elif HAVE_CERTIFI:
             ctx.load_verify_locations(certifi.where())
         else:
             raise ConfigurationError(
                 "`ssl_cert_reqs` is not ssl.CERT_NONE and no system "
                 "CA certificates could be loaded. `ssl_ca_certs` is "
                 "required.")
     ctx.verify_mode = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
     return ctx