-
Notifications
You must be signed in to change notification settings - Fork 32
/
Ims.kt
100 lines (85 loc) · 3.85 KB
/
Ims.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.cognifide.gradle.aem.common.instance
import com.cognifide.gradle.aem.AemExtension
import com.cognifide.gradle.aem.common.instance.service.ims.AccessObject
import com.cognifide.gradle.aem.common.instance.service.ims.Secret
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import java.io.*
import java.security.KeyFactory
import java.security.spec.PKCS8EncodedKeySpec
/**
* Generates Bearer token based on the credentials fetched from AEMaaCS
* by communicating with Adobe Identity Management Services.
*/
class Ims(private val aem: AemExtension) {
private val common = aem.common
private val expirationTime = aem.obj.long {
convention(86400L)
aem.prop.long("ims.expirationTime")?.let { set(it) }
}
/**
* This is how Adobe calculates expTime in an example they provided for generating token.
*/
private val expirationTimeNormalized get() = System.currentTimeMillis() / 1000 + expirationTime.get()
@Suppress("TooGenericExceptionCaught")
fun generateToken(serviceCredentials: File): String {
if (!serviceCredentials.exists()) {
throw ImsException("Adobe IMS service credentials file does not exist '$serviceCredentials'!")
}
try {
val secret = readCredentialsFile(serviceCredentials)
val jwtToken = generateJWTToken(secret)
val accessObject = fetchAccessObject(jwtToken, secret)
return accessObject.accessToken
} catch (e: Exception) {
throw ImsException("Adobe IMS token could not be generated using provided file '$serviceCredentials'!", e)
}
}
private fun readCredentialsFile(serviceCredentials: File): Secret = serviceCredentials.inputStream().use {
common.formats.toObjectFromJson(it, Secret::class.java)
}
private fun generateJWTToken(secret: Secret): String {
val privateKeyContent = PEMParser(StringReader(secret.integration.privateKey)).use { pemParser ->
val keyPair = JcaPEMKeyConverter().getKeyPair(pemParser.readObject() as PEMKeyPair)
keyPair.private.encoded
}
val keyFactory = KeyFactory.getInstance("RSA")
val keySpec = PKCS8EncodedKeySpec(privateKeyContent)
val rsaPrivateKey = keyFactory.generatePrivate(keySpec)
val imsHost = secret.integration.imsHost
val metaScopes = secret.integration.metascopes.split(",")
val jwtClaims = mapOf<String, Any>(
"iss" to secret.integration.orgId,
"sub" to secret.integration.technicalAccountId,
"exp" to expirationTimeNormalized,
"aud" to "https://$imsHost/c/${secret.integration.technicalAccount.clientId}",
) + metaScopes.associate {
"https://$imsHost/s/$it" to true
}
return Jwts.builder()
.setClaims(jwtClaims)
.signWith(SignatureAlgorithm.RS256, rsaPrivateKey)
.compact()
}
private fun fetchAccessObject(jwtToken: String, secret: Secret): AccessObject {
val imsExchangeEndpoint = "https://${secret.integration.imsHost}/ims/exchange/jwt"
val clientId = secret.integration.technicalAccount.clientId
val clientSecret = secret.integration.technicalAccount.clientSecret
val response = common.http {
post(
imsExchangeEndpoint,
mapOf(
"client_id" to clientId,
"client_secret" to clientSecret,
"jwt_token" to jwtToken
)
) { httpResponse ->
asStream(httpResponse).bufferedReader().use { it.readText() }
}
}
return common.formats.toObjectFromJson(response, AccessObject::class.java)
}
}