Skip to content

Support authorizedCollections option for listCollections helpers #855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.mongodb.ServerCursor;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerDescription;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.async.SingleResultCallback;
import com.mongodb.internal.binding.AsyncConnectionSource;
Expand Down Expand Up @@ -57,6 +58,7 @@
import static com.mongodb.ReadPreference.primary;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.connection.ServerType.SHARD_ROUTER;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback;
import static com.mongodb.internal.operation.CommandOperationHelper.CommandCreator;
import static com.mongodb.internal.operation.CommandOperationHelper.createReadCommandAndExecute;
Expand All @@ -83,6 +85,8 @@
* An operation that provides a cursor allowing iteration through the metadata of all the collections in a database. This operation
* ensures that the value of the {@code name} field of each returned document is the simple name of the collection rather than the full
* namespace.
* <p>
* See <a href="https://docs.mongodb.com/manual/reference/command/listCollections/">{@code listCollections}</a></p>.
*
* @param <T> the document type
* @since 3.0
Expand All @@ -95,6 +99,7 @@ public class ListCollectionsOperation<T> implements AsyncReadOperation<AsyncBatc
private int batchSize;
private long maxTimeMS;
private boolean nameOnly;
private boolean authorizedCollections;

/**
* Construct a new instance.
Expand Down Expand Up @@ -157,6 +162,25 @@ public ListCollectionsOperation<T> nameOnly(final boolean nameOnly) {
return this;
}

/**
* Ignored unless {@link #nameOnly(boolean)} is {@code true}.
*
* @since 4.5
* @mongodb.server.release 4.0
*/
public ListCollectionsOperation<T> authorizedCollections(final boolean authorizedCollections) {
this.authorizedCollections = authorizedCollections;
return this;
}

/**
* This method is used by tests via the reflection API. For example, see {@code TestHelper.assertOperationIsTheSameAs}.
*/
@VisibleForTesting(otherwise = PRIVATE)
public boolean isAuthorizedCollections() {
return authorizedCollections;
}

