diff --git a/package-lock.json b/package-lock.json index bcd800d1b..5ad732b21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7557,6 +7557,10 @@ "resolved": "packages/cli-repl", "link": true }, + "node_modules/@mongosh/connectivity-tests": { + "resolved": "packages/connectivity-tests", + "link": true + }, "node_modules/@mongosh/docker-build-scripts": { "resolved": "scripts/docker", "link": true @@ -13169,10 +13173,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "node_modules/@mongosh/connectivity-tests": { - "resolved": "packages/connectivity-tests", - "link": true - }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -16905,7 +16905,7 @@ "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, + "devOptional": true, "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.0", @@ -16926,7 +16926,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -23029,7 +23029,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "devOptional": true }, "node_modules/netmask": { "version": "2.0.2", @@ -29132,7 +29132,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -29164,7 +29164,6 @@ "version": "3.15.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz", "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==", - "dev": true, "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -30317,7 +30316,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "devOptional": true }, "node_modules/workerpool": { "version": "6.2.1", @@ -37875,6 +37874,12 @@ } } }, + "@mongosh/connectivity-tests": { + "version": "file:packages/connectivity-tests", + "requires": { + "mongosh": "0.0.0-dev.0" + } + }, "@mongosh/docker-build-scripts": { "version": "file:scripts/docker", "requires": { @@ -42659,12 +42664,6 @@ "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true }, - "connectivity-tests": { - "version": "file:packages/connectivity-tests", - "requires": { - "mongosh": "0.0.0-dev.0" - } - }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -45555,7 +45554,7 @@ "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, + "devOptional": true, "requires": { "minimist": "^1.2.5", "neo-async": "^2.6.0", @@ -45568,7 +45567,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "devOptional": true } } }, @@ -50230,7 +50229,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "devOptional": true }, "netmask": { "version": "2.0.2", @@ -54966,7 +54965,7 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true + "devOptional": true }, "ua-parser-js": { "version": "0.7.35", @@ -54978,7 +54977,6 @@ "version": "3.15.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz", "integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==", - "dev": true, "optional": true }, "unbox-primitive": { @@ -55807,7 +55805,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "devOptional": true }, "workerpool": { "version": "6.2.1", diff --git a/packages/java-shell/src/main/js/all.js b/packages/java-shell/src/main/js/all.js index 93ee586b9..9a3bb5009 100644 --- a/packages/java-shell/src/main/js/all.js +++ b/packages/java-shell/src/main/js/all.js @@ -7,22 +7,18 @@ process.cwd = process.cwd || (() => '/'); globalThis.btoa = globalThis.btoa || (data => Buffer.from(data, 'latin1').toString('base64')); globalThis.atob = globalThis.atob || (data => Buffer.from(data, 'base64').toString('latin1')); -// The BSON package tries to use crypto.randomBytes(), but that throws -// in the current browserify replacement for that package. -// We provide a low-quality polyfill for now. -// https://jira.mongodb.org/browse/MONGOSH-988 +// crypto.randomBytes() is required by BSON package +// but that throws error in the current browserify replacement for that package. +// Here we create crypto object that will be linked to BSON package +// we provide proper implementation of randomBytes in Kotlin in MongoShellEvaluator const crypto = require('crypto'); try { crypto.randomBytes(1); } catch (err) { crypto.randomBytes = function(size) { - const uint8Array = new Uint8Array(size); - for (var i = 0; i < uint8Array.length; i++) { - uint8Array[i] = Math.random() * 256; - } - return size; + throw new Error('randomBytes is not implemented'); }; -}; +} require('../../../../service-provider-core'); // Ensure TextEncoder polyfill is loaded early enough const ShellApi = require('../../../../shell-api/'); @@ -34,4 +30,5 @@ _global = { ShellEvaluator: ShellEvaluator, toShellResult: ShellApi.toShellResult, getShellApiType: ShellApi.getShellApiType, + crypto: crypto }; diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellConverter.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellConverter.kt index d67365db1..b782e5e7a 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellConverter.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellConverter.kt @@ -7,6 +7,7 @@ import com.mongodb.mongosh.service.Either import com.mongodb.mongosh.service.Left import com.mongodb.mongosh.service.Right import org.bson.* +import org.bson.internal.UuidHelper import org.bson.json.JsonReader import org.bson.types.* import org.graalvm.polyglot.Value @@ -93,6 +94,14 @@ internal class MongoShellConverter(private val context: MongoShellContext, priva return array } + fun toBuffer(array: ByteArray): Value { + val jsArray = context.eval("new Buffer(${array.size})") + array.forEachIndexed { index, v -> + jsArray.setArrayElement(index.toLong(), v) + } + return jsArray + } + fun toJsPromise(promise: Either): Value { return when (promise) { is Right -> context.eval("(v) => new Promise(((resolve) => resolve(v)))", "resolved_promise_script").execute(toJs(promise.value)) @@ -189,7 +198,15 @@ internal class MongoShellConverter(private val context: MongoShellContext, priva v.instanceOf(context, bsonTypes.numberLong) -> LongResult(JsonReader(v.invokeMember("toExtendedJSON").toString()).readInt64()) v.instanceOf(context, bsonTypes.binData) -> { val binary = JsonReader(v.invokeMember("toExtendedJSON").toString()).readBinaryData() - BinaryResult(Binary(binary.type, binary.data)) + val uuidRepresentation = when (binary.type) { + BsonBinarySubType.UUID_STANDARD.value -> UuidRepresentation.STANDARD + BsonBinarySubType.UUID_LEGACY.value -> UuidRepresentation.JAVA_LEGACY + else -> null + } + if (uuidRepresentation != null) { + UUIDResult(UuidHelper.decodeBinaryToUuid(binary.data, binary.type, uuidRepresentation)) + } + else BinaryResult(Binary(binary.type, binary.data)) } v.instanceOf(context, bsonTypes.hexData) -> { val binary = JsonReader(v.invokeMember("toExtendedJSON").toString()).readBinaryData() diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellEvaluator.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellEvaluator.kt index ab2f8d4d7..1e3c6bf54 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellEvaluator.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellEvaluator.kt @@ -6,6 +6,7 @@ import org.graalvm.polyglot.Source import org.graalvm.polyglot.Value import org.graalvm.polyglot.proxy.ProxyExecutable import org.intellij.lang.annotations.Language +import java.security.SecureRandom import java.time.LocalDateTime import java.time.OffsetDateTime import java.time.ZoneOffset @@ -37,7 +38,7 @@ internal class MongoShellEvaluator(client: MongoClient?, private val context: Mo toShellResultFn = global.getMember("toShellResult") getShellApiTypeFn = global.getMember("getShellApiType") shellInstanceState.invokeMember("setCtx", context.bindings) - initContext(context.bindings) + initContext(context.bindings, global) } private fun resultHandler() = context.jsFun { args -> @@ -51,13 +52,24 @@ internal class MongoShellEvaluator(client: MongoClient?, private val context: Mo } } - private fun initContext(bindings: Value) { + private fun initContext(bindings: Value, global: Value) { val date = context.eval("(dateHelper) => function inner() { return dateHelper(new.target !== undefined, ...arguments) }", "dateHelper_script") - .execute(ProxyExecutable { args -> dateHelper(args[0].asBoolean(), args.drop(1)) }) + .execute(ProxyExecutable { args -> dateHelper(args[0].asBoolean(), args.drop(1)) }) date["now"] = ProxyExecutable { System.currentTimeMillis() } bindings["Date"] = date bindings["ISODate"] = context.jsFun { args -> dateHelper(true, args.toList()) } - bindings["UUID"] = context.jsFun { args -> if (args.isEmpty()) UUID.randomUUID() else UUID.fromString(args[0].asString()) } + val secureRandom = SecureRandom() + global["crypto"]!!["randomBytes"] = context.jsFun { args -> + // randomBytes method is used to generate UUID bson object + // here we use SecureRandom to generate cryptographically strong random numbers + // java.util.UUID also uses SecureRandom + if (args.size != 1) { + throw IllegalArgumentException("Expected one argument. Got ${args.size} ${args.contentToString()}") + } + val randomBytes = ByteArray(args[0].asInt()) + secureRandom.nextBytes(randomBytes) + return@jsFun converter.toBuffer(randomBytes) // Buffer must be used because .toString('hex') will be invoked on the object + } } private fun shellResult(printable: Value, type: String): Value { diff --git a/packages/java-shell/src/test/resources/collection/insertOne.expected.txt b/packages/java-shell/src/test/resources/collection/insertOne.expected.txt index dd932824b..7137af091 100644 --- a/packages/java-shell/src/test/resources/collection/insertOne.expected.txt +++ b/packages/java-shell/src/test/resources/collection/insertOne.expected.txt @@ -3,7 +3,7 @@ true [ { "_id": , "a": 1, "objectId": , "maxKey": {"$maxKey": 1}, "minKey": {"$minKey": 1}, "binData": {"$binary": {"base64": "MTIzNA==", "subType": "10"}}, "date": {"$date": {"$numberLong": "1355875200000"}}, "isoDate": {"$date": {"$numberLong": "1355875200000"}}, "numberInt": 24, "timestamp": Timestamp{value=429496729600, seconds=100, inc=0}, "undefined": null, "null": null, "uuid": } ] { "acknowledged": true, "insertedId": null } { "acknowledged": true, "insertedId": {"$date": {"$numberLong": "1355875200000"}} } -{ "acknowledged": true, "insertedId": {"$binary": {"base64": "ASNFZ4mrze8BI0VniavN7w==", "subType": "04"}} } +{ "acknowledged": true, "insertedId": } { "acknowledged": true, "insertedId": {"$maxKey": 1} } { "acknowledged": true, "insertedId": 24 } { "acknowledged": true, "insertedId": true } diff --git a/packages/java-shell/src/test/resources/literal/BinData.expected.txt b/packages/java-shell/src/test/resources/literal/BinData.expected.txt index 33608aa46..665919140 100644 --- a/packages/java-shell/src/test/resources/literal/BinData.expected.txt +++ b/packages/java-shell/src/test/resources/literal/BinData.expected.txt @@ -1,4 +1,4 @@ BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "10"}} BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "10"}} -BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "04"}} +UUIDResult: 5220b418-8f7d-4cd9-bd27-35b6f8d990c5 BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "10"}} diff --git a/packages/java-shell/src/test/resources/literal/BinData.js b/packages/java-shell/src/test/resources/literal/BinData.js index c24e287ba..680fbb970 100644 --- a/packages/java-shell/src/test/resources/literal/BinData.js +++ b/packages/java-shell/src/test/resources/literal/BinData.js @@ -4,8 +4,8 @@ db.coll.insertOne({"_id": 1, v: new BinData(16, 'MTIzNA==')}) new BinData(16, 'MTIzNA==') // command checkResultClass BinData(16, 'MTIzNA==') -// command checkResultClass -new BinData(4, 'MTIzNA==') +// command checkResultClass dontReplaceId +new BinData(4, 'UiC0GI99TNm9JzW2+NmQxQ==') // command checkResultClass db.coll.find().toArray()[0].v // clear