Skip to content

Commit d026dd9

Browse files
authored
SWIFT-773: add authorizedDatabases optional option (#450)
1 parent 8727aaf commit d026dd9

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

Guides/Development.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Swift Driver Development Guide
22

33
## Index
4-
* [Things to Install](#things-to-install)
4+
* [Things to Install](#things-to-install)
55
* [The Code](#the-code)
66
* [Building](#building)
77
* [Running Tests](#running-tests)
@@ -15,15 +15,17 @@
1515
* [swiftenv](https://swiftenv.fuller.li/en/latest/installation.html): a command-line tool that allows easy installation of and switching between versions of Swift.
1616
* [Jazzy](https://github.com/realm/jazzy#installation): the tool we use to generate documentation.
1717
* [SwiftFormat](https://github.com/nicklockwood/SwiftFormat#how-do-i-install-it): the Swift formatter we use.
18-
* [SwiftLint](https://github.com/realm/SwiftLint#using-homebrew): the Swift linter we use.
18+
* [SwiftLint](https://github.com/realm/SwiftLint#using-homebrew): the Swift linter we use.
1919
* [Sourcery](https://github.com/krzysztofzablocki/Sourcery/#installation): the tool we use for code generation.
2020

2121
## The code
2222
You should clone this repository, as well as the [MongoDB Driver specifications](https://github.com/mongodb/specifications).
2323

24-
## Building
24+
## Building
2525
### From the Command line
26-
Run `swift build` or simply `make` in the project's root directory.
26+
Run `swift build` or simply `make` in the project's root directory.
27+
28+
If you add symbols you may need to run `make exports` which will generate [Sources/MongoSwiftSync/Exports.swift](Sources/MongoSwiftSync/Exports.swift). This makes symbols declared in `MongoSwift` available to importers of `MongoSwiftSync`.
2729

2830
### In Xcode
2931
We do not provide or maintain an already-generated `.xcodeproj` in our repository. Instead, you must generate it locally.
@@ -33,21 +35,21 @@ We do not provide or maintain an already-generated `.xcodeproj` in our repositor
3335
2. Run `make project`
3436
3. You're ready to go! Open `MongoSwift.xcodeproj` and build and test as normal.
3537

36-
Why is this necessary? The project requires a customized "copy resources" build phase to include various test `.json` files. By default, this phase is not included when you run `swift package generate-xcodeproj`. So `make project` first generates the project, and then uses `xcodeproj` to manually add the files to the appropriate targets (see `add_json_files.rb`).
38+
Why is this necessary? The project requires a customized "copy resources" build phase to include various test `.json` files. By default, this phase is not included when you run `swift package generate-xcodeproj`. So `make project` first generates the project, and then uses `xcodeproj` to manually add the files to the appropriate targets (see `add_json_files.rb`).
3739

3840
## Running Tests
3941
**NOTE**: Several of the tests require a mongod instance to be running on the default host/port, `localhost:27017`. You can start this by running `mongod --setParameter enableTestCommands=1`. The `enableTestCommands` parameter is required to use some test-only commands built into MongoDB that we utilize in our tests, e.g. `failCommand`.
4042

4143
You can run tests from Xcode as usual. If you prefer to test from the command line, keep reading.
4244

43-
### From the Command Line
45+
### From the Command Line
4446
We recommend installing the ruby gem `xcpretty` and running tests by executing `make test-pretty`, as this provides output in a much more readable format. (Works on MacOS only.)
4547

4648
Alternatively, you can just run the tests with `swift test`, or `make test`.
4749

4850
To filter tests by regular expression:
49-
- If you are using `swift test`, provide the `--filter` argument: for example, `swift test --filter=MongoClientTests`.
50-
- If you are using `make test` or `make test-pretty`, provide the `FILTER` environment variable: for example, `make test-pretty FILTER=MongoCollectionTests`.
51+
- If you are using `swift test`, provide the `--filter` argument: for example, `swift test --filter=MongoClientTests`.
52+
- If you are using `make test` or `make test-pretty`, provide the `FILTER` environment variable: for example, `make test-pretty FILTER=MongoCollectionTests`.
5153

5254
### Diagnosing Backtraces on Linux
5355

@@ -61,10 +63,10 @@ $ symbolicate-linux-fatal /path/to/MongoSwiftPackageTests.xctest crash.log
6163
This will require you to manually provide the path to the compiled test binary (e.g. `.build/x86_64-unknown-linux/debug/MongoSwiftPackageTests.xctest`).
6264

6365
## Writing and Generating Documentation
64-
We document new code as we write it. We use C-style documentation blocks (`/** ... */`) for documentation longer than 3 lines, and triple-slash (`///`) for shorter documentation.
66+
We document new code as we write it. We use C-style documentation blocks (`/** ... */`) for documentation longer than 3 lines, and triple-slash (`///`) for shorter documentation.
6567
Comments that are _not_ documentation should use two slashes (`//`).
6668

67-
Documentation comments should generally be complete sentences and should end with periods.
69+
Documentation comments should generally be complete sentences and should end with periods.
6870

6971
Our documentation site is automatically generated from the source code using [Jazzy](https://github.com/realm/jazzy#installation). We regenerate it via our release script each time we release a new version of the driver.
7072

@@ -93,7 +95,7 @@ If you have a setup for developing the driver in an editor other than the ones l
9395
* Please read the [NOTICE](https://github.com/Utagai/swift.vim#notice) for proper credits.
9496

9597
## Workflow
96-
1. Create a feature branch, named by the corresponding JIRA ticket if exists, along with a short descriptor of the work: for example, `SWIFT-30/writeconcern`.
98+
1. Create a feature branch, named by the corresponding JIRA ticket if exists, along with a short descriptor of the work: for example, `SWIFT-30/writeconcern`.
9799
1. Do your work on the branch.
98100
1. If you add, remove, or rename any tests, make sure to update `LinuxMain.swift` accordingly. If you are on MacOS, you can do that by running `make linuxmain`.
99101
1. Ensure your code passes both the linter and the formatter.
@@ -103,8 +105,8 @@ If you have a setup for developing the driver in an editor other than the ones l
103105
1. Open a pull request on the repository. Make sure you have rebased your branch onto the latest commits on `master`.
104106
1. Go through code review to get the team's approval on your changes. (See the next section on [Code Review](#code-review) for more details on this process.) Once you get the required approvals and your code passes all tests:
105107
1. Rebase on master again if needed.
106-
1. Rerun tests.
107-
1. Squash all commits into a single, descriptive commit method, formatted as: `TICKET-NUMBER: Description of changes`. For example, `SWIFT-30: Implement WriteConcern type`.
108+
1. Rerun tests.
109+
1. Squash all commits into a single, descriptive commit method, formatted as: `TICKET-NUMBER: Description of changes`. For example, `SWIFT-30: Implement WriteConcern type`.
108110
1. Merge it, or if you don't have permissions, ask someone to merge it for you.
109111

110112
## Code Review

Sources/MongoSwift/MongoClient.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ public class MongoClient {
342342
* - Parameters:
343343
* - filter: Optional `Document` specifying a filter that the listed databases must pass. This filter can be based
344344
* on the "name", "sizeOnDisk", "empty", or "shards" fields of the output.
345+
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
345346
* - session: Optional `ClientSession` to use when executing this command.
346347
*
347348
* - Returns:
@@ -352,14 +353,16 @@ public class MongoClient {
352353
* - `LogicError` if the provided session is inactive.
353354
* - `LogicError` if this client has already been closed.
354355
* - `EncodingError` if an error is encountered while encoding the options to BSON.
356+
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
355357
*
356358
* - SeeAlso: https://docs.mongodb.com/manual/reference/command/listDatabases/
357359
*/
358360
public func listDatabases(
359361
_ filter: Document? = nil,
362+
options: ListDatabasesOptions? = nil,
360363
session: ClientSession? = nil
361364
) -> EventLoopFuture<[DatabaseSpecification]> {
362-
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: nil)
365+
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: nil, options: options)
363366
return self.operationExecutor.execute(operation, client: self, session: session).flatMapThrowing { result in
364367
guard case let .specs(dbs) = result else {
365368
throw InternalError(message: "Invalid result")
@@ -373,6 +376,7 @@ public class MongoClient {
373376
*
374377
* - Parameters:
375378
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
379+
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
376380
* - session: Optional `ClientSession` to use when executing this command
377381
*
378382
* - Returns:
@@ -382,19 +386,22 @@ public class MongoClient {
382386
* If the future fails, the error is likely one of the following:
383387
* - `LogicError` if the provided session is inactive.
384388
* - `LogicError` if this client has already been closed.
389+
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
385390
*/
386391
public func listMongoDatabases(
387392
_ filter: Document? = nil,
393+
options: ListDatabasesOptions? = nil,
388394
session: ClientSession? = nil
389395
) -> EventLoopFuture<[MongoDatabase]> {
390-
self.listDatabaseNames(filter, session: session).map { $0.map { self.db($0) } }
396+
self.listDatabaseNames(filter, options: options, session: session).map { $0.map { self.db($0) } }
391397
}
392398

393399
/**
394400
* Get the names of databases in this client's MongoDB deployment.
395401
*
396402
* - Parameters:
397403
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
404+
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
398405
* - session: Optional `ClientSession` to use when executing this command
399406
*
400407
* - Returns:
@@ -404,12 +411,14 @@ public class MongoClient {
404411
* If the future fails, the error is likely one of the following:
405412
* - `LogicError` if the provided session is inactive.
406413
* - `LogicError` if this client has already been closed.
414+
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
407415
*/
408416
public func listDatabaseNames(
409417
_ filter: Document? = nil,
418+
options: ListDatabasesOptions? = nil,
410419
session: ClientSession? = nil
411420
) -> EventLoopFuture<[String]> {
412-
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: true)
421+
let operation = ListDatabasesOperation(client: self, filter: filter, nameOnly: true, options: options)
413422
return self.operationExecutor.execute(operation, client: self, session: session).flatMapThrowing { result in
414423
guard case let .names(names) = result else {
415424
throw InternalError(message: "Invalid result")

Sources/MongoSwift/Operations/ListDatabasesOperation.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,34 @@ internal enum ListDatabasesResults {
2525
case names([String])
2626
}
2727

28+
/// Options for "listDatabases" operations.
29+
public struct ListDatabasesOptions {
30+
/// Specifies whether to only return databases for which the user has privileges.
31+
public var authorizedDatabases: Bool?
32+
33+
/// Convenience initializer allowing any/all parameters to be omitted or optional.
34+
public init(authorizedDatabases: Bool? = nil) {
35+
self.authorizedDatabases = authorizedDatabases
36+
}
37+
}
38+
2839
/// An operation corresponding to a "listDatabases" command on a collection.
2940
internal struct ListDatabasesOperation: Operation {
3041
private let client: MongoClient
3142
private let filter: Document?
3243
private let nameOnly: Bool?
44+
private let options: ListDatabasesOptions?
3345

3446
internal init(
3547
client: MongoClient,
3648
filter: Document?,
37-
nameOnly: Bool?
49+
nameOnly: Bool?,
50+
options: ListDatabasesOptions?
3851
) {
3952
self.client = client
4053
self.filter = filter
4154
self.nameOnly = nameOnly
55+
self.options = options
4256
}
4357

4458
internal func execute(using connection: Connection, session: ClientSession?) throws -> ListDatabasesResults {
@@ -51,6 +65,9 @@ internal struct ListDatabasesOperation: Operation {
5165
if let nameOnly = self.nameOnly {
5266
cmd["nameOnly"] = .bool(nameOnly)
5367
}
68+
if let authorizedDatabases = self.options?.authorizedDatabases {
69+
cmd["authorizedDatabases"] = .bool(authorizedDatabases)
70+
}
5471

5572
let opts = try encodeOptions(options: nil as Document?, session: session)
5673
var reply = Document()

Sources/MongoSwiftSync/Exports.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
@_exported import struct MongoSwift.InternalError
7676
@_exported import struct MongoSwift.InvalidArgumentError
7777
@_exported import struct MongoSwift.ListCollectionsOptions
78+
@_exported import struct MongoSwift.ListDatabasesOptions
7879
@_exported import struct MongoSwift.LogicError
7980
@_exported import struct MongoSwift.MongoNamespace
8081
@_exported import struct MongoSwift.ObjectId

Sources/MongoSwiftSync/MongoClient.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,56 +92,68 @@ public class MongoClient {
9292
* - Parameters:
9393
* - filter: Optional `Document` specifying a filter that the listed databases must pass. This filter can be based
9494
* on the "name", "sizeOnDisk", "empty", or "shards" fields of the output.
95+
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
9596
* - session: Optional `ClientSession` to use when executing this command.
9697
*
9798
* - Returns: A `[DatabaseSpecification]` containing the databases matching provided criteria.
9899
*
99100
* - Throws:
100101
* - `LogicError` if the provided session is inactive.
101102
* - `EncodingError` if an error is encountered while encoding the options to BSON.
103+
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
102104
*
103105
* - SeeAlso: https://docs.mongodb.com/manual/reference/command/listDatabases/
104106
*/
105107
public func listDatabases(
106108
_ filter: Document? = nil,
109+
options: ListDatabasesOptions? = nil,
107110
session: ClientSession? = nil
108111
) throws -> [DatabaseSpecification] {
109-
try self.asyncClient.listDatabases(filter, session: session?.asyncSession).wait()
112+
try self.asyncClient.listDatabases(filter, options: options, session: session?.asyncSession).wait()
110113
}
111114

112115
/**
113116
* Get a list of `MongoDatabase`s.
114117
*
115118
* - Parameters:
116119
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
120+
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
117121
* - session: Optional `ClientSession` to use when executing this command
118122
*
119123
* - Returns: An Array of `MongoDatabase`s that match the provided filter.
120124
*
121125
* - Throws:
122126
* - `LogicError` if the provided session is inactive.
127+
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
123128
*/
124129
public func listMongoDatabases(
125130
_ filter: Document? = nil,
131+
options: ListDatabasesOptions? = nil,
126132
session: ClientSession? = nil
127133
) throws -> [MongoDatabase] {
128-
try self.listDatabaseNames(filter, session: session).map { self.db($0) }
134+
try self.listDatabaseNames(filter, options: options, session: session).map { self.db($0) }
129135
}
130136

131137
/**
132138
* Get a list of names of databases.
133139
*
134140
* - Parameters:
135141
* - filter: Optional `Document` specifying a filter on the names of the returned databases.
142+
* - options: Optional `ListDatabasesOptions` specifying options for listing databases.
136143
* - session: Optional `ClientSession` to use when executing this command
137144
*
138145
* - Returns: A `[String]` containing names of databases that match the provided filter.
139146
*
140147
* - Throws:
141148
* - `LogicError` if the provided session is inactive.
149+
* - `CommandError` if options.authorizedDatabases is false and the user does not have listDatabases permissions.
142150
*/
143-
public func listDatabaseNames(_ filter: Document? = nil, session: ClientSession? = nil) throws -> [String] {
144-
try self.asyncClient.listDatabaseNames(filter, session: session?.asyncSession).wait()
151+
public func listDatabaseNames(
152+
_ filter: Document? = nil,
153+
options: ListDatabasesOptions? = nil,
154+
session: ClientSession? = nil
155+
) throws -> [String] {
156+
try self.asyncClient.listDatabaseNames(filter, options: options, session: session?.asyncSession).wait()
145157
}
146158

147159
/**

Tests/MongoSwiftSyncTests/SyncMongoClientTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,29 @@ final class SyncMongoClientTests: MongoSwiftTestCase {
4848
if MongoSwiftTestCase.topologyType == .sharded {
4949
expect(dbInfo.first?.shards).toNot(beNil())
5050
}
51+
52+
let monitor = client.addCommandMonitor()
53+
54+
try monitor.captureEvents {
55+
var opts = ListDatabasesOptions(authorizedDatabases: true)
56+
_ = try client.listDatabaseNames(nil, options: opts, session: nil)
57+
opts.authorizedDatabases = false
58+
_ = try client.listDatabaseNames(nil, options: opts, session: nil)
59+
_ = try client.listDatabaseNames()
60+
}
61+
62+
let events = monitor.commandStartedEvents()
63+
expect(events).to(haveCount(3))
64+
65+
let listDbsAuthTrue = events[0]
66+
expect(listDbsAuthTrue.command["listDatabases"]).toNot(beNil())
67+
expect(listDbsAuthTrue.command["authorizedDatabases"]?.boolValue).to(beTrue())
68+
let listDbsAuthFalse = events[1]
69+
expect(listDbsAuthFalse.command["listDatabases"]).toNot(beNil())
70+
expect(listDbsAuthFalse.command["authorizedDatabases"]?.boolValue).to(beFalse())
71+
let listDbsAuthNil = events[2]
72+
expect(listDbsAuthNil.command["listDatabases"]).toNot(beNil())
73+
expect(listDbsAuthNil.command["authorizedDatabases"]).to(beNil())
5174
}
5275

5376
func testFailedClientInitialization() {

0 commit comments

Comments
 (0)