/**
* Gets the number of documents to return per batch.
*
Expand Down Expand Up @@ -351,6 +375,9 @@ private BsonDocument getCommand() {
}
if (nameOnly) {
command.append("nameOnly", BsonBoolean.TRUE);
if (authorizedCollections) {
command.append("authorizedCollections", BsonBoolean.TRUE);
}
Comment on lines 376 to +380
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation says: "When used without nameOnly: true, this option has no effect." That's why we send the option only if nameOnly is true.

}
if (maxTimeMS > 0) {
command.put("maxTimeMS", new BsonInt64(maxTimeMS));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,11 +542,13 @@ public DropIndexOperation dropIndex(final Bson keys, final DropIndexOptions drop

public <TResult> ListCollectionsOperation<TResult> listCollections(final String databaseName, final Class<TResult> resultClass,
final Bson filter, final boolean collectionNamesOnly,
final boolean authorizedCollections,
final Integer batchSize, final long maxTimeMS) {
return new ListCollectionsOperation<TResult>(databaseName, codecRegistry.get(resultClass))
.retryReads(retryReads)
.filter(toBsonDocumentOrNull(filter))
.nameOnly(collectionNamesOnly)
.authorizedCollections(authorizedCollections)
.batchSize(batchSize == null ? 0 : batchSize)
.maxTime(maxTimeMS, MILLISECONDS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,10 @@ public WriteOperation<Void> dropIndex(final Bson keys, final DropIndexOptions op

public <TResult> ReadOperation<BatchCursor<TResult>> listCollections(final String databaseName, final Class<TResult> resultClass,
final Bson filter, final boolean collectionNamesOnly,
final boolean authorizedCollections,
final Integer batchSize, final long maxTimeMS) {
return operations.listCollections(databaseName, resultClass, filter, collectionNamesOnly, batchSize, maxTimeMS);
return operations.listCollections(databaseName, resultClass, filter, collectionNamesOnly, authorizedCollections,
batchSize, maxTimeMS);
}

public <TResult> ReadOperation<BatchCursor<TResult>> listDatabases(final Class<TResult> resultClass, final Bson filter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
*
* @param <TResult> The type of the result.
* @since 1.0
* @mongodb.driver.manual reference/command/listCollections/ listCollections
*/
public interface ListCollectionsPublisher<TResult> extends Publisher<TResult> {

Expand All @@ -39,6 +40,21 @@ public interface ListCollectionsPublisher<TResult> extends Publisher<TResult> {
*/
ListCollectionsPublisher<TResult> filter(@Nullable Bson filter);

/**
* Sets the {@code authorizedCollections} field of the {@code listCollections} command.
* This method is ignored if called on a {@link ListCollectionsPublisher} obtained not via any of the
* {@link MongoDatabase#listCollectionNames() MongoDatabase.listCollectionNames} methods.
*
* @param authorizedCollections If {@code true}, allows executing the {@code listCollections} command,
* which has the {@code nameOnly} field set to {@code true}, without having the
* <a href="https://docs.mongodb.com/manual/reference/privilege-actions/#mongodb-authaction-listCollections">
* {@code listCollections} privilege</a> on the corresponding database resource.
* @return {@code this}.
* @since 4.5
* @mongodb.server.release 4.0
*/
ListCollectionsPublisher<TResult> authorizedCollections(boolean authorizedCollections);

/**
* Sets the maximum execution time on the server for this operation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,20 @@ public interface MongoDatabase {
* Gets the names of all the collections in this database.
*
* @return a publisher with all the names of all the collections in this database
* @mongodb.driver.manual reference/command/listCollections listCollections
*/
Publisher<String> listCollectionNames();
ListCollectionsPublisher<String> listCollectionNames();

/**
* Gets the names of all the collections in this database.
*
* @param clientSession the client session with which to associate this operation
* @return a publisher with all the names of all the collections in this database
* @mongodb.driver.manual reference/command/listCollections listCollections
* @mongodb.server.release 3.6
* @since 1.7
*/
Publisher<String> listCollectionNames(ClientSession clientSession);
ListCollectionsPublisher<String> listCollectionNames(ClientSession clientSession);

/**
* Finds all the collections in this database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,29 @@
package com.mongodb.reactivestreams.client.internal;

import com.mongodb.ReadConcern;
import com.mongodb.internal.VisibleForTesting;
import com.mongodb.internal.async.AsyncBatchCursor;
import com.mongodb.internal.operation.AsyncReadOperation;
import com.mongodb.lang.Nullable;
import com.mongodb.reactivestreams.client.ClientSession;
import com.mongodb.reactivestreams.client.ListCollectionsPublisher;
import org.bson.conversions.Bson;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

final class ListCollectionsPublisherImpl<T> extends BatchCursorPublisher<T> implements ListCollectionsPublisher<T> {

private final boolean collectionNamesOnly;
private boolean authorizedCollections;
private Bson filter;
private long maxTimeMS;

Expand Down Expand Up @@ -59,8 +67,72 @@ public ListCollectionsPublisherImpl<T> filter(@Nullable final Bson filter) {
return this;
}

@Override
public ListCollectionsPublisherImpl<T> authorizedCollections(final boolean authorizedCollections) {
this.authorizedCollections = authorizedCollections;
return this;
}

AsyncReadOperation<AsyncBatchCursor<T>> asAsyncReadOperation(final int initialBatchSize) {
return getOperations().listCollections(getNamespace().getDatabaseName(), getDocumentClass(), filter, collectionNamesOnly,
initialBatchSize, maxTimeMS);
authorizedCollections, initialBatchSize, maxTimeMS);
}

<U> ListCollectionsPublisher<U> map(final Function<T, U> mapper) {
return new Mapping<>(this, mapper);
}

private static final class Mapping<T, U> implements ListCollectionsPublisher<U> {
private final ListCollectionsPublisher<T> wrapped;
private final Publisher<U> mappingPublisher;
private final Function<T, U> mapper;

Mapping(final ListCollectionsPublisher<T> publisher, final Function<T, U> mapper) {
this.wrapped = publisher;
mappingPublisher = Flux.from(publisher).map(mapper);
this.mapper = mapper;
}

@Override
public ListCollectionsPublisher<U> filter(@Nullable final Bson filter) {
wrapped.filter(filter);
return this;
}

@Override
public ListCollectionsPublisher<U> authorizedCollections(final boolean authorizedCollections) {
wrapped.authorizedCollections(authorizedCollections);
return this;
}

@Override
public ListCollectionsPublisher<U> maxTime(final long maxTime, final TimeUnit timeUnit) {
wrapped.maxTime(maxTime, timeUnit);
return this;
}

@Override
public ListCollectionsPublisher<U> batchSize(final int batchSize) {
wrapped.batchSize(batchSize);
return this;
}

@Override
public Publisher<U> first() {
return Mono.from(wrapped.first()).map(mapper);
}

@Override
public void subscribe(final Subscriber<? super U> s) {
mappingPublisher.subscribe(s);
}

/**
* This method is used in tests via the reflection API.
*/
@VisibleForTesting(otherwise = PRIVATE)
ListCollectionsPublisher<T> getMapped() {
return wrapped;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -168,14 +167,14 @@ public Publisher<Void> drop(final ClientSession clientSession) {
}

@Override
public Publisher<String> listCollectionNames() {
return Flux.from(new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true))
public ListCollectionsPublisher<String> listCollectionNames() {
return new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true)
.map(d -> d.getString("name"));
}

@Override
public Publisher<String> listCollectionNames(final ClientSession clientSession) {
return Flux.from(new ListCollectionsPublisherImpl<>(notNull("clientSession", clientSession), mongoOperationPublisher, true))
public ListCollectionsPublisher<String> listCollectionNames(final ClientSession clientSession) {
return new ListCollectionsPublisherImpl<>(notNull("clientSession", clientSession), mongoOperationPublisher, true)
.map(d -> d.getString("name"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public ListCollectionsIterable<T> filter(@Nullable final Bson filter) {
return this;
}

@Override
public ListCollectionsIterable<T> authorizedCollections(final boolean authorizedCollections) {
wrapped.authorizedCollections(authorizedCollections);
return this;
}

@Override
public ListCollectionsIterable<T> maxTime(final long maxTime, final TimeUnit timeUnit) {
wrapped.maxTime(maxTime, timeUnit);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.mongodb.client.ListCollectionsIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.CreateViewOptions;
import org.bson.Document;
Expand Down Expand Up @@ -157,7 +156,7 @@ public void drop(final ClientSession clientSession) {
}

@Override
public MongoIterable<String> listCollectionNames() {
public ListCollectionsIterable<String> listCollectionNames() {
throw new UnsupportedOperationException();
}

Expand All @@ -172,7 +171,7 @@ public <TResult> ListCollectionsIterable<TResult> listCollections(final Class<TR
}

@Override
public MongoIterable<String> listCollectionNames(final ClientSession clientSession) {
public ListCollectionsIterable<String> listCollectionNames(final ClientSession clientSession) {
throw new UnsupportedOperationException();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ public class ListCollectionsPublisherImplTest extends TestHelper {
void shouldBuildTheExpectedOperation() {
TestOperationExecutor executor = createOperationExecutor(asList(getBatchCursor(), getBatchCursor()));
ListCollectionsPublisher<String> publisher = new ListCollectionsPublisherImpl<>(null, createMongoOperationPublisher(executor)
.withDocumentClass(String.class), true);
.withDocumentClass(String.class), true)
.authorizedCollections(true);

ListCollectionsOperation<String> expectedOperation = new ListCollectionsOperation<>(DATABASE_NAME,
getDefaultCodecRegistry().get(String.class))
.batchSize(Integer.MAX_VALUE)
.nameOnly(true).retryReads(true);
.nameOnly(true)
.authorizedCollections(true)
.retryReads(true);

// default input should be as expected
Flux.from(publisher).blockFirst();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ void testListCollectionNames() {
new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true);
assertPublisherIsTheSameAs(expected, database.listCollectionNames(), "Default");
},
() -> {
ListCollectionsPublisher<Document> expected =
new ListCollectionsPublisherImpl<>(null, mongoOperationPublisher, true)
.authorizedCollections(true);
assertPublisherIsTheSameAs(expected, database.listCollectionNames().authorizedCollections(true),
"nameOnly & authorizedCollections");
},
() -> {
ListCollectionsPublisher<Document> expected =
new ListCollectionsPublisherImpl<>(clientSession, mongoOperationPublisher, true);
Expand Down
Loading