Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ functions:
MONGODB_VERSION=${MONGODB_VERSION} \
TOPOLOGY=${TOPOLOGY} \
SSL=${SSL} \
AUTH=${AUTH} \
sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
- command: expansions.update
Expand All @@ -130,9 +131,10 @@ functions:
script: |
${PREPARE_SHELL}

MONGODB_URI=${MONGODB_URI} \
MONGODB_URI="${MONGODB_URI}" \
TOPOLOGY=${TOPOLOGY} \
SSL=${SSL} \
AUTH=${AUTH} \
SWIFT_VERSION=${SWIFT_VERSION} \
sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh

Expand Down Expand Up @@ -390,17 +392,19 @@ axes:
variables:
SWIFT_VERSION: "5.1.4"

- id: ssl
display_name: SSL
- id: ssl-auth
display_name: SSL and Auth
values:
- id: ssl
display_name: SSL
- id: ssl-auth
display_name: SSL Auth
variables:
SSL: "ssl"
- id: nossl
display_name: NoSSL
AUTH: "auth"
- id: nossl-noauth
display_name: NoSSL NoAuth
variables:
SSL: "nossl"
AUTH: "noauth"


buildvariants:
Expand All @@ -409,8 +413,8 @@ buildvariants:
matrix_spec:
os-fully-featured: "*"
swift-version: "*"
ssl: "*"
display_name: "${swift-version} ${os-fully-featured} ${ssl}"
ssl-auth: "*"
display_name: "${swift-version} ${os-fully-featured} ${ssl-auth}"
tasks:
- ".latest"
- ".4.2"
Expand All @@ -423,7 +427,7 @@ buildvariants:
- if:
os-fully-featured: "ubuntu-18.04"
swift-version: "*"
ssl: "ssl"
ssl-auth: "ssl-auth"
then:
remove_tasks: ".3.6"

Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ let package = Package(
.target(name: "MongoSwift", dependencies: ["CLibMongoC", "NIO", "NIOConcurrencyHelpers"]),
.target(name: "MongoSwiftSync", dependencies: ["MongoSwift"]),
.target(name: "AtlasConnectivity", dependencies: ["MongoSwiftSync"]),
.target(name: "TestsCommon", dependencies: ["MongoSwift", "Nimble", "CLibMongoC"]),
.target(name: "TestsCommon", dependencies: ["MongoSwift", "Nimble"]),
.testTarget(name: "BSONTests", dependencies: ["MongoSwift", "TestsCommon", "Nimble", "CLibMongoC"]),
.testTarget(name: "MongoSwiftTests", dependencies: ["MongoSwift", "TestsCommon", "Nimble", "NIO", "CLibMongoC"]),
.testTarget(name: "MongoSwiftSyncTests", dependencies: ["MongoSwiftSync", "TestsCommon", "Nimble", "CLibMongoC"]),
.testTarget(name: "MongoSwiftSyncTests", dependencies: ["MongoSwiftSync", "TestsCommon", "Nimble", "MongoSwift"]),
.target(
name: "CLibMongoC",
dependencies: [],
Expand Down
32 changes: 32 additions & 0 deletions Sources/MongoSwift/ConnectionPool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ internal class ConnectionPool {
throw InternalError(message: "ConnectionPool was already closed")
}
}

/// Selects a server according to the specified parameters and returns a description of a suitable server to use.
/// Throws an error if a server cannot be selected. This method will start up SDAM in libmongoc if it hasn't been
/// started already. This method may block.
internal func selectServer(forWrites: Bool, readPreference: ReadPreference? = nil) throws -> ServerDescription {
return try self.withConnection { conn in
var error = bson_error_t()
guard let desc = mongoc_client_select_server(
conn.clientHandle,
forWrites,
readPreference?._readPreference,
&error
) else {
throw extractMongoError(error: error)
}

defer { mongoc_server_description_destroy(desc) }
return ServerDescription(desc)
}
}

/// Retrieves the connection string used to create this pool. If SDAM has been started in libmongoc, the getters
/// on the returned connection string will return any values that were retrieved from TXT records. Throws an error
/// if the connection string cannot be retrieved.
internal func getConnectionString() throws -> ConnectionString {
return try self.withConnection { conn in
guard let uri = mongoc_client_get_uri(conn.clientHandle) else {
throw InternalError(message: "Couldn't retrieve client's connection string")
}
return ConnectionString(copying: uri)
}
}
}

extension String {
Expand Down
117 changes: 117 additions & 0 deletions Sources/MongoSwift/ConnectionString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ internal class ConnectionString {
}
}

/// Initializes a new connection string that wraps a copy of the provided URI. Does not destroy the input URI.
internal init(copying uri: OpaquePointer) {
self._uri = mongoc_uri_copy(uri)
}

/// Cleans up the underlying `mongoc_uri_t`.
deinit {
mongoc_uri_destroy(self._uri)
Expand Down Expand Up @@ -72,4 +77,116 @@ internal class ConnectionString {
mongoc_uri_set_read_prefs_t(self._uri, rp._readPreference)
}
}

/// Returns the username if one was provided, otherwise nil.
internal var username: String? {
guard let username = mongoc_uri_get_username(self._uri) else {
return nil
}
return String(cString: username)
}

