blob: 2e4141826cb8757297c1f1ab6df6bbf724ad69bc [file] [log] [blame]
// Copyright 2022 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:github/github.dart';
import 'package:shelf/shelf.dart';
import 'package:crypto/crypto.dart';
import '../request_handling/pubsub.dart';
import '../service/config.dart';
import '../service/log.dart';
import '../server/request_handler.dart';
import '../requests/exceptions.dart';
/// Handler for processing GitHub webhooks.
///
/// On events where an 'autosubmit' label was added to a pull request,
/// check if the pull request is mergable and publish to pubsub.
class GithubWebhook extends RequestHandler {
const GithubWebhook({
required super.config,
this.pubsub = const PubSub(),
});
final PubSub pubsub;
@override
Future<Response> post(Request request) async {
final Map<String, String> reqHeader = request.headers;
log.info('Header: $reqHeader');
final String? gitHubEvent = request.headers['X-GitHub-Event'];
if (gitHubEvent == null || request.headers['X-Hub-Signature'] == null) {
throw const BadRequestException('Missing required headers.');
}
final List<int> requestBytes = await request.read().expand((_) => _).toList();
final String? hmacSignature = request.headers['X-Hub-Signature'];
if (!await _validateRequest(hmacSignature, requestBytes)) {
throw const Forbidden();
}
// Listen to the pull request with 'autosubmit' label.
bool hasAutosubmit = false;
bool hasRevertLabel = false;
final String rawBody = utf8.decode(requestBytes);
final body = json.decode(rawBody) as Map<String, dynamic>;
if (!body.containsKey('pull_request') || !((body['pull_request'] as Map<String, dynamic>).containsKey('labels'))) {
return Response.ok(jsonEncode(<String, String>{}));
}
final PullRequest pullRequest = PullRequest.fromJson(body['pull_request'] as Map<String, dynamic>);
hasAutosubmit = pullRequest.labels!.any((label) => label.name == Config.kAutosubmitLabel);
hasRevertLabel = pullRequest.labels!.any((label) => label.name == Config.kRevertLabel);
if (hasAutosubmit || hasRevertLabel) {
log.info('Found pull request with auto submit and/or revert label.');
await pubsub.publish('auto-submit-queue', pullRequest);
}
return Response.ok(rawBody);
}
Future<bool> _validateRequest(
String? signature,
List<int> requestBody,
) async {
final String rawKey = await config.getWebhookKey();
final List<int> key = utf8.encode(rawKey);
final Hmac hmac = Hmac(sha1, key);
final Digest digest = hmac.convert(requestBody);
final String bodySignature = 'sha1=$digest';
return bodySignature == signature;
}
}