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

New credentials #2

Merged
merged 12 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
6 changes: 4 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
pull_request:
branches:
- main
- v2

jobs:
test:
Expand All @@ -30,11 +31,12 @@ jobs:
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add - <<< "${{ secrets.GH_SSH_PRIVATE }}"

- name: create credentials.yaml file for the tests to read the keys
- name: create credentials.json files for the tests to read the keys
run: |
mkdir -p ~/.config/high-mobility/fleet-sdk/
# ' will not escape "" inside the yaml file
echo '${{ secrets.CREDENTIALS_YAML }}' > ~/.config/high-mobility/fleet-sdk/credentials.yaml
echo '${{ secrets.CREDENTIALS_JSON }}' > ~/.config/high-mobility/fleet-sdk/credentials.json
echo '${{ secrets.CREDENTIALS_JSON_PRIVATE_KEY }}' > ~/.config/high-mobility/fleet-sdk/credentialsPrivateKey.json

- name: Update submodules
env:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,5 @@ credentials
vehicleAccess.yaml
vehicleAccess.json
private-key.json
credentialsPrivateKey.json
credentialsOAuth.json
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ subprojects {
}

ext {
kotlin_version = "1.8.20"
kotlin_version = "1.9.20"

depLocation = 0
coroutinesVersion = '1.7.3'
koinVersion = '3.4.3'
koinVersion = '3.5.0'
ver = [
"hmkit-crypto-telematics": "0.1",
"hmkit-auto-api" : "13.1.1",
Expand Down
23 changes: 9 additions & 14 deletions hmkit-fleet/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,32 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion"

implementation('org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0-RC')
implementation('org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0')

if (!project.hasProperty('depLocation') || project.depLocation > 0) {
api deps.crypto
} else {
api project(":hmkit-crypto-telematics")
}

// web
api('com.squareup.okhttp3:okhttp:4.10.0')
api('com.squareup.okhttp3:okhttp:4.12.0')
slashmili marked this conversation as resolved.
Show resolved Hide resolved

// Koin
implementation "io.insert-koin:koin-core:$koinVersion"

// logging
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'org.slf4j:slf4j-api:2.0.9'

// test
testImplementation deps.autoApi
testImplementation "io.insert-koin:koin-test:$koinVersion"

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1'

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

testImplementation 'org.slf4j:slf4j-simple:1.7.36'
testImplementation 'io.mockk:mockk:1.12.5'
testImplementation 'com.charleskorn.kaml:kaml:0.47.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0'
testImplementation 'org.slf4j:slf4j-simple:2.0.9'
testImplementation 'io.mockk:mockk:1.13.7'
testImplementation 'com.charleskorn.kaml:kaml:0.55.0'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'

detektPlugins('io.gitlab.arturbosch.detekt:detekt-formatting:1.23.0')
}
Expand Down
40 changes: 25 additions & 15 deletions hmkit-fleet/src/main/kotlin/HMKitConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,33 @@ package com.highmobility.hmkitfleet
import okhttp3.OkHttpClient

class HMKitConfiguration private constructor(builder: Builder) {
val client: OkHttpClient
val credentials = builder.credentials ?: throw IllegalArgumentException("credentials must be set")
val client = builder.client ?: OkHttpClient()
val environment = builder.environment

init {
client = builder.client ?: OkHttpClient()
}
class Builder {
var credentials: HMKitCredentials? = null
private set
var environment: HMKitFleet.Environment = HMKitFleet.Environment.PRODUCTION
private set
var client: OkHttpClient? = null
private set

class Builder {
var client: OkHttpClient? = null
private set
/**
* Set the credentials to be used for the SDK. Choose from either [HMKitOAuthCredentials] or [HMKitPrivateKeyCredentials]. This is a mandatory field.
*/
fun credentials(credentials: HMKitCredentials) = apply { this.credentials = credentials }

fun client(client: OkHttpClient) = apply { this.client = client }
fun build() = HMKitConfiguration(this)
}
/**
* Set the SDK environment. Default is Production.
*/
fun environment(environment: HMKitFleet.Environment) = apply { this.environment = environment }

companion object {
fun defaultConfiguration(): HMKitConfiguration {
return Builder().build()
}
}
/**
* Optionally, set the OkHttpClient to be used for network requests.
*/
fun client(client: OkHttpClient) = apply { this.client = client }

fun build() = HMKitConfiguration(this)
}
}
107 changes: 107 additions & 0 deletions hmkit-fleet/src/main/kotlin/HMKitCredentials.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.highmobility.hmkitfleet

import com.highmobility.crypto.Crypto
import com.highmobility.crypto.value.PrivateKey
import com.highmobility.utils.Base64
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

abstract class HMKitCredentials {
internal abstract fun getTokenRequestBody(jwtProvider: JwtProvider?): String

interface JwtProvider {
fun getBaseUrl(): String
fun getCrypto(): Crypto
fun generateUuid(): String
fun getTimestamp(): Long
}
}

@Serializable
data class HMKitOAuthCredentials(
/**
* The OAuth client ID.
*/
val clientId: String,

/**
* The OAuth client secret.
*/
val clientSecret: String,
) : HMKitCredentials() {
override fun getTokenRequestBody(
jwtProvider: JwtProvider?
): String {
return buildJsonObject {
put("client_id", clientId)
put("client_secret", clientSecret)
put("grant_type", "client_credentials")
}.toString()
}
}

@Serializable
data class HMKitPrivateKeyCredentials(
/**
* The OAuth client ID.
*/
val clientId: String,
/**
* The PKCS8 formatted private key. It is included in the .json file downloaded from the developer console after creating a new private key in the App>OAuth section.
*/
val privateKey: String,
/**
* The private key ID. It is included in the .json file downloaded from the developer console after creating a new private key in the App>OAuth section.
*/
val privateKeyId: String
) : HMKitCredentials() {
override fun getTokenRequestBody(
jwtProvider: JwtProvider?
): String {
val jwt = getJwt(
privateKey,
privateKeyId,
jwtProvider!!
)

return buildJsonObject {
put("client_id", clientId)
put("client_assertion", jwt)
put("grant_type", "client_credentials")
put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
}.toString()
}
}

internal fun getJwt(
privateKey: String,
privateKeyId: String,
jwtProvider: HMKitCredentials.JwtProvider
): String {
val crypto: Crypto = jwtProvider.getCrypto()
slashmili marked this conversation as resolved.
Show resolved Hide resolved
val baseUrl = jwtProvider.getBaseUrl()
val uuid = jwtProvider.generateUuid()
val timestamp = jwtProvider.getTimestamp()

val header = buildJsonObject {
put("alg", "ES256")
put("typ", "JWT")
}.toString()

val jwtBody = buildJsonObject {
put("ver", 2)
put("iss", privateKeyId)
put("aud", baseUrl)
put("jti", uuid)
put("iat", timestamp / 1000)
}.toString()

val hmPrivateKey = PrivateKey(privateKey, PrivateKey.Format.PKCS8)
val headerBase64 = Base64.encodeUrlSafe(header.toByteArray())
val bodyBase64 = Base64.encodeUrlSafe(jwtBody.toByteArray())
val jwtContent = String.format("%s.%s", headerBase64, bodyBase64)
val jwtSignature = crypto.signJWT(jwtContent.toByteArray(), hmPrivateKey)

return String.format("%s.%s", jwtContent, jwtSignature.base64UrlSafe)
}
Loading
Loading