in src/main/kotlin/org/jetbrains/teamcity/github/controllers/GitHubWebHookListener.kt [89:194]
override fun doHandle(request: HttpServletRequest, response: HttpServletResponse): ModelAndView? {
//TODO: Check User-Agent
// From documentation: Also, the User-Agent for the requests will have the prefix GitHub-Hookshot/.
val eventType: String? = request.getHeader(X_GitHub_Event)
@Suppress("IfNullToElvis")
if (eventType == null) {
return simpleText(response, SC_BAD_REQUEST, "'$X_GitHub_Event' header is missing")
}
if (!SupportedEvents.contains(eventType)) {
LOG.info("Received unsupported event type '$eventType', ignoring")
return simpleText(response, SC_ACCEPTED, "Unsupported event type '$eventType'")
}
val signature = request.getHeader(X_Hub_Signature)
if (signature == null || signature.isBlank()) {
LOG.warn("Received event without signature ($X_Hub_Signature header)")
return simpleText(response, SC_BAD_REQUEST, "'$X_Hub_Signature' header is missing")
}
val path = WebUtil.getPathWithoutAuthenticationType(request)
val pubKey: String? = getPubKeyFromRequestPath(path)
if (pubKey == null) {
LOG.warn("Received hook event with signature header but without public key part of url")
return simpleText(response, SC_BAD_REQUEST, "'$X_Hub_Signature' is present but request url does not contains public key part")
}
LOG.debug("Received hook event with public key in path: $pubKey")
val authData = getAuthData(pubKey)
if (authData == null) {
LOG.warn("No stored auth data (secret key) found for public key '$pubKey'")
return simpleText(response, SC_NOT_FOUND, "No stored auth data (secret key) found for public key '$pubKey'. Seems hook created not by this TeamCity server. Reinstall hook via TeamCity UI.")
}
val userId = authData.userId
val user = UserModel.findUserById(userId)
if (user == null) {
AuthDataStorage.removeAllForUser(userId)
LOG.warn("User with id '$userId' not found")
return simpleText(response, SC_NOT_FOUND, "User installed webhook no longer registered in TeamCity. Remove and reinstall webhook.")
}
val hookInfo = getHookInfoWithWaiting(authData, eventType)
if (hookInfo == null) {
// Seems local cache was cleared or it's a organization hook
LOG.warn("No stored hook info found for public key '$pubKey' and repository '${authData.repository}'")
}
if (request.contentLength >= MaxPayloadSize) {
val message = "Payload size exceed ${StringUtil.formatFileSize(MaxPayloadSize, 0)} limit"
LOG.info("$message. Requests url: $path")
return simpleText(response, SC_REQUEST_ENTITY_TOO_LARGE, message)
}
val content: ByteArray
try {
content = LimitInputStream(request.inputStream, MaxPayloadSize).readBytes()
} catch(e: IOException) {
LOG.warnAndDebugDetails("Failed to read payload of $eventType event", e)
return simpleText(response, SC_SERVICE_UNAVAILABLE, "Failed to read payload: ${e.message}")
} finally {
FileUtil.close(request.inputStream)
}
if (content.size >= MaxPayloadSize) {
val message = "Payload size exceed ${StringUtil.formatFileSize(MaxPayloadSize, 0)} limit"
LOG.info("$message. Requests url: $path")
return simpleText(response, SC_REQUEST_ENTITY_TOO_LARGE, message)
}
if (!HMacUtil.checkHMac(content, authData.secret.toByteArray(charset("UTF-8")), signature)) {
LOG.warn("HMac verification failed for $eventType event")
return simpleText(response, SC_FORBIDDEN, "Payload signature verification failed. Ensure request url, '$X_Hub_Signature' header and payload are correct")
}
val contentReader = BufferedReader(InputStreamReader(ByteArrayInputStream(content), request.characterEncoding ?: "UTF-8"))
try {
when (eventType) {
"ping" -> {
val payload = GsonUtilsEx.fromJson(contentReader, PingWebHookPayload::class.java)
val pair = doHandlePingEvent(payload, hookInfo, user)
return pair?.let { simpleText(response, pair.first, pair.second) }
}
"push" -> {
val payload = GsonUtilsEx.fromJson(contentReader, PushWebHookPayload::class.java)
val pair = doHandlePushEvent(payload, hookInfo, user)
return pair?.let { simpleText(response, pair.first, pair.second) }
}
"pull_request" -> {
val payload = GsonUtilsEx.fromJson(contentReader, PullRequestPayloadEx::class.java)
val pair = doHandlePullRequestEvent(payload, hookInfo, user)
return pair?.let { simpleText(response, pair.first, pair.second) }
}
}
} catch(e: Exception) {
val message = if (e is JsonSyntaxException || e is JsonIOException) {
"Failed to parse payload"
} else {
"Failed to process request (event type is '$eventType')"
}
LOG.warnAndDebugDetails(message, e)
return simpleText(response, SC_SERVICE_UNAVAILABLE, message + ": ${e.message}")
}
return null
}