added secret verification to webhook endpoint w/ burnettk

This commit is contained in:
jasquat 2022-12-09 16:51:00 -05:00
parent d7221690f0
commit 6122fb0ae5
7 changed files with 63 additions and 8 deletions

View File

@ -61,8 +61,9 @@ SPIFFWORKFLOW_BACKEND_LOG_LEVEL = environ.get(
# When a user clicks on the `Publish` button, this is the default branch this server merges into. # When a user clicks on the `Publish` button, this is the default branch this server merges into.
# I.e., dev server could have `staging` here. Staging server might have `production` here. # I.e., dev server could have `staging` here. Staging server might have `production` here.
GIT_BRANCH_TO_PUBLISH_TO = environ.get("GIT_BRANCH_TO_PUBLISH_TO", default="staging") GIT_BRANCH_TO_PUBLISH_TO = environ.get("GIT_BRANCH_TO_PUBLISH_TO")
GIT_CLONE_URL_FOR_PUBLISHING = environ.get("GIT_CLONE_URL", default=None) GIT_BRANCH = environ.get("GIT_BRANCH")
GIT_CLONE_URL_FOR_PUBLISHING = environ.get("GIT_CLONE_URL")
GIT_COMMIT_ON_SAVE = environ.get("GIT_COMMIT_ON_SAVE", default="false") == "true" GIT_COMMIT_ON_SAVE = environ.get("GIT_COMMIT_ON_SAVE", default="false") == "true"
# Datbase Configuration # Datbase Configuration

View File

@ -17,3 +17,5 @@ GIT_CLONE_URL_FOR_PUBLISHING = environ.get(
) )
GIT_USERNAME = "sartography-automated-committer" GIT_USERNAME = "sartography-automated-committer"
GIT_USER_EMAIL = f"{GIT_USERNAME}@users.noreply.github.com" GIT_USER_EMAIL = f"{GIT_USERNAME}@users.noreply.github.com"
GIT_BRANCH_TO_PUBLISH_TO = "main"
GIT_BRANCH = "main"

View File

@ -0,0 +1,4 @@
"""staging."""
GIT_BRANCH = "staging"
GIT_BRANCH_TO_PUBLISH_TO = "main"
GIT_COMMIT_ON_SAVE = False

View File

@ -1826,8 +1826,12 @@ def get_spiff_task_from_process_instance(
# where 7000 is the port the app is running on locally # where 7000 is the port the app is running on locally
def github_webhook_receive(body: Dict) -> Response: def github_webhook_receive(body: Dict) -> Response:
"""Github_webhook_receive.""" """Github_webhook_receive."""
GitService.handle_web_hook(body) auth_header = request.headers.get("X-Hub-Signature-256")
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") AuthorizationService.verify_sha256_token(auth_header)
result = GitService.handle_web_hook(body)
return Response(
json.dumps({"git_pull": result}), status=200, mimetype="application/json"
)
# #

View File

@ -1,6 +1,9 @@
"""Authorization_service.""" """Authorization_service."""
import inspect import inspect
import re import re
from hashlib import sha256
from hmac import compare_digest
from hmac import HMAC
from typing import Optional from typing import Optional
from typing import Union from typing import Union
@ -45,6 +48,27 @@ class UserDoesNotHaveAccessToTaskError(Exception):
class AuthorizationService: class AuthorizationService:
"""Determine whether a user has permission to perform their request.""" """Determine whether a user has permission to perform their request."""
# https://stackoverflow.com/a/71320673/6090676
@classmethod
def verify_sha256_token(cls, auth_header: Optional[str]) -> None:
"""Verify_sha256_token."""
if auth_header is None:
raise ApiError(
error_code="unauthorized",
message="",
status_code=403,
)
received_sign = auth_header.split("sha256=")[-1].strip()
secret = current_app.config["GITHUB_WEBHOOK_SECRET"].encode()
expected_sign = HMAC(key=secret, msg=request.data, digestmod=sha256).hexdigest()
if not compare_digest(received_sign, expected_sign):
raise ApiError(
error_code="unauthorized",
message="",
status_code=403,
)
@classmethod @classmethod
def has_permission( def has_permission(
cls, principals: list[PrincipalModel], permission: str, target_uri: str cls, principals: list[PrincipalModel], permission: str, target_uri: str

View File

@ -140,7 +140,7 @@ class GitService:
# only supports github right now # only supports github right now
@classmethod @classmethod
def handle_web_hook(cls, webhook: dict) -> None: def handle_web_hook(cls, webhook: dict) -> bool:
"""Handle_web_hook.""" """Handle_web_hook."""
cls.check_for_configs() cls.check_for_configs()
@ -155,8 +155,25 @@ class GitService:
f"Configured clone url does not match clone url from webhook: {clone_url}" f"Configured clone url does not match clone url from webhook: {clone_url}"
) )
if "ref" not in webhook:
raise InvalidGitWebhookBodyError(
f"Could not find the 'ref' arg in the webhook boy: {webhook}"
)
if current_app.config["GIT_BRANCH"] is None:
raise MissingGitConfigsError(
"Missing config for GIT_BRANCH. "
"This is required for updating the repository as a result of the webhook"
)
ref = webhook["ref"]
git_branch = current_app.config["GIT_BRANCH"]
if ref != f"refs/heads/{git_branch}":
return False
with FileSystemService.cd(current_app.config["BPMN_SPEC_ABSOLUTE_DIR"]): with FileSystemService.cd(current_app.config["BPMN_SPEC_ABSOLUTE_DIR"]):
cls.run_shell_command(["git", "pull"]) cls.run_shell_command(["git", "pull"])
return True
@classmethod @classmethod
def publish(cls, process_model_id: str, branch_to_update: str) -> str: def publish(cls, process_model_id: str, branch_to_update: str) -> str:

View File

@ -9,7 +9,6 @@ import pytest
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from flask_bpmn.models.db import db from flask_bpmn.models.db import db
from spiffworkflow_backend.services.git_service import GitService
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@ -33,6 +32,7 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.git_service import GitService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )
@ -2573,7 +2573,8 @@ class TestProcessApi(BaseTest):
assert "On branch main" in output assert "On branch main" in output
assert "No commits yet" in output assert "No commits yet" in output
assert ( assert (
'nothing to commit (create/copy files and use "git add" to track)' in output 'nothing to commit (create/copy files and use "git add" to track)'
in output
) )
process_group_id = "test_group" process_group_id = "test_group"
@ -2593,7 +2594,9 @@ class TestProcessApi(BaseTest):
bpmn_file_name=bpmn_file_name, bpmn_file_name=bpmn_file_name,
bpmn_file_location=bpmn_file_location, bpmn_file_location=bpmn_file_location,
) )
process_model_absolute_dir = os.path.join(bpmn_root, process_model_identifier) process_model_absolute_dir = os.path.join(
bpmn_root, process_model_identifier
)
output = GitService.run_shell_command_to_get_stdout(["git", "status"]) output = GitService.run_shell_command_to_get_stdout(["git", "status"])
test_string = 'Untracked files:\n (use "git add <file>..." to include in what will be committed)\n\ttest_group' test_string = 'Untracked files:\n (use "git add <file>..." to include in what will be committed)\n\ttest_group'