/// Returns the password if one was provided, otherwise nil.
internal var password: String? {
guard let pw = mongoc_uri_get_password(self._uri) else {
return nil
}
return String(cString: pw)
}

/// Returns the auth database if one was provided, otherwise nil.
internal var authSource: String? {
guard let source = mongoc_uri_get_auth_source(self._uri) else {
return nil
}
return String(cString: source)
}

/// Returns the auth mechanism if one was provided, otherwise nil.
internal var authMechanism: AuthMechanism? {
guard let mechanism = mongoc_uri_get_auth_mechanism(self._uri) else {
return nil
}
let str = String(cString: mechanism)
return AuthMechanism(rawValue: str)
}

/// Returns a document containing the auth mechanism properties if any were provided, otherwise nil.
internal var authMechanismProperties: Document? {
var props = bson_t()
return withUnsafeMutablePointer(to: &props) { propsPtr in
let opaquePtr = OpaquePointer(propsPtr)
guard mongoc_uri_get_mechanism_properties(self._uri, opaquePtr) else {
return nil
}
/// This copy should not be returned directly as its only guaranteed valid for as long as the
/// `mongoc_uri_t`, as `props` was statically initialized from data stored in the URI and may contain
/// pointers that will be invalidated once the URI is.
let copy = Document(copying: opaquePtr)

return copy.mapValues { value in
// mongoc returns boolean options e.g. CANONICALIZE_HOSTNAME as strings, but they are boolean values.
switch value {
case "true":
return true
case "false":
return false
default:
return value
}
}
}
}

/// Returns the credential configured on this URI. Will be empty if no options are set.
internal var credential: Credential {
return Credential(
username: self.username,
password: self.password,
source: self.authSource,
mechanism: self.authMechanism,
mechanismProperties: self.authMechanismProperties
)
}

internal var db: String? {
guard let db = mongoc_uri_get_database(self._uri) else {
return nil
}
return String(cString: db)
}

/// Returns a document containing all of the options provided after the ? of the URI.
internal var options: Document? {
guard let optsDoc = mongoc_uri_get_options(self._uri) else {
return nil
}
return Document(copying: optsDoc)
}

/// Returns the host/port pairs specified in the connection string, or nil if this connection string's scheme is
/// “mongodb+srv://”.
internal var hosts: [String]? {
guard let hostList = mongoc_uri_get_hosts(self._uri) else {
return nil
}

var hosts = [String]()
var next = hostList.pointee
while true {
hosts.append(withUnsafeBytes(of: next.host_and_port) { rawPtr in
guard let baseAddress = rawPtr.baseAddress else {
return ""
}
return String(cString: baseAddress.assumingMemoryBound(to: CChar.self))
})

if next.next == nil {
break
}
next = next.next.pointee
}

return hosts
}
}
63 changes: 63 additions & 0 deletions Sources/MongoSwift/Credential.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// Represents an authentication credential.
internal struct Credential: Decodable, Equatable {
/// A string containing the username. For auth mechanisms that do not utilize a password, this may be the entire
/// `userinfo` token from the connection string.
internal let username: String?
/// A string containing the password.
internal let password: String?
/// A string containing the authentication database.
internal let source: String?
/// The authentication mechanism. A nil value for this property indicates that a mechanism wasn't specified and
/// that mechanism negotiation is required.
internal let mechanism: AuthMechanism?
/// A document containing mechanism-specific properties.
internal let mechanismProperties: Document?

private enum CodingKeys: String, CodingKey {
case username, password, source, mechanism, mechanismProperties = "mechanism_properties"
}

// TODO: SWIFT-636: remove this initializer and the one below it.
internal init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.username = try container.decodeIfPresent(String.self, forKey: .username)
self.password = try container.decodeIfPresent(String.self, forKey: .password)
self.source = try container.decodeIfPresent(String.self, forKey: .source)
self.mechanism = try container.decodeIfPresent(AuthMechanism.self, forKey: .mechanism)

// libmongoc does not return the service name if it's the default, but it is contained in the spec test files,
// so filter it out here if it's present.
let properties = try container.decodeIfPresent(Document.self, forKey: .mechanismProperties)
let filteredProperties = properties?.filter { !($0.0 == "SERVICE_NAME" && $0.1 == "mongodb") }
// if SERVICE_NAME was the only key then don't return an empty document.
if filteredProperties?.isEmpty == true {
self.mechanismProperties = nil
} else {
self.mechanismProperties = filteredProperties
}
}

internal init(
username: String?,
password: String?,
source: String?,
mechanism: AuthMechanism?,
mechanismProperties: Document?
) {
self.mechanism = mechanism
self.mechanismProperties = mechanismProperties
self.password = password
self.source = source
self.username = username
}
}

/// Possible authentication mechanisms.
internal enum AuthMechanism: String, Decodable {
case scramSHA1 = "SCRAM-SHA-1"
case scramSHA256 = "SCRAM-SHA-256"
case gssAPI = "GSSAPI"
case mongodbCR = "MONGODB-CR"
case mongodbX509 = "MONGODB-X509"
case plain = "PLAIN"
}
Loading