Skip to content
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

KSM-473 - Java SDK - Added tryParse and Validate Notation and made it public #544

Merged
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/test.java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Setup, Build and Test
uses: gradle/gradle-build-action@v2
with:
gradle-version: 7.5
gradle-version: 8.4
arguments: build test
build-root-directory: ./sdk/java/core

Expand Down
57 changes: 25 additions & 32 deletions sdk/java/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,28 @@ version = "16.6.2"

plugins {
`java-library`
kotlin("jvm") version "1.6.21"
kotlin("plugin.serialization") version "1.6.21"
kotlin("jvm") version "1.9.20"
kotlin("plugin.serialization") version "1.9.20"
`maven-publish`
signing
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
sourceCompatibility = JavaVersion.VERSION_1_8.toString()
targetCompatibility = JavaVersion.VERSION_1_8.toString()
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
tasks.withType<JavaCompile>().configureEach {
javaCompiler.set(javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(8))
})
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "1.8"
}
}

repositories {
Expand All @@ -35,24 +42,20 @@ repositories {

dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.20"))

// Use the Kotlin JDK 8 standard library.
api("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")

implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")

api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.20")

// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.9.20")

// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")

testImplementation("org.bouncycastle:bc-fips:1.0.2.3")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.9.20")

testImplementation("org.bouncycastle:bc-fips:1.0.2.4")
// testImplementation("org.bouncycastle:bcprov-jdk15on:1.70")
}

Expand All @@ -73,19 +76,12 @@ ext["ossrhPassword"] = null

// Grabbing secrets from local.properties file or from environment variables, which could be used on CI
val secretPropsFile = project.rootProject.file("local.properties")

if (secretPropsFile.exists()) {

// Retrieving variables from the properties file
secretPropsFile.reader().use {
Properties().apply {
load(it)
}
}.onEach { (name, value) ->
ext[name.toString()] = value
}
val localProperties = Properties()
localProperties.load(secretPropsFile.inputStream())
localProperties.forEach { prop -> ext[prop.key.toString()] = prop.value }
} else {

// Retrieving variables from the properties file
ext["signing.keyId"] = System.getenv("SIGNING_KEY_ID")
ext["signing.password"] = System.getenv("SIGNING_PASSWORD")
Expand Down Expand Up @@ -139,7 +135,6 @@ publishing {
name.set("Maksim Ustinov")
email.set("[email protected]")
}

}
contributors {
contributor {
Expand All @@ -154,7 +149,6 @@ publishing {
name.set("Maksim Ustinov")
url.set("https://github.com/maksimu")
}

}
scm {
connection.set("scm:git:git://github.com/Keeper-Security/secrets-manager.git")
Expand Down Expand Up @@ -184,7 +178,6 @@ publishing {
}
}


signing {
sign(publishing.publications["mavenJava"])
}
Expand All @@ -193,4 +186,4 @@ tasks.javadoc {
if (JavaVersion.current().isJava9Compatible) {
(options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
}
}
}
3 changes: 1 addition & 2 deletions sdk/java/core/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
#distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip

zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
24 changes: 15 additions & 9 deletions sdk/java/core/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.2.2/userguide/multi_project_builds.html
*/

rootProject.name = "core"

plugins {
id("org.gradle.toolchains.foojay-resolver") version "0.7.0"
}

@Suppress("UnstableApiUsage")
toolchainManagement {
jvm {
javaRepositories {
repository("foojay") {
resolverClass.set(org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ private fun getFieldJsonValue(field: KeeperRecordField): String {
}

// data class to represent parsed notation section
internal data class NotationSection(
data class NotationSection(
var section: String? = null, // section name - ex. prefix
var isPresent: Boolean = false, // presence flag
var startPos: Int = -1, // section start position in URI
Expand Down Expand Up @@ -586,7 +586,7 @@ private fun parseSection(notation: String, section: String, pos: Int): NotationS
return result
}

internal fun parseNotation(notationUri: String, legacyMode: Boolean = false): List<NotationSection> {
fun parseNotation(notationUri: String, legacyMode: Boolean = false): List<NotationSection> {
var notation = notationUri
if (notation.isEmpty())
throw Exception("Keeper notation is missing or invalid.")
Expand Down Expand Up @@ -643,3 +643,56 @@ internal fun parseNotation(notationUri: String, legacyMode: Boolean = false): Li

return listOf(prefix, record, selector, footer)
}

fun tryParseNotation(notationUri: String, legacyMode: Boolean = false): Pair<List<NotationSection>, String> {
var error = ""
val notations = try {
parseNotation(notationUri, legacyMode)
} catch (e: Exception) {
error = e.message.toString()
emptyList<NotationSection>()
}
return Pair(notations, error)
}

fun validateNotation(notationUri: String, legacyMode: Boolean = false): Boolean {
val (parsedNotation, error) = tryParseNotation(notationUri, legacyMode)
// validate syntax
if (error.isNotBlank()) return false

// check for logical errors
// prefix, record, selector, footer
if (parsedNotation.size < 3) return false

// prefix: parsedNotation[0] == "keeper://" is optional
// record: parsedNotation[1] == UID or Title
if (!parsedNotation[1].isPresent || parsedNotation[1].text == null) return false
// selector: parsedNotation[2] == type|title|notes or file|field|custom_field
if (!parsedNotation[2].isPresent || parsedNotation[2].text == null) return false
// footer: parsedNotation[3] == anything else after a valid notation (should not be present)
if (parsedNotation[3].isPresent) return false
// valid notation section names
if (parsedNotation.firstOrNull { it.section.isNullOrBlank() ||
!listOf("prefix", "record", "selector", "footer").contains(it.section!!.lowercase()) } != null)
return false

val selector = parsedNotation[2].text!!.first.lowercase() // type|title|notes or file|field|custom_field
val parameter = parsedNotation[2].parameter?.first
// short selectors don't have parameters
if (listOf("type", "title", "notes").contains(selector) && parameter != null) return false
// these selectors require a parameter
if (listOf("file", "field", "custom_field").contains(selector) && parameter == null) return false
// file selectors have a single parameter(filename) and don't use indexes
if (selector == "file" && (parsedNotation[2].index1 != null || parsedNotation[2].index2 != null))
return false

// cannot have second index without first
if (parsedNotation[2].index1 == null && parsedNotation[2].index2 != null) return false
// .../<type|label>[index1][index2], ex. /url == /url[] == /url[][] == full value
val idx = parsedNotation[2].index1?.first?.toIntOrNull() ?: -1 // -1 full value
// valid only if [] or missing - ex. /field/phone or /field/phone[]
if (idx == -1 && !(parsedNotation[2].index1?.second.isNullOrEmpty() || parsedNotation[2].index1?.second == "[]"))
return false

return true
}
Loading