From 4f3920b82b3b883a960b0f17dde64090b76871ae Mon Sep 17 00:00:00 2001 From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:46:17 -0500 Subject: [PATCH 1/9] KSM-634 Added links 2 remove parameter for files removal (#766) * bump up to version=17.0.1 * upgrade Gradle and all dependencies * KSM-634 Added links2Remove parameter for files removal --- sdk/java/core/README.md | 3 ++ sdk/java/core/build.gradle.kts | 20 +++++----- .../gradle/wrapper/gradle-wrapper.properties | 2 +- sdk/java/core/settings.gradle.kts | 2 +- .../secretsManager/core/SecretsManager.kt | 40 ++++++++++++++++--- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/sdk/java/core/README.md b/sdk/java/core/README.md index d58986a90..cb7a54c40 100644 --- a/sdk/java/core/README.md +++ b/sdk/java/core/README.md @@ -4,6 +4,9 @@ For more information see our official documentation page https://docs.keeper.io/ # Change Log +## 17.0.1 +- KSM-634 - Added links2Remove parameter for files removal + ## 17.0.0 - KSM-580 - Added new PAM fields diff --git a/sdk/java/core/build.gradle.kts b/sdk/java/core/build.gradle.kts index 2dcf84bed..c866d6160 100644 --- a/sdk/java/core/build.gradle.kts +++ b/sdk/java/core/build.gradle.kts @@ -7,12 +7,12 @@ import java.util.* group = "com.keepersecurity.secrets-manager" // During publishing, If version ends with '-SNAPSHOT' then it will be published to Maven snapshot repository -version = "17.0.0" +version = "17.0.1" plugins { `java-library` - kotlin("jvm") version "2.0.20" - kotlin("plugin.serialization") version "2.0.20" + kotlin("jvm") version "2.2.0" + kotlin("plugin.serialization") version "2.2.0" `maven-publish` signing id("io.github.gradle-nexus.publish-plugin") version "2.0.0" @@ -43,20 +43,20 @@ repositories { dependencies { // Align versions of all Kotlin components - implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.0.20")) + implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.2.0")) // Use the Kotlin JDK 8 standard library. - api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.2") - implementation("org.jetbrains.kotlin:kotlin-reflect:2.0.20") + api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + implementation("org.jetbrains.kotlin:kotlin-reflect:2.2.0") // Use the Kotlin test library. - testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") + testImplementation("org.jetbrains.kotlin:kotlin-test:2.2.0") // Use the Kotlin JUnit integration. - testImplementation("org.jetbrains.kotlin:kotlin-test-junit:2.0.20") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit:2.2.0") - testImplementation("org.bouncycastle:bc-fips:2.0.0") + testImplementation("org.bouncycastle:bc-fips:2.1.0") // testImplementation("org.bouncycastle:bcprov-jdk15on:1.70") } diff --git a/sdk/java/core/gradle/wrapper/gradle-wrapper.properties b/sdk/java/core/gradle/wrapper/gradle-wrapper.properties index 793ed65e1..994b22a0b 100644 --- a/sdk/java/core/gradle/wrapper/gradle-wrapper.properties +++ b/sdk/java/core/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sdk/java/core/settings.gradle.kts b/sdk/java/core/settings.gradle.kts index 7c2709aac..9050c2426 100644 --- a/sdk/java/core/settings.gradle.kts +++ b/sdk/java/core/settings.gradle.kts @@ -1,7 +1,7 @@ rootProject.name = "core" plugins { - id("org.gradle.toolchains.foojay-resolver") version "0.8.0" + id("org.gradle.toolchains.foojay-resolver") version "1.0.0" } @Suppress("UnstableApiUsage") diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt index b65260ff3..19a541e6f 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt @@ -18,7 +18,7 @@ import java.util.* import java.util.concurrent.* import javax.net.ssl.* -const val KEEPER_CLIENT_VERSION = "mj17.0.0" +const val KEEPER_CLIENT_VERSION = "mj17.0.1" const val KEY_HOSTNAME = "hostname" // base url for the Secrets Manager service const val KEY_SERVER_PUBIC_KEY_ID = "serverPublicKeyId" @@ -59,6 +59,11 @@ data class CreateOptions @JvmOverloads constructor( val subFolderUid: String? = null, ) +data class UpdateOptions @JvmOverloads constructor( + val transactionType: UpdateTransactionType? = null, + val linksToRemove: List? = null, +) + typealias QueryFunction = (url: String, transmissionKey: TransmissionKey, payload: EncryptedPayload) -> KeeperHttpResponse data class TransmissionKey(var publicKeyId: Int, var key: ByteArray, val encryptedKey: ByteArray) @@ -94,7 +99,8 @@ private data class UpdatePayload( val recordUid: String, val data: String, val revision: Long, - val transactionType: UpdateTransactionType? = null + val transactionType: UpdateTransactionType? = null, + val links2Remove: List? = null ): CommonPayload() @Serializable @@ -159,6 +165,7 @@ private data class FileUploadPayload( val fileRecordData: String, val ownerRecordUid: String, val ownerRecordData: String, + val ownerRecordRevision: Long, val linkKey: String, val fileSize: Int, // we will not allow upload size > 2GB due to memory constraints ): CommonPayload() @@ -585,7 +592,12 @@ fun deleteFolder(options: SecretsManagerOptions, folderUids: List, force @ExperimentalSerializationApi fun updateSecret(options: SecretsManagerOptions, record: KeeperRecord, transactionType: UpdateTransactionType? = null) { - val payload = prepareUpdatePayload(options.storage, record, transactionType) + updateSecretWithOptions(options, record, UpdateOptions(transactionType, null)) +} + +@ExperimentalSerializationApi +fun updateSecretWithOptions(options: SecretsManagerOptions, record: KeeperRecord, updateOptions: UpdateOptions? = null) { + val payload = prepareUpdatePayload(options.storage, record, updateOptions) postQuery(options, "update_secret", payload) } @@ -948,12 +960,29 @@ private fun prepareDeleteFolderPayload( private fun prepareUpdatePayload( storage: KeyValueStorage, record: KeeperRecord, - transactionType: UpdateTransactionType? = null + updateOptions: UpdateOptions? = null ): UpdatePayload { val clientId = storage.getString(KEY_CLIENT_ID) ?: throw Exception("Client Id is missing from the configuration") + + updateOptions?.linksToRemove?.takeIf { it.isNotEmpty() }?.let { + val frefs = record.data.getField() + if (frefs?.value?.isNotEmpty() == true){ + frefs.value.removeAll(it) + } + } + val recordBytes = stringToBytes(Json.encodeToString(record.data)) val encryptedRecord = encrypt(recordBytes, record.recordKey) - return UpdatePayload(KEEPER_CLIENT_VERSION, clientId, record.recordUid, webSafe64FromBytes(encryptedRecord), record.revision, transactionType) + + return UpdatePayload( + clientVersion = KEEPER_CLIENT_VERSION, + clientId = clientId, + recordUid = record.recordUid, + data = webSafe64FromBytes(encryptedRecord), + revision = record.revision, + transactionType = updateOptions?.transactionType, + links2Remove = updateOptions?.linksToRemove + ) } @ExperimentalSerializationApi @@ -1066,6 +1095,7 @@ private fun prepareFileUploadPayload( webSafe64FromBytes(encryptedFileRecord), ownerRecord.recordUid, webSafe64FromBytes(encryptedOwnerRecord), + ownerRecord.revision, bytesToBase64(encryptedLinkKey), encryptedFileData.size ), From 0f3ac46c642f24c51df14d74576ebfc75becde10 Mon Sep 17 00:00:00 2001 From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:53:04 -0500 Subject: [PATCH 2/9] Update test.java.yml (#768) --- .github/workflows/test.java.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.java.yml b/.github/workflows/test.java.yml index c0ad13bd5..af002b712 100644 --- a/.github/workflows/test.java.yml +++ b/.github/workflows/test.java.yml @@ -16,15 +16,15 @@ jobs: run: working-directory: ./sdk/java/core steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Java ${{ matrix.java-version }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java-version }} - name: Setup, Build and Test - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: gradle-version: 8.4 arguments: build test From 12b0a8acad717a96706c7b53230b96b9a3d63c6d Mon Sep 17 00:00:00 2001 From: Ivan Dimov <78815270+idimov-keeper@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:15:58 -0500 Subject: [PATCH 3/9] downgrade foojay-resolver to 0.9 for compatibility with current GHA java-version matrix --- .github/workflows/test.java.yml | 2 +- sdk/java/core/settings.gradle.kts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.java.yml b/.github/workflows/test.java.yml index af002b712..76fcbe458 100644 --- a/.github/workflows/test.java.yml +++ b/.github/workflows/test.java.yml @@ -26,7 +26,7 @@ jobs: - name: Setup, Build and Test uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 8.4 + gradle-version: '8.14' arguments: build test build-root-directory: ./sdk/java/core diff --git a/sdk/java/core/settings.gradle.kts b/sdk/java/core/settings.gradle.kts index 9050c2426..f705909ce 100644 --- a/sdk/java/core/settings.gradle.kts +++ b/sdk/java/core/settings.gradle.kts @@ -1,7 +1,10 @@ rootProject.name = "core" plugins { - id("org.gradle.toolchains.foojay-resolver") version "1.0.0" + // Note: Versions prior to 1.0.0 require Java 8 or later and Gradle 7.6 or later. + // Versions 1.0.0 and after require Java 17 or later and Gradle 7.6 or later. + // Upgrade to 1.0.0+ entails moving java-version matrix out of GHA and implementing in Gradle + id("org.gradle.toolchains.foojay-resolver") version "0.9.0" } @Suppress("UnstableApiUsage") From 344ae1270ad623c4bbedf685a4e5689b13178e6b Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Tue, 12 Aug 2025 17:34:41 -0400 Subject: [PATCH 4/9] [KA-6991]: read GraphSync links (#732) * [KA-6991]: read GraphSync links Add requestLinks option to QueryOptions and CreateOptions types * [KA-6991]: Add support for links in SecretsManager response * [KA-6991]: Refactor link types in SecretsManager response to KeeperRecordLink * [KA-6991]: Java SDK update Add requestLinks option and support for KeeperRecordLink in SecretsManager * Update SecretsManager.kt * [KA-6991]: Add missing PAM fields and KeeperRecordLink utility methods (#777) RecordData.kt changes: - Host: Add allowSupplyUser field - PamResource: Add adminCredentialRef field - PamRbiConnection: Add recordingIncludeKeys field - PamSettingsConnection: Add allowSupplyUser, recordingIncludeKeys, enableFullWindowDrag, enableWallpaper fields SecretsManager.kt changes: - Add comprehensive KeeperRecordLink utility methods: isAdminUser(), isLaunchCredential(), allowsRotation(), allowsConnections(), allowsPortForwards(), allowsSessionRecording(), allowsTypescriptRecording(), allowsRemoteBrowserIsolation(), rotatesOnTermination(), getLinkDataVersion(), getDecodedData(), hasReadableData() Fixes PAM record serialization errors and provides convenient methods for analyzing link data and permissions. --------- Co-authored-by: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com> Co-authored-by: Max Ustinov --- .../secretsManager/core/RecordData.kt | 7 + .../secretsManager/core/SecretsManager.kt | 165 +++++++++++++++++- 2 files changed, 166 insertions(+), 6 deletions(-) diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt index fc49c3b4b..894371067 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt @@ -346,6 +346,7 @@ data class KeyPairs @JvmOverloads constructor( data class Host @JvmOverloads constructor( val hostName: String? = null, val port: String? = null, + val allowSupplyUser: Boolean? = null, ) @Serializable @@ -402,6 +403,7 @@ data class PamResource @JvmOverloads constructor( val controllerUid: String? = null, val folderUid: String? = null, val resourceRef: MutableList? = null, + val adminCredentialRef: String? = null, val allowedSettings: AllowedSettings? = null ) @@ -587,6 +589,7 @@ data class AppFillers @JvmOverloads constructor( @SerialName("connection") data class PamRbiConnection @JvmOverloads constructor( val protocol: String? = null, + val recordingIncludeKeys: Boolean? = null, val userRecords: MutableList? = null, val allowUrlManipulation: Boolean? = null, val allowedUrlPatterns: String? = null, @@ -632,10 +635,14 @@ data class PamSettingsConnection @JvmOverloads constructor( val protocol: String? = null, val userRecords: MutableList? = null, val port: String? = null, + val allowSupplyUser: Boolean? = null, + val recordingIncludeKeys: Boolean? = null, // Common display and security settings val colorScheme: String? = null, val resizeMethod: String? = null, + val enableFullWindowDrag: Boolean? = null, + val enableWallpaper: Boolean? = null, val security: String? = null, val ignoreCert: Boolean? = null, diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt index 19a541e6f..3c577067b 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt @@ -5,6 +5,7 @@ package com.keepersecurity.secretsManager.core import kotlinx.serialization.* import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import java.net.HttpURLConnection.HTTP_OK @@ -52,6 +53,7 @@ data class SecretsManagerOptions @JvmOverloads constructor( data class QueryOptions @JvmOverloads constructor( val recordsFilter: List = emptyList(), val foldersFilter: List = emptyList(), + val requestLinks: Boolean? = null ) data class CreateOptions @JvmOverloads constructor( @@ -83,7 +85,8 @@ private data class GetPayload( override val clientId: String, var publicKey: String? = null, var requestedRecords: List? = null, - var requestedFolders: List? = null + var requestedFolders: List? = null, + var requestLinks: Boolean? = null ): CommonPayload() @Serializable @@ -191,9 +194,145 @@ private data class SecretsManagerResponseRecord( val revision: Long, val isEditable: Boolean, val files: List?, - val innerFolderUid: String? + val innerFolderUid: String?, + val links: List? = null ) +@Serializable +data class KeeperRecordLink( + val recordUid: String, + val data: String? = null +) { + + /** + * Parse the link data as a JSON object, handling errors gracefully + */ + private fun parseJsonData(): Map? { + if (data == null) return null + + return try { + val decodedData = String(java.util.Base64.getDecoder().decode(data)) + val jsonElement = Json.parseToJsonElement(decodedData) + if (jsonElement is JsonObject) { + jsonElement.entries.associate { (key, value) -> + key to when { + value is JsonPrimitive && value.isString -> value.content + value is JsonPrimitive -> { + // Try to parse as different types + when { + value.content == "true" -> true + value.content == "false" -> false + value.content.toIntOrNull() != null -> value.content.toInt() + value.content.toLongOrNull() != null -> value.content.toLong() + else -> value.content + } + } + else -> value.toString() + } + } + } else { + System.err.println("KeeperRecordLink: Link data is not a valid JSON object") + null + } + } catch (e: Exception) { + System.err.println("KeeperRecordLink: Failed to parse link data - ${e.message}") + null + } + } + + /** + * Get a boolean value from the parsed JSON data + */ + private fun getBooleanValue(key: String): Boolean { + return parseJsonData()?.get(key) as? Boolean ?: false + } + + /** + * Get an integer value from the parsed JSON data + */ + private fun getIntValue(key: String): Int? { + return parseJsonData()?.get(key) as? Int + } + + /** + * Get a string value from the parsed JSON data + */ + private fun getStringValue(key: String): String? { + return parseJsonData()?.get(key) as? String + } + + /** + * Check if the link data indicates admin status for a user + */ + fun isAdminUser(): Boolean = getBooleanValue("is_admin") + + /** + * Check if this is a launch credential link + */ + fun isLaunchCredential(): Boolean = getBooleanValue("is_launch_credential") + + /** + * Check if rotation is allowed based on link settings + */ + fun allowsRotation(): Boolean = getBooleanValue("rotation") + + /** + * Check if connections are allowed based on link settings + */ + fun allowsConnections(): Boolean = getBooleanValue("connections") + + /** + * Check if port forwards are allowed based on link settings + */ + fun allowsPortForwards(): Boolean = getBooleanValue("portForwards") + + /** + * Check if session recording is enabled + */ + fun allowsSessionRecording(): Boolean = getBooleanValue("sessionRecording") + + /** + * Check if TypeScript recording is enabled + */ + fun allowsTypescriptRecording(): Boolean = getBooleanValue("typescriptRecording") + + /** + * Check if remote browser isolation is enabled + */ + fun allowsRemoteBrowserIsolation(): Boolean = getBooleanValue("remoteBrowserIsolation") + + /** + * Check if rotation on termination is enabled + */ + fun rotatesOnTermination(): Boolean = getBooleanValue("rotateOnTermination") + + /** + * Get the link data version (if available) + */ + fun getLinkDataVersion(): Int? = getIntValue("version") + + /** + * Get the decoded JSON data as a string (for debugging/advanced use) + */ + fun getDecodedData(): String? { + if (data == null) return null + return try { + String(java.util.Base64.getDecoder().decode(data)) + } catch (e: Exception) { + System.err.println("KeeperRecordLink: Failed to decode Base64 data - ${e.message}") + null + } + } + + /** + * Check if the link has readable JSON data (vs. encrypted/binary data) + */ + fun hasReadableData(): Boolean { + val decoded = getDecodedData() + return decoded != null && (decoded.startsWith("{") || decoded.startsWith("[")) + } +} + @Serializable private data class SecretsManagerResponseFile( val fileUid: String, @@ -258,7 +397,8 @@ data class KeeperRecord( var innerFolderUid: String? = null, val data: KeeperRecordData, val revision: Long, - val files: List? = null + val files: List? = null, + val links: List? = null ) { fun getPassword(): String? { val passwordField = data.getField() ?: return null @@ -814,7 +954,7 @@ private fun decryptRecord(record: SecretsManagerResponseRecord, recordKey: ByteA // When SDK is behind/ahead of record/field type definitions then // strict mapping between JSON attributes and object properties // will fail on any unknown field/key - currently just log the error - // and continue without the field - nb! field will be lost on save + // and continue without the field - NB! field will be lost on save var recordData: KeeperRecordData? = null try { recordData = Json.decodeFromString(bytesToString(decryptedRecord)) @@ -870,7 +1010,17 @@ private fun decryptRecord(record: SecretsManagerResponseRecord, recordKey: ByteA } } - return if (recordData != null) KeeperRecord(recordKey, record.recordUid, null, null, record.innerFolderUid, recordData, record.revision, files) else null + return if (recordData != null) KeeperRecord( + recordKey, + record.recordUid, + null, + null, + record.innerFolderUid, + recordData, + record.revision, + files, + record.links + ) else null } @ExperimentalSerializationApi @@ -931,7 +1081,10 @@ private fun prepareGetPayload( payload.requestedRecords = queryOptions.recordsFilter } if (queryOptions.foldersFilter.isNotEmpty()) { - payload.requestedRecords = queryOptions.foldersFilter + payload.requestedFolders = queryOptions.foldersFilter + } + if (queryOptions.requestLinks != null) { + payload.requestLinks = queryOptions.requestLinks } } return payload From e1502d75f699e1b22d8dd9a27c0774cc1db5ee46 Mon Sep 17 00:00:00 2001 From: Max Ustinov Date: Tue, 12 Aug 2025 14:56:17 -0700 Subject: [PATCH 5/9] KSM-587: Add logging option to SecretsManager for enhanced error reporting (#705) --- .../secretsManager/core/SecretsManager.kt | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt index 3c577067b..f7b9168d3 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt @@ -43,7 +43,8 @@ interface KeyValueStorage { data class SecretsManagerOptions @JvmOverloads constructor( val storage: KeyValueStorage, val queryFunction: QueryFunction? = null, - val allowUnverifiedCertificate: Boolean = false + val allowUnverifiedCertificate: Boolean = false, + val loggingEnabled: Boolean = true ) { init { testSecureRandom() @@ -545,7 +546,9 @@ fun getSecrets(options: SecretsManagerOptions, recordsFilter: List = emp try { fetchAndDecryptSecrets(options, queryOptions) } catch (e: Exception) { - println(e) + if (options.loggingEnabled) { + println(e) + } } } return secrets @@ -559,7 +562,9 @@ fun getSecrets2(options: SecretsManagerOptions, queryOptions: QueryOptions? = nu try { fetchAndDecryptSecrets(options, queryOptions) } catch (e: Exception) { - println(e) + if (options.loggingEnabled) { + println(e) + } } } return secrets @@ -577,7 +582,9 @@ fun tryGetNotationResults(options: SecretsManagerOptions, notation: String): Lis try { return getNotationResults(options, notation) } catch (e: Exception) { - println(e.message) + if (options.loggingEnabled) { + println(e.message) + } } return emptyList() } @@ -706,7 +713,7 @@ fun getNotationResults(options: SecretsManagerOptions, notation: String): List= 0) 1 else valuesCount - if (res.size != expectedSize) + if (res.size != expectedSize && options.loggingEnabled) println("Notation warning - extracted ${res.size} out of $valuesCount values for '$objPropertyName' property.") if (res.isNotEmpty()) result.addAll(res) @@ -897,7 +904,7 @@ private fun fetchAndDecryptSecrets( if (response.records != null) { response.records.forEach { val recordKey = decrypt(it.recordKey, appKey) - val decryptedRecord = decryptRecord(it, recordKey) + val decryptedRecord = decryptRecord(it, recordKey, options) if (decryptedRecord != null) { records.add(decryptedRecord) } @@ -908,7 +915,7 @@ private fun fetchAndDecryptSecrets( val folderKey = decrypt(folder.folderKey, appKey) folder.records!!.forEach { record -> val recordKey = decrypt(record.recordKey, folderKey) - val decryptedRecord = decryptRecord(record, recordKey) + val decryptedRecord = decryptRecord(record, recordKey, options) if (decryptedRecord != null) { decryptedRecord.folderUid = folder.folderUid decryptedRecord.folderKey = folderKey @@ -930,7 +937,7 @@ private fun fetchAndDecryptSecrets( } @ExperimentalSerializationApi -private fun decryptRecord(record: SecretsManagerResponseRecord, recordKey: ByteArray): KeeperRecord? { +private fun decryptRecord(record: SecretsManagerResponseRecord, recordKey: ByteArray, options: SecretsManagerOptions): KeeperRecord? { val decryptedRecord = decrypt(record.data, recordKey) val files: MutableList = mutableListOf() @@ -985,13 +992,15 @@ private fun decryptRecord(record: SecretsManagerResponseRecord, recordKey: ByteA else -> "Unexpected error: ${e.message}" } - println(""" - Record ${record.recordUid} (type: $recordType) parsing error: - Error: $errorDetails - This may occur if the Keeper Secrets Manager (KSM) SDK version you're using is not compatible with the record's data schema. - Please ensure that you are using the latest version of the KSM SDK. If the issue persists, contact support@keepersecurity.com for assistance. - """.trimIndent()) - + if (options.loggingEnabled) { + println(""" + Record ${record.recordUid} (type: $recordType) parsing error: + Error: $errorDetails + This may occur if the Keeper Secrets Manager (KSM) SDK version you're using is not compatible with the record's data schema. + Please ensure that you are using the latest version of the KSM SDK. If the issue persists, contact support@keepersecurity.com for assistance. + """.trimIndent() + ) + } try { // Attempt to parse with non-strict JSON parser for recovery recordData = nonStrictJson.decodeFromString(bytesToString(decryptedRecord)) @@ -1002,11 +1011,14 @@ private fun decryptRecord(record: SecretsManagerResponseRecord, recordKey: ByteA } else -> "Unexpected error during non-strict parsing: ${e2.message}" } - println(""" - Failed to parse record ${record.recordUid} (type: $recordType) even with non-strict parser. - Error: $secondaryError - Record will be skipped. - """.trimIndent()) + if (options.loggingEnabled) { + println(""" + Failed to parse record ${record.recordUid} (type: $recordType) even with non-strict parser. + Error: $secondaryError + Record will be skipped. + """.trimIndent() + ) + } } } From bb55dd558fa5f11a5fbbe019171f097ee804a706 Mon Sep 17 00:00:00 2001 From: Max Ustinov Date: Tue, 12 Aug 2025 15:01:40 -0700 Subject: [PATCH 6/9] KSM-586: Add recordingIncludeKeys to data classes (#704) Co-authored-by: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com> --- .../com/keepersecurity/secretsManager/core/RecordData.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt index 894371067..a76b69152 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt @@ -591,6 +591,7 @@ data class PamRbiConnection @JvmOverloads constructor( val protocol: String? = null, val recordingIncludeKeys: Boolean? = null, val userRecords: MutableList? = null, + val recordingIncludeKeys: Boolean? = null, val allowUrlManipulation: Boolean? = null, val allowedUrlPatterns: String? = null, val allowedResourceUrlPatterns: String? = null, @@ -668,6 +669,8 @@ data class PamSettingsConnection @JvmOverloads constructor( val preconnectionId: String? = null, val preconnectionBlob: String? = null, val disableAudio: Boolean? = null, + val enableWallpaper: Boolean? = null, + val enableFullWindowDrag: Boolean? = null, val sftp: SFTPConnection? = null, // Telnet specific fields From 0f121eebbb7e450490066e0bbc0486b5d847a04e Mon Sep 17 00:00:00 2001 From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:08:06 -0500 Subject: [PATCH 7/9] Update README.md --- sdk/java/core/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sdk/java/core/README.md b/sdk/java/core/README.md index cb7a54c40..0e10827fe 100644 --- a/sdk/java/core/README.md +++ b/sdk/java/core/README.md @@ -4,11 +4,14 @@ For more information see our official documentation page https://docs.keeper.io/ # Change Log -## 17.0.1 -- KSM-634 - Added links2Remove parameter for files removal - -## 17.0.0 +## 17.1.0 - KSM-580 - Added new PAM fields +- KSM-581 - Add GraphSync library to KSM SDK +- KSM-582 - fix NPE use safe cast in KeeperRecordData.getField() + -KSM-586 - Add recordingIncludeKeys to data classes +- KSM-587 - Add logging option +- KSM-627 - Java SDK Add GraphSync links +- KSM-634 - Added links2Remove parameter for files removal ## 16.6.6 - KSM-560 - Improved error handling when parsing JSON From a0f2de6e9b25a4b9c2f7ed5806a8ca421f4e4a81 Mon Sep 17 00:00:00 2001 From: Ivan Dimov <78815270+idimov-keeper@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:22:27 -0500 Subject: [PATCH 8/9] bump up versions --- sdk/java/core/build.gradle.kts | 4 ++-- sdk/java/core/gradle.properties | 1 + sdk/java/core/gradle/wrapper/gradle-wrapper.properties | 2 +- .../com/keepersecurity/secretsManager/core/SecretsManager.kt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 sdk/java/core/gradle.properties diff --git a/sdk/java/core/build.gradle.kts b/sdk/java/core/build.gradle.kts index c866d6160..ef148416d 100644 --- a/sdk/java/core/build.gradle.kts +++ b/sdk/java/core/build.gradle.kts @@ -7,7 +7,7 @@ import java.util.* group = "com.keepersecurity.secrets-manager" // During publishing, If version ends with '-SNAPSHOT' then it will be published to Maven snapshot repository -version = "17.0.1" +version = "17.1.0" plugins { `java-library` @@ -56,7 +56,7 @@ dependencies { // Use the Kotlin JUnit integration. testImplementation("org.jetbrains.kotlin:kotlin-test-junit:2.2.0") - testImplementation("org.bouncycastle:bc-fips:2.1.0") + testImplementation("org.bouncycastle:bc-fips:2.1.1") // testImplementation("org.bouncycastle:bcprov-jdk15on:1.70") } diff --git a/sdk/java/core/gradle.properties b/sdk/java/core/gradle.properties new file mode 100644 index 000000000..3a3b0f87e --- /dev/null +++ b/sdk/java/core/gradle.properties @@ -0,0 +1 @@ +org.gradle.configuration-cache=true diff --git a/sdk/java/core/gradle/wrapper/gradle-wrapper.properties b/sdk/java/core/gradle/wrapper/gradle-wrapper.properties index 994b22a0b..8461cf9cb 100644 --- a/sdk/java/core/gradle/wrapper/gradle-wrapper.properties +++ b/sdk/java/core/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt index f7b9168d3..b8a05fed3 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/SecretsManager.kt @@ -19,7 +19,7 @@ import java.util.* import java.util.concurrent.* import javax.net.ssl.* -const val KEEPER_CLIENT_VERSION = "mj17.0.1" +const val KEEPER_CLIENT_VERSION = "mj17.1.0" const val KEY_HOSTNAME = "hostname" // base url for the Secrets Manager service const val KEY_SERVER_PUBIC_KEY_ID = "serverPublicKeyId" From f5e05c233fd43c475cff21eb5ee8fd44d9feffb4 Mon Sep 17 00:00:00 2001 From: Ivan Dimov <78815270+idimov-keeper@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:24:31 -0500 Subject: [PATCH 9/9] removed duplicate data class properties --- .../com/keepersecurity/secretsManager/core/RecordData.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt index a76b69152..7d4ca5ac7 100644 --- a/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt +++ b/sdk/java/core/src/main/kotlin/com/keepersecurity/secretsManager/core/RecordData.kt @@ -589,7 +589,6 @@ data class AppFillers @JvmOverloads constructor( @SerialName("connection") data class PamRbiConnection @JvmOverloads constructor( val protocol: String? = null, - val recordingIncludeKeys: Boolean? = null, val userRecords: MutableList? = null, val recordingIncludeKeys: Boolean? = null, val allowUrlManipulation: Boolean? = null, @@ -642,8 +641,6 @@ data class PamSettingsConnection @JvmOverloads constructor( // Common display and security settings val colorScheme: String? = null, val resizeMethod: String? = null, - val enableFullWindowDrag: Boolean? = null, - val enableWallpaper: Boolean? = null, val security: String? = null, val ignoreCert: Boolean? = null,