Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35c7712f85 | |||
| 84d09f6dbb | |||
| c5ff6e555a |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,5 +39,5 @@ out/
|
||||
### Kotlin ###
|
||||
.kotlin
|
||||
|
||||
### cert ###
|
||||
cert/
|
||||
#### Hlæja ###
|
||||
/cert/
|
||||
|
||||
@@ -16,6 +16,7 @@ Classes and endpoints, to shape and to steer, Devices and sensors, their purpose
|
||||
| spring.data.redis.host | ✓ | Redis host |
|
||||
| spring.data.redis.port | | Redis port |
|
||||
| spring.data.redis.database | ✓ | Redis database |
|
||||
| spring.data.redis.password | ✗ | Redis password |
|
||||
| cache.time-to-live | | Cache time to live (minutes) |
|
||||
| jwt.public-key | ✓ | JWT public key |
|
||||
| device-registry.url | ✓ | Device Register URL |
|
||||
|
||||
@@ -17,14 +17,12 @@ dependencies {
|
||||
implementation(hlaeja.kotlinx.coroutines)
|
||||
implementation(hlaeja.micrometer.registry.influx)
|
||||
implementation(hlaeja.library.hlaeja.common.messages)
|
||||
implementation(hlaeja.library.hlaeja.jwt)
|
||||
implementation(hlaeja.springboot.starter.actuator)
|
||||
implementation(hlaeja.springboot.starter.cache)
|
||||
implementation(hlaeja.springboot.starter.redis)
|
||||
implementation(hlaeja.springboot.starter.webflux)
|
||||
|
||||
runtimeOnly(hlaeja.jjwt.impl)
|
||||
runtimeOnly(hlaeja.jjwt.jackson)
|
||||
|
||||
testImplementation(hlaeja.kotlin.test.junit5)
|
||||
testImplementation(hlaeja.kotlinx.coroutines.test)
|
||||
testImplementation(hlaeja.mockk)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
kotlin.code.style=official
|
||||
org.gradle.jvmargs=-Xmx1g
|
||||
version=0.3.0
|
||||
catalog=0.7.0
|
||||
version=0.4.0
|
||||
catalog=0.8.0
|
||||
docker.port.expose=8443
|
||||
container.port.expose=8443
|
||||
container.port.host=9000
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package ltd.hlaeja.controller
|
||||
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||
import ltd.hlaeja.service.DeviceConfigurationService
|
||||
import ltd.hlaeja.service.JwtService
|
||||
import ltd.hlaeja.service.DeviceRegistryService
|
||||
import ltd.hlaeja.util.toDeviceResponse
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestHeader
|
||||
@@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RequestMapping("/configuration")
|
||||
class ConfigurationController(
|
||||
private val configurationService: DeviceConfigurationService,
|
||||
private val jwtService: JwtService,
|
||||
private val deviceRegistry: DeviceRegistryService,
|
||||
private val publicJwtService: PublicJwtService,
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
suspend fun getNodeConfiguration(
|
||||
@RequestHeader("Identity") identityToken: String,
|
||||
): Map<String, String> = jwtService.getIdentity(identityToken)
|
||||
): Map<String, String> = readIdentityToken(identityToken)
|
||||
.let { deviceRegistry.getIdentityFromDevice(it) }
|
||||
.let { configurationService.getConfiguration(it.node).toDeviceResponse() }
|
||||
|
||||
private fun readIdentityToken(identityToken: String): UUID = publicJwtService
|
||||
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package ltd.hlaeja.controller
|
||||
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.jwt.service.PublicJwtService
|
||||
import ltd.hlaeja.library.deviceData.MeasurementData
|
||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||
import ltd.hlaeja.service.DeviceDataService
|
||||
import ltd.hlaeja.service.JwtService
|
||||
import ltd.hlaeja.service.DeviceRegistryService
|
||||
import org.springframework.http.HttpStatus.CREATED
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
@@ -16,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RequestMapping("/measurement")
|
||||
class MeasurementController(
|
||||
private val dataService: DeviceDataService,
|
||||
private val jwtService: JwtService,
|
||||
private val deviceRegistry: DeviceRegistryService,
|
||||
private val publicJwtService: PublicJwtService,
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
suspend fun getNodeMeasurement(
|
||||
@RequestHeader("Identity") identityToken: String,
|
||||
): Map<String, Number> = jwtService.getIdentity(identityToken)
|
||||
): Map<String, Number> = readIdentityToken(identityToken)
|
||||
.let { dataService.getMeasurement(it.client, it.node).fields }
|
||||
|
||||
@PostMapping
|
||||
@@ -31,7 +35,7 @@ class MeasurementController(
|
||||
@RequestHeader("Identity") identityToken: String,
|
||||
@RequestBody measurement: Map<String, Number>,
|
||||
) {
|
||||
return jwtService.getIdentity(identityToken)
|
||||
return readIdentityToken(identityToken)
|
||||
.let {
|
||||
dataService.addMeasurement(
|
||||
it.client,
|
||||
@@ -45,4 +49,8 @@ class MeasurementController(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readIdentityToken(identityToken: String): Identity.Response = publicJwtService
|
||||
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
|
||||
.let { deviceRegistry.getIdentityFromDevice(it) }
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package ltd.hlaeja.service
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.jsonwebtoken.JwtException
|
||||
import io.jsonwebtoken.JwtParser
|
||||
import io.jsonwebtoken.Jwts
|
||||
import java.util.UUID
|
||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||
import ltd.hlaeja.property.JwtProperty
|
||||
import ltd.hlaeja.util.PublicKeyProvider
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
private val log = KotlinLogging.logger {}
|
||||
|
||||
@Service
|
||||
class JwtService(
|
||||
jwtProperty: JwtProperty,
|
||||
private val deviceRegistry: DeviceRegistryService,
|
||||
) {
|
||||
|
||||
private val parser: JwtParser = Jwts.parser()
|
||||
.verifyWith(PublicKeyProvider.load(jwtProperty.publicKey))
|
||||
.build()
|
||||
|
||||
suspend fun getIdentity(
|
||||
identityToken: String,
|
||||
): Identity.Response = try {
|
||||
readIdentity(identityToken)
|
||||
.let { deviceRegistry.getIdentityFromDevice(it) }
|
||||
} catch (e: JwtException) {
|
||||
log.warn { e.localizedMessage }
|
||||
throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||
}
|
||||
|
||||
private suspend fun readIdentity(
|
||||
identity: String,
|
||||
): UUID = parser.parseSignedClaims(identity)
|
||||
.let { UUID.fromString(it.payload["device"] as String) }
|
||||
.also { log.debug { "Identified client device: $it" } }
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package ltd.hlaeja.util
|
||||
|
||||
import java.security.KeyFactory
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.Base64.getDecoder
|
||||
import ltd.hlaeja.exception.KeyProviderException
|
||||
|
||||
object PublicKeyProvider {
|
||||
|
||||
fun load(
|
||||
pemFile: String,
|
||||
): RSAPublicKey = readPublicPemFile(pemFile)
|
||||
.let(::makePublicKey)
|
||||
|
||||
private fun makePublicKey(
|
||||
publicKeyBytes: ByteArray,
|
||||
): RSAPublicKey = KeyFactory.getInstance("RSA")
|
||||
.generatePublic(X509EncodedKeySpec(publicKeyBytes)) as RSAPublicKey
|
||||
|
||||
private fun readPublicPemFile(
|
||||
publicKey: String,
|
||||
): ByteArray = javaClass.classLoader
|
||||
.getResource(publicKey)
|
||||
?.readText()
|
||||
?.let(::getPublicKeyByteArray)
|
||||
?: throw KeyProviderException("Could not load public key")
|
||||
|
||||
private fun getPublicKeyByteArray(
|
||||
keyText: String,
|
||||
): ByteArray = keyText.replace(Regex("[\r\n]+"), "")
|
||||
.removePrefix("-----BEGIN PUBLIC KEY-----")
|
||||
.removeSuffix("-----END PUBLIC KEY-----")
|
||||
.let { getDecoder().decode(it) }
|
||||
}
|
||||
@@ -20,11 +20,6 @@
|
||||
"type": "java.lang.String",
|
||||
"description": "Application build os version."
|
||||
},
|
||||
{
|
||||
"name": "jwt.public-key",
|
||||
"type": "java.lang.String",
|
||||
"description": "Jwt public key file."
|
||||
},
|
||||
{
|
||||
"name": "device-registry.url",
|
||||
"type": "java.lang.String",
|
||||
|
||||
9
src/test/resources/cert/valid-public-key.pem
Normal file
9
src/test/resources/cert/valid-public-key.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ZdlbISX729m5Ur1pVhg
|
||||
XIvazcgUt0T0G32ML0tfwQ4aWTfwPII0SQRThaN6eiiBMRa0V8JMih1LT8JmGgst
|
||||
dEx2nhMbVs/Osu8MhmP86c+HB/jPa1+0IR1TZKXoZoF52D2ZtoVf+mOWggAcm1R+
|
||||
V0Fj2cR/pgLkVt3GKUE2OokFC1iFUQFjThd1EzKcOv53TUek8FY8t66npQ4t3unD
|
||||
bXZKoGXMuXCqZVykMbGTUQFRuT3NAOXRrJP+UDeY2uM2Yk98J+8FtLDYD6jpmyi0
|
||||
ghv6k8pK1w1n5NI3atVv5ZMUeQZ36AXL8SZi1105mamhLVQ0e0JixoMOPh7ziFyv
|
||||
uwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
Reference in New Issue
Block a user