Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add initial message endpoint #31

Merged
merged 1 commit into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions devel/ansible/roles/dev/files/config.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[flaskapp]
DEBUG = true
SECRET_KEY = "vagrant-env"
FASJSON_URL = "https://fasjson.tinystage.test/fasjson"
SQLALCHEMY_DATABASE_URI = "sqlite:////home/vagrant/w2fm.db"

[flaskapp.logsconf]
Expand Down
1,450 changes: 1,341 additions & 109 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ classifiers = [
python = "^3.11"
sqlalchemy-helpers = ">=0.12.1"
flask = "^3.0.3"
webhook-to-fedora-messaging-messages = "^1.0.0"
fasjson-client = "^1.0.8"
gunicorn = {version = "^22.0.0", optional = true}


[tool.poetry.group.dev.dependencies]
pytest = ">=7.0.0"
pytest-cov = ">=4.0.0"
Expand Down
2 changes: 1 addition & 1 deletion webhook_to_fedora_messaging/config/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Defaults:
PERMANENT_SESSION_LIFETIME = 604800
SESSION_COOKIE_NAME = "user"


FASJSON_URL = "https://fasjson.fedoraproject.org"
LOGGER_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
Expand Down
37 changes: 37 additions & 0 deletions webhook_to_fedora_messaging/endpoints/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from flask import Blueprint, abort
from ..database import db
from ..models.service import Service
from .parser.parser import msg_parser
from fedora_messaging import api
from webhook_to_fedora_messaging.exceptions import SignatureMatchError
from sqlalchemy import select
from sqlalchemy.exc import NoResultFound


message_endpoint = Blueprint("message_endpoint", __name__)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blueprints are supposed to contain more than one view. You can define a generic blueprint for the app in endpoints.__init__.py and register the views with it.
Examples: https://github.com/fedora-infra/noggin/blob/dev/noggin/controller/__init__.py and https://github.com/fedora-infra/noggin/blob/dev/noggin/controller/root.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may conflict with other branches I guess. We might need another pr to switch this to the one you supposed. Since the other branches are merged.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand. Which branches will it conflict with?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the code you suggested on local but after a meeting with @gridhead we thought the current one might be better. So whichever you guys decide I can push.



@message_endpoint.route("/<service_uuid>", methods=["POST"])
def create_msg(service_uuid):
"""
Used for creating a new message by sending a post request to /message path

Request Body:
service_uuid: Service related to message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't expect the message body to always contain a key named service_uuid, because we don't control the content of the webhook. Instead, we can put the UUID in the URL route. You can change the route pattern in the decorator to: /message/<service_uuid>, and add service_uuid to the function's arguments.


"""

try:
service = db.session.execute(select(Service).where(Service.uuid == service_uuid)).scalar_one()
except NoResultFound:
return {'message': 'Service UUID Not Found'}, 404

try:
msg = msg_parser(service.type, service.token)
except SignatureMatchError as e:
return abort(400, {'message': str(e)})
except ValueError as e:
return abort(400, {'message': str(e)})

api.publish(msg)
return {'status': 'OK', 'message_id': msg.id}
Empty file.
53 changes: 53 additions & 0 deletions webhook_to_fedora_messaging/endpoints/parser/github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from flask import request, current_app
import hashlib
import hmac
from webhook_to_fedora_messaging_messages.github.github import GithubMessageV1
from webhook_to_fedora_messaging.exceptions import SignatureMatchError
import fasjson_client


def github_parser(secret: str) -> GithubMessageV1:
"""Convert Flask request objects into desired FedMsg format.

Args:
secret: Specifies whether the webhook has secret key feature on or not
"""

headers = dict(request.headers)

if secret and not verify_signature(secret, headers['X-Hub-Signature-256']):
raise SignatureMatchError("Message Signature Couldn't be Matched.")

topic = f"github.{headers['X-Github-Event']}"
agent = fas_by_github(request.json['sender']['login']) # FASJSON
return GithubMessageV1(topic=topic, body={'body': request.json, 'headers': headers, 'agent': agent})


def verify_signature(secret_token: str, signature_header: str) -> bool:
"""Verify that the payload was sent from GitHub by validating SHA256.

Return false if not authorized.

Args:
secret_token: GitHub app webhook token (WEBHOOK_SECRET)
signature_header: header received from GitHub (x-hub-signature-256)
"""
if not signature_header:
return False
hash_object = hmac.new(secret_token.encode('utf-8'), msg=request.data, digestmod=hashlib.sha256)
expected_signature = "sha256=" + hash_object.hexdigest()

return hmac.compare_digest(expected_signature, signature_header)


def fas_by_github(username: str) -> str:
"""Get the Fedora Account System Username of the given github username

Args:
username: Github Username"""

fasjson = fasjson_client.Client(current_app.config["FASJSON_URL"])
response = fasjson.search(github_username=username)
if response.result and len(response.result) == 1:
return response.result[0]["username"]
return None
7 changes: 7 additions & 0 deletions webhook_to_fedora_messaging/endpoints/parser/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .github import github_parser

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the empty line from the top of the file.

def msg_parser(service_type: str, secret: str):
if service_type.lower() == "github":
return github_parser(secret)
else:
raise ValueError(f"Unsupported Service: {service_type}")
4 changes: 4 additions & 0 deletions webhook_to_fedora_messaging/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ def __init__(self, text):

def __str__(self):
return self.text

class SignatureMatchError(Exception):
pass

2 changes: 2 additions & 0 deletions webhook_to_fedora_messaging/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .config.defaults import LOGGER_CONFIG
from logging.config import dictConfig
from .endpoints.user import user_endpoint
from .endpoints.message import message_endpoint
from .endpoints.service import service_endpoint
from webhook_to_fedora_messaging.exceptions import ConfigError
import logging
Expand All @@ -32,6 +33,7 @@ def create_app():

app.register_blueprint(user_endpoint, url_prefix="/user")
app.register_blueprint(service_endpoint, url_prefix="/service")
app.register_blueprint(message_endpoint, url_prefix="/message")

# Then load the variables up from the custom configuration file
try:
Expand Down
Loading