helpers/feature-freeze-utils/google-calendar-events.main.kts (57 lines of code) (raw):
@file:Repository("https://repo.maven.apache.org/maven2/")
@file:DependsOn("com.auth0:java-jwt:4.4.0")
@file:DependsOn("com.squareup.okhttp3:okhttp:4.12.0")
@file:DependsOn("com.fasterxml.jackson.core:jackson-databind:2.17.2")
@file:DependsOn("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2")
@file:Import("shared-utils.main.kts")
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.net.URLEncoder
import java.security.KeyFactory
import java.security.interfaces.RSAPrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.time.Instant
import java.util.*
private val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
private fun parsePrivateKey(pem: String): RSAPrivateKey {
val stripped = pem.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replace("\\s".toRegex(), "")
val keyBytes = Base64.getDecoder().decode(stripped)
val spec = PKCS8EncodedKeySpec(keyBytes)
return KeyFactory.getInstance("RSA").generatePrivate(spec) as RSAPrivateKey
}
data class AccessToken(@param:JsonProperty("access_token") val accessToken: String)
private fun getAccessToken(assertion: String): String {
val requestBody =
"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$assertion".toRequestBody("application/x-www-form-urlencoded".toMediaType())
val request = Request.Builder().url("https://oauth2.googleapis.com/token").post(requestBody).build()
return request.sendRequest<AccessToken>()?.accessToken ?: error("Failed to get access token")
}
data class ServiceAccountKey(@param:JsonProperty("private_key") val privateKey: String, @param:JsonProperty("client_email") val clientEmail: String)
data class EventDateTime(@param:JsonProperty("date") val date: String?)
data class Event(@param:JsonProperty("summary") val summary: String?, @param:JsonProperty("start") val start: EventDateTime?)
data class Events(@param:JsonProperty("items") val items: List<Event>?)
fun getCalendarEvents(calendarId: String, serviceAccountKeyJson: String, startTime: Instant, endTime: Instant): List<Event>? {
val keyData = mapper.readValue(serviceAccountKeyJson, ServiceAccountKey::class.java)
val clientEmail = keyData.clientEmail
val privateKeyPem = keyData.privateKey
val now = Instant.now()
val privateKey = parsePrivateKey(privateKeyPem)
val algorithm = Algorithm.RSA256(null, privateKey)
val signedJwt = JWT.create().withIssuer(clientEmail).withSubject(clientEmail).withAudience("https://oauth2.googleapis.com/token")
.withClaim("scope", "https://www.googleapis.com/auth/calendar.readonly").withIssuedAt(now).withExpiresAt(now.plusSeconds(3600))
.sign(algorithm)
val accessToken = getAccessToken(signedJwt)
val minTime = URLEncoder.encode(startTime.toString(), "UTF-8")
val maxTime = URLEncoder.encode(endTime.toString(), "UTF-8")
val encodedCalendarId = URLEncoder.encode(calendarId, "UTF-8")
val eventsUrl =
"""https://www.googleapis.com/calendar/v3/calendars/$encodedCalendarId/events?singleEvents=true&orderBy=startTime&timeMin=$minTime&timeMax=$maxTime"""
val request = Request.Builder().url(eventsUrl).get().addHeader("Authorization", "Bearer $accessToken").build()
return request.sendRequest<Events>()?.items
}