Skip to content

Commit

Permalink
test(NODE-5621): unskip and fix the unicode auth prose spec tests (#3965
Browse files Browse the repository at this point in the history
)

Co-authored-by: Durran Jordan <durran@gmail.com>
  • Loading branch information
alenakhineika and durran authored Jan 22, 2024
1 parent f506b6a commit 7f97c2a
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 47 deletions.
193 changes: 177 additions & 16 deletions test/integration/auth/auth.prose.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ describe('Authentication Spec Prose Tests', function () {
);
});

// todo(NODE-5621): fix the issue with unicode characters.
describe('Step 4', function () {
/**
* Step 4
Expand Down Expand Up @@ -340,9 +339,15 @@ describe('Authentication Spec Prose Tests', function () {
];

beforeEach(async function () {
utilClient = this.configuration.newClient();
utilClient = this.configuration.newClient(this.configuration.url());
const db = utilClient.db('admin');

try {
await Promise.all(users.map(user => db.removeUser(user.username)));
} catch (err) {
/** We ensure that users are deleted. No action needed. */
}

const createUserCommands = users.map(user => ({
createUser: user.username,
pwd: user.password,
Expand All @@ -354,33 +359,189 @@ describe('Authentication Spec Prose Tests', function () {
});

afterEach(async function () {
const db = utilClient.db('admin');
await Promise.all(users.map(user => db.removeUser(user.username)));
await utilClient?.close();
await client?.close();
});

for (const { username, password } of [
{ username: 'IX', password: 'IX' },
{ username: 'IX', password: 'I\u00ADX' },
{ username: '\u2168', password: 'IV' },
{ username: '\u2168', password: 'I\u00ADV' }
]) {
it.skip(
`logs in with username "${username}" and password "${password}"`,
context('auth credentials in options', () => {
it('logs in with non-normalized username and password', metadata, async function () {
const options = {
auth: { username: 'IX', password: 'IX' },
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};

client = this.configuration.newClient({}, options);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});

it(
'logs in with non-normalized username and normalized password',
metadata,
async function () {
const options = {
auth: { username, password },
auth: { username: 'IX', password: 'I\u00ADX' },
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};

client = this.configuration.newClient(options);
client = this.configuration.newClient({}, options);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
}
).skipReason = 'todo(NODE-5621): fix the issue with unicode characters.';
}
);

it(
'logs in with normalized username and non-normalized password',
metadata,
async function () {
const options = {
auth: { username: '\u2168', password: 'IV' },
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};

client = this.configuration.newClient({}, options);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
}
);

it('logs in with normalized username and normalized password', metadata, async function () {
const options = {
auth: { username: '\u2168', password: 'I\u00ADV' },
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};

client = this.configuration.newClient({}, options);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});
});

context('auth credentials in url', () => {
context('encoded', () => {
it('logs in with not encoded username and password', metadata, async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: 'IX', password: 'IX' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});

it('logs in with not encoded username and encoded password', metadata, async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: 'IX', password: 'I%C2%ADX' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});

it('logs in with encoded username and not encoded password', metadata, async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: '%E2%85%A8', password: 'IV' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});

it('logs in with encoded username and encoded password', metadata, async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: '%E2%85%A8', password: 'I%C2%ADV' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});
});

context('normalized', () => {
it('logs in with non-normalized username and password', metadata, async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: 'IX', password: 'IX' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
});

it(
'logs in with non-normalized username and normalized password',
metadata,
async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: 'IX', password: 'I\u00ADX' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
}
);

it(
'logs in with normalized username and non-normalized password',
metadata,
async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: '\u2168', password: 'I\u00ADV' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
}
);

it(
'logs in with normalized username and normalized password',
metadata,
async function () {
const options = {
authSource: 'admin',
authMechanism: 'SCRAM-SHA-256'
};
client = this.configuration.newClient(
this.configuration.url({ username: '\u2168', password: 'I\u00ADV' }),
options
);
const stats = await client.db('admin').stats();
expect(stats).to.exist;
}
);
});
});
});
});
});
64 changes: 33 additions & 31 deletions test/tools/runner/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,88 +158,90 @@ export class TestConfiguration {
return uri.indexOf('MONGODB-OIDC') > -1 && uri.indexOf('PROVIDER_NAME:azure') > -1;
}

