Skip to content
33 changes: 33 additions & 0 deletions src/common/atlas/roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { UserConfig } from "../config.js";
import { DatabaseUserRole } from "./openapi.js";

/**
* Get the default role name for the database user based on the Atlas Admin API
* https://www.mongodb.com/docs/atlas/mongodb-users-roles-and-privileges/
*/
export function getDefaultRoleFromConfig(config: UserConfig): DatabaseUserRole {
if (config.readOnly) {
return {
roleName: "readAnyDatabase",
databaseName: "admin",
};
}

// If all write tools are enabled, use readWriteAnyDatabase
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a bit hard to follow due to the double negation, but I don't think this is the right condition to check - if any write tool is enabled, we should default to readWrite, otherwise we're risking some tools to not work as intended. For example, if I want to be able to insert new data, but not delete existing documents, I may disable delete tools, but I'll still need the readWriteAnyDatabase user.

Copy link
Collaborator Author

@blva blva Aug 22, 2025

Choose a reason for hiding this comment

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

This is a bit hard to follow due to the double negation, but I don't think this is the right condition to check - if any write tool is enabled, we should default to readWrite, otherwise we're risking some tools to not work as intended.

Oh, got it! I thought that we were trying to avoid any writes if any of them are disabled.

For example, if I want to be able to insert new data, but not delete existing documents, I may disable delete tools, but I'll still need the readWriteAnyDatabase user.

Fair, I thought we wanted to be more strict, but I agree that this would just cause people to enable all tools

if (
!config.disabledTools?.includes("create") &&
!config.disabledTools?.includes("update") &&
!config.disabledTools?.includes("delete") &&
!config.disabledTools?.includes("metadata")
) {
return {
roleName: "readWriteAnyDatabase",
databaseName: "admin",
};
}

return {
roleName: "readAnyDatabase",
databaseName: "admin",
};
}
19 changes: 3 additions & 16 deletions src/tools/atlas/connect/connectCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LogId } from "../../../common/logger.js";
import { inspectCluster } from "../../../common/atlas/cluster.js";
import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js";
import { AtlasClusterConnectionInfo } from "../../../common/connectionManager.js";
import { getDefaultRoleFromConfig } from "../../../common/atlas/roles.js";

const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours

Expand Down Expand Up @@ -72,16 +73,7 @@ export class ConnectClusterTool extends AtlasToolBase {
const password = await generateSecurePassword();

const expiryDate = new Date(Date.now() + EXPIRY_MS);

const readOnly =
this.config.readOnly ||
(this.config.disabledTools?.includes("create") &&
this.config.disabledTools?.includes("update") &&
this.config.disabledTools?.includes("delete") &&
!this.config.disabledTools?.includes("read") &&
!this.config.disabledTools?.includes("metadata"));

const roleName = readOnly ? "readAnyDatabase" : "readWriteAnyDatabase";
const role = getDefaultRoleFromConfig(this.config);

await this.session.apiClient.createDatabaseUser({
params: {
Expand All @@ -92,12 +84,7 @@ export class ConnectClusterTool extends AtlasToolBase {
body: {
databaseName: "admin",
groupId: projectId,
roles: [
{
roleName,
databaseName: "admin",
},
],
roles: [role],
scopes: [{ type: "CLUSTER", name: clusterName }],
username,
password,
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/common/roles.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, it, expect } from "vitest";
import { getDefaultRoleFromConfig } from "../../../src/common/atlas/roles.js";
import { defaultUserConfig, UserConfig } from "../../../src/common/config.js";

describe("getDefaultRoleFromConfig", () => {
const defaultConfig: UserConfig = {
...defaultUserConfig,
};

const readOnlyConfig: UserConfig = {
...defaultConfig,
readOnly: true,
};

const readWriteConfig: UserConfig = {
...defaultConfig,
readOnly: false,
disabledTools: [],
};

it("should return the correct role for a read-only config", () => {
const role = getDefaultRoleFromConfig(readOnlyConfig);
expect(role).toEqual({
roleName: "readAnyDatabase",
databaseName: "admin",
});
});

it("should return the correct role for a read-write config", () => {
const role = getDefaultRoleFromConfig(readWriteConfig);
expect(role).toEqual({
roleName: "readWriteAnyDatabase",
databaseName: "admin",
});
});

it("should return the correct role for a read-write config with all tools enabled", () => {
const role = getDefaultRoleFromConfig(readWriteConfig);
expect(role).toEqual({
roleName: "readWriteAnyDatabase",
databaseName: "admin",
});
});

// loop with each disabled tool
for (const tool of ["create", "update", "delete", "metadata"]) {
it(`should return the correct role for a read-write config with ${tool} disabled`, () => {
const config = { ...readWriteConfig, disabledTools: [tool] };
const role = getDefaultRoleFromConfig(config);
expect(role).toEqual({
roleName: "readAnyDatabase",
databaseName: "admin",
});
});
}
});
Loading