Skip to content

Commit fd30343

Browse files
authored
SWIFT-593 Add auth variants to Evergreen, enable auth DNS seedlist tests (#401)
1 parent 597197a commit fd30343

File tree

14 files changed

+354
-228
lines changed

14 files changed

+354
-228
lines changed

.evergreen/config.yml

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ functions:
104104
MONGODB_VERSION=${MONGODB_VERSION} \
105105
TOPOLOGY=${TOPOLOGY} \
106106
SSL=${SSL} \
107+
AUTH=${AUTH} \
107108
sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
108109
# run-orchestration generates expansion file with the MONGODB_URI for the cluster
109110
- command: expansions.update
@@ -130,9 +131,10 @@ functions:
130131
script: |
131132
${PREPARE_SHELL}
132133
133-
MONGODB_URI=${MONGODB_URI} \
134+
MONGODB_URI="${MONGODB_URI}" \
134135
TOPOLOGY=${TOPOLOGY} \
135136
SSL=${SSL} \
137+
AUTH=${AUTH} \
136138
SWIFT_VERSION=${SWIFT_VERSION} \
137139
sh ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
138140
@@ -390,17 +392,19 @@ axes:
390392
variables:
391393
SWIFT_VERSION: "5.1.4"
392394

393-
- id: ssl
394-
display_name: SSL
395+
- id: ssl-auth
396+
display_name: SSL and Auth
395397
values:
396-
- id: ssl
397-
display_name: SSL
398+
- id: ssl-auth
399+
display_name: SSL Auth
398400
variables:
399401
SSL: "ssl"
400-
- id: nossl
401-
display_name: NoSSL
402+
AUTH: "auth"
403+
- id: nossl-noauth
404+
display_name: NoSSL NoAuth
402405
variables:
403406
SSL: "nossl"
407+
AUTH: "noauth"
404408

405409

406410
buildvariants:
@@ -409,8 +413,8 @@ buildvariants:
409413
matrix_spec:
410414
os-fully-featured: "*"
411415
swift-version: "*"
412-
ssl: "*"
413-
display_name: "${swift-version} ${os-fully-featured} ${ssl}"
416+
ssl-auth: "*"
417+
display_name: "${swift-version} ${os-fully-featured} ${ssl-auth}"
414418
tasks:
415419
- ".latest"
416420
- ".4.2"
@@ -423,7 +427,7 @@ buildvariants:
423427
- if:
424428
os-fully-featured: "ubuntu-18.04"
425429
swift-version: "*"
426-
ssl: "ssl"
430+
ssl-auth: "ssl-auth"
427431
then:
428432
remove_tasks: ".3.6"
429433

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ let package = Package(
1414
.target(name: "MongoSwift", dependencies: ["CLibMongoC", "NIO", "NIOConcurrencyHelpers"]),
1515
.target(name: "MongoSwiftSync", dependencies: ["MongoSwift"]),
1616
.target(name: "AtlasConnectivity", dependencies: ["MongoSwiftSync"]),
17-
.target(name: "TestsCommon", dependencies: ["MongoSwift", "Nimble", "CLibMongoC"]),
17+
.target(name: "TestsCommon", dependencies: ["MongoSwift", "Nimble"]),
1818
.testTarget(name: "BSONTests", dependencies: ["MongoSwift", "TestsCommon", "Nimble", "CLibMongoC"]),
1919
.testTarget(name: "MongoSwiftTests", dependencies: ["MongoSwift", "TestsCommon", "Nimble", "NIO", "CLibMongoC"]),
20-
.testTarget(name: "MongoSwiftSyncTests", dependencies: ["MongoSwiftSync", "TestsCommon", "Nimble", "CLibMongoC"]),
20+
.testTarget(name: "MongoSwiftSyncTests", dependencies: ["MongoSwiftSync", "TestsCommon", "Nimble", "MongoSwift"]),
2121
.target(
2222
name: "CLibMongoC",
2323
dependencies: [],

Sources/MongoSwift/ConnectionPool.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,38 @@ internal class ConnectionPool {
122122
throw InternalError(message: "ConnectionPool was already closed")
123123
}
124124
}
125+
126+
/// Selects a server according to the specified parameters and returns a description of a suitable server to use.
127+
/// Throws an error if a server cannot be selected. This method will start up SDAM in libmongoc if it hasn't been
128+
/// started already. This method may block.
129+
internal func selectServer(forWrites: Bool, readPreference: ReadPreference? = nil) throws -> ServerDescription {
130+
return try self.withConnection { conn in
131+
var error = bson_error_t()
132+
guard let desc = mongoc_client_select_server(
133+
conn.clientHandle,
134+
forWrites,
135+
readPreference?._readPreference,
136+
&error
137+
) else {
138+
throw extractMongoError(error: error)
139+
}
140+
141+
defer { mongoc_server_description_destroy(desc) }
142+
return ServerDescription(desc)
143+
}
144+
}
145+
146+
/// Retrieves the connection string used to create this pool. If SDAM has been started in libmongoc, the getters
147+
/// on the returned connection string will return any values that were retrieved from TXT records. Throws an error
148+
/// if the connection string cannot be retrieved.
149+
internal func getConnectionString() throws -> ConnectionString {
150+
return try self.withConnection { conn in
151+
guard let uri = mongoc_client_get_uri(conn.clientHandle) else {
152+
throw InternalError(message: "Couldn't retrieve client's connection string")
153+
}
154+
return ConnectionString(copying: uri)
155+
}
156+
}
125157
}
126158

127159
extension String {

Sources/MongoSwift/ConnectionString.swift

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ internal class ConnectionString {
3434
}
3535
}
3636

37+
/// Initializes a new connection string that wraps a copy of the provided URI. Does not destroy the input URI.
38+
internal init(copying uri: OpaquePointer) {
39+
self._uri = mongoc_uri_copy(uri)
40+
}
41+
3742
/// Cleans up the underlying `mongoc_uri_t`.
3843
deinit {
3944
mongoc_uri_destroy(self._uri)
@@ -72,4 +77,116 @@ internal class ConnectionString {
7277
mongoc_uri_set_read_prefs_t(self._uri, rp._readPreference)
7378
}
7479
}
80+
81+
/// Returns the username if one was provided, otherwise nil.
82+
internal var username: String? {
83+
guard let username = mongoc_uri_get_username(self._uri) else {
84+
return nil
85+
}
86+
return String(cString: username)
87+
}
88+
89+
/// Returns the password if one was provided, otherwise nil.
90+
internal var password: String? {
91+
guard let pw = mongoc_uri_get_password(self._uri) else {
92+
return nil
93+
}
94+
return String(cString: pw)
95+
}
96+
97+
/// Returns the auth database if one was provided, otherwise nil.
98+
internal var authSource: String? {
99+
guard let source = mongoc_uri_get_auth_source(self._uri) else {
100+
return nil
101+
}
102+
return String(cString: source)
103+
}
104+
105+
/// Returns the auth mechanism if one was provided, otherwise nil.
106+
internal var authMechanism: AuthMechanism? {
107+
guard let mechanism = mongoc_uri_get_auth_mechanism(self._uri) else {
108+
return nil
109+
}
110+
let str = String(cString: mechanism)
111+
return AuthMechanism(rawValue: str)
112+
}
113+
114+
/// Returns a document containing the auth mechanism properties if any were provided, otherwise nil.
115+
internal var authMechanismProperties: Document? {
116+
var props = bson_t()
117+
return withUnsafeMutablePointer(to: &props) { propsPtr in
118+
let opaquePtr = OpaquePointer(propsPtr)
119+
guard mongoc_uri_get_mechanism_properties(self._uri, opaquePtr) else {
120+
return nil
121+
}
122+
/// This copy should not be returned directly as its only guaranteed valid for as long as the
123+
/// `mongoc_uri_t`, as `props` was statically initialized from data stored in the URI and may contain
124+
/// pointers that will be invalidated once the URI is.
125+
let copy = Document(copying: opaquePtr)
126+
127+
return copy.mapValues { value in
128+
// mongoc returns boolean options e.g. CANONICALIZE_HOSTNAME as strings, but they are boolean values.
129+
switch value {
130+
case "true":
131+
return true
132+
case "false":
133+
return false
134+
default:
135+
return value
136+
}
137+
}
138+
}
139+
}
140+
141+
/// Returns the credential configured on this URI. Will be empty if no options are set.
142+
internal var credential: Credential {
143+
return Credential(
144+
username: self.username,
145+
password: self.password,
146+
source: self.authSource,
147+
mechanism: self.authMechanism,
148+
mechanismProperties: self.authMechanismProperties
149+
)
150+
}
151+
152+
internal var db: String? {
153+
guard let db = mongoc_uri_get_database(self._uri) else {
154+
return nil
155+
}
156+
return String(cString: db)
157+
}
158+
159+
/// Returns a document containing all of the options provided after the ? of the URI.
160+
internal var options: Document? {
161+
guard let optsDoc = mongoc_uri_get_options(self._uri) else {
162+
return nil
163+
}
164+
return Document(copying: optsDoc)
165+
}
166+
167+
/// Returns the host/port pairs specified in the connection string, or nil if this connection string's scheme is
168+
/// “mongodb+srv://”.
169+
internal var hosts: [String]? {
170+
guard let hostList = mongoc_uri_get_hosts(self._uri) else {
171+
return nil
172+
}
173+
174+
var hosts = [String]()
175+
var next = hostList.pointee
176+
while true {
177+
hosts.append(withUnsafeBytes(of: next.host_and_port) { rawPtr in
178+
guard let baseAddress = rawPtr.baseAddress else {
179+
return ""
180+
}
181+
return String(cString: baseAddress.assumingMemoryBound(to: CChar.self))
182+
})
183+
184+
if next.next == nil {
185+
break
186+
}
187+
next = next.next.pointee
188+
}
189+
190+
return hosts
191+
}
75192
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/// Represents an authentication credential.
2+
internal struct Credential: Decodable, Equatable {
3+
/// A string containing the username. For auth mechanisms that do not utilize a password, this may be the entire
4+
/// `userinfo` token from the connection string.
5+
internal let username: String?
6+
/// A string containing the password.
7+
internal let password: String?
8+
/// A string containing the authentication database.
9+
internal let source: String?
10+
/// The authentication mechanism. A nil value for this property indicates that a mechanism wasn't specified and
11+
/// that mechanism negotiation is required.
12+
internal let mechanism: AuthMechanism?
13+
/// A document containing mechanism-specific properties.
14+
internal let mechanismProperties: Document?
15+
16+
private enum CodingKeys: String, CodingKey {
17+
case username, password, source, mechanism, mechanismProperties = "mechanism_properties"
18+
}
19+
20+
// TODO: SWIFT-636: remove this initializer and the one below it.
21+
internal init(from decoder: Decoder) throws {
22+
let container = try decoder.container(keyedBy: CodingKeys.self)
23+
self.username = try container.decodeIfPresent(String.self, forKey: .username)
24+
self.password = try container.decodeIfPresent(String.self, forKey: .password)
25+
self.source = try container.decodeIfPresent(String.self, forKey: .source)
26+
self.mechanism = try container.decodeIfPresent(AuthMechanism.self, forKey: .mechanism)
27+
28+
// libmongoc does not return the service name if it's the default, but it is contained in the spec test files,
29+
// so filter it out here if it's present.
30+
let properties = try container.decodeIfPresent(Document.self, forKey: .mechanismProperties)
31+
let filteredProperties = properties?.filter { !($0.0 == "SERVICE_NAME" && $0.1 == "mongodb") }
32+
// if SERVICE_NAME was the only key then don't return an empty document.
33+
if filteredProperties?.isEmpty == true {
34+
self.mechanismProperties = nil
35+
} else {
36+
self.mechanismProperties = filteredProperties
37+
}
38+
}
39+
40+
internal init(
41+
username: String?,
42+
password: String?,
43+
source: String?,
44+
mechanism: AuthMechanism?,
45+
mechanismProperties: Document?
46+
) {
47+
self.mechanism = mechanism
48+
self.mechanismProperties = mechanismProperties
49+
self.password = password
50+
self.source = source
51+
self.username = username
52+
}
53+
}
54+
55+
/// Possible authentication mechanisms.
56+
internal enum AuthMechanism: String, Decodable {
57+
case scramSHA1 = "SCRAM-SHA-1"
58+
case scramSHA256 = "SCRAM-SHA-256"
59+
case gssAPI = "GSSAPI"
60+
case mongodbCR = "MONGODB-CR"
61+
case mongodbX509 = "MONGODB-X509"
62+
case plain = "PLAIN"
63+
}

0 commit comments

Comments
 (0)