newClient(dbOptions?: string | Record<string, any>, serverOptions?: Record<string, any>) {
newClient(urlOrQueryOptions?: string | Record<string, any>, serverOptions?: Record<string, any>) {
serverOptions = Object.assign({}, getEnvironmentalOptions(), serverOptions);

// support MongoClient constructor form (url, options) for `newClient`
if (typeof dbOptions === 'string') {
return new MongoClient(dbOptions, serverOptions);
// Support MongoClient constructor form (url, options) for `newClient`.
if (typeof urlOrQueryOptions === 'string') {
if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) {
throw new Error(`Cannot use options to specify host/port, must be in ${urlOrQueryOptions}`);
}

return new MongoClient(urlOrQueryOptions, serverOptions);
}

dbOptions = dbOptions || {};
// Fall back
let dbHost = (serverOptions && serverOptions.host) || this.options.host;
const dbPort = (serverOptions && serverOptions.port) || this.options.port;
const queryOptions = urlOrQueryOptions || {};

// Fall back.
let dbHost = serverOptions.host || this.options.host;
if (dbHost.indexOf('.sock') !== -1) {
dbHost = qs.escape(dbHost);
}
const dbPort = serverOptions.port || this.options.port;

if (this.options.authMechanism) {
Object.assign(dbOptions, {
if (this.options.authMechanism && !serverOptions.authMechanism) {
Object.assign(queryOptions, {
authMechanism: this.options.authMechanism
});
}

if (this.options.authMechanismProperties) {
Object.assign(dbOptions, {
if (this.options.authMechanismProperties && !serverOptions.authMechanismProperties) {
Object.assign(queryOptions, {
authMechanismProperties: convertToConnStringMap(this.options.authMechanismProperties)
});
}

if (this.options.replicaSet) {
Object.assign(dbOptions, { replicaSet: this.options.replicaSet });
if (this.options.replicaSet && !serverOptions.replicaSet) {
Object.assign(queryOptions, { replicaSet: this.options.replicaSet });
}

if (this.options.proxyURIParams) {
for (const [name, value] of Object.entries(this.options.proxyURIParams)) {
if (value) {
dbOptions[name] = value;
queryOptions[name] = value;
}
}
}

// Flatten any options nested under `writeConcern` before we make the connection string
if (dbOptions.writeConcern) {
Object.assign(dbOptions, dbOptions.writeConcern);
delete dbOptions.writeConcern;
// Flatten any options nested under `writeConcern` before we make the connection string.
if (queryOptions.writeConcern && !serverOptions.writeConcern) {
Object.assign(queryOptions, queryOptions.writeConcern);
delete queryOptions.writeConcern;
}

if (this.topologyType === TopologyType.LoadBalanced && !this.isServerless) {
dbOptions.loadBalanced = true;
queryOptions.loadBalanced = true;
}

const urlOptions: url.UrlObject = {
protocol: this.isServerless ? 'mongodb+srv' : 'mongodb',
slashes: true,
hostname: dbHost,
port: this.isServerless ? null : dbPort,
query: dbOptions,
query: queryOptions,
pathname: '/'
};

if (this.options.auth) {
if (this.options.auth && !serverOptions.auth) {
const { username, password } = this.options.auth;
if (username) {
urlOptions.auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
}
}

if (dbOptions.auth) {
const { username, password } = dbOptions.auth;
if (queryOptions.auth) {
const { username, password } = queryOptions.auth;
if (username) {
urlOptions.auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
}
}

if (typeof urlOptions.query === 'object') {
// Auth goes at the top of the uri, not in the searchParams
delete urlOptions.query.auth;
// Auth goes at the top of the uri, not in the searchParams.
delete urlOptions.query?.auth;
}

const connectionString = url.format(urlOptions);
if (Reflect.has(serverOptions, 'host') || Reflect.has(serverOptions, 'port')) {
throw new Error(`Cannot use options to specify host/port, must be in ${connectionString}`);
}

return new MongoClient(connectionString, serverOptions);
}
Expand Down Expand Up @@ -277,8 +279,8 @@ export class TestConfiguration {

url.pathname = `/${options.db}`;

const username = this.options.username || (this.options.auth && this.options.auth.username);
const password = this.options.password || (this.options.auth && this.options.auth.password);
const username = options.username || this.options.auth?.username;
const password = options.password || this.options.auth?.password;

if (username) {
url.username = username;
Expand Down

0 comments on commit 7f97c2a

Please sign in to comment.