hasher-matcher-actioner/hmalib/lambdas/api/action_rules.py (127 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
import bottle
import typing as t
import sys
from botocore.exceptions import ClientError
from bottle import response
from dataclasses import dataclass, field
from hmalib.common.logging import get_logger
from hmalib.lambdas.api.middleware import (
jsoninator,
JSONifiable,
DictParseable,
SubApp,
)
from hmalib.common.config import HMAConfig
from hmalib.common import config as hmaconfig
from hmalib.common.configs.evaluator import ActionRule
logger = get_logger(__name__)
@dataclass
class ActionRulesRequest(DictParseable):
action_rule: ActionRule
@classmethod
def from_dict(cls, d: dict) -> "ActionRulesRequest":
ar = ActionRule.from_aws(d["action_rule"])
logger.debug("Deserialized ActionRule: %s", ar)
return cls(ar)
@dataclass
class ActionRulesResponse(JSONifiable):
error_message: str
action_rules: t.List[ActionRule] = field(default_factory=list)
def to_json(self) -> t.Dict:
return {
"error_message": self.error_message,
"action_rules": [action_rule.to_aws() for action_rule in self.action_rules],
}
# TODO elevate this to some central place when working on Issue 599
def handle_unexpected_error(e: Exception):
logger.error("Unexpected error: %s", sys.exc_info()[0])
logger.exception(e)
response.status = 500
def get_action_rules_api(hma_config_table: str) -> bottle.Bottle:
# The endpoints below imply a prefix of '/action-rules'
action_rules_api = SubApp()
HMAConfig.initialize(hma_config_table)
@action_rules_api.get("/", apply=[jsoninator])
def get_action_rules() -> ActionRulesResponse:
"""
Return all action rules.
"""
error_message = ""
action_rules = []
try:
action_rules = ActionRule.get_all()
logger.info("action_rules: %s", action_rules)
except Exception as e:
error_message = "Unexpected error."
handle_unexpected_error(e)
return ActionRulesResponse(error_message, action_rules)
@action_rules_api.post("/", apply=[jsoninator(ActionRulesRequest)])
def create_action_rule(
request: ActionRulesRequest,
) -> ActionRulesResponse:
"""
Create an action rule.
"""
logger.info("request: %s", request)
error_message = ""
try:
hmaconfig.create_config(request.action_rule)
except ClientError as e:
# TODO this test for "already exists" should be moved to a common place
if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
error_message = f"An action rule with the name '{request.action_rule.name}' already exists."
logger.warning(
"Duplicate action rule creation attempted: %s",
e.response["Error"]["Message"],
)
else:
error_message = "Unexpected error."
logger.error(
"Unexpected client error: %s", e.response["Error"]["Message"]
)
logger.exception(e)
response.status = 500
except Exception as e:
error_message = "Unexpected error."
handle_unexpected_error(e)
return ActionRulesResponse(error_message)
@action_rules_api.put("/<old_name>", apply=[jsoninator(ActionRulesRequest)])
def update_action_rule(
request: ActionRulesRequest,
old_name: str,
) -> ActionRulesResponse:
"""
Update the action rule with name=<oldname>.
"""
logger.info("old_name: %s", old_name)
logger.info("request: %s", request)
error_message = ""
if ActionRule.exists(request.action_rule.name):
try:
hmaconfig.update_config(request.action_rule)
except Exception as e:
error_message = "Unexpected error."
handle_unexpected_error(e)
elif ActionRule.exists(old_name):
try:
hmaconfig.create_config(request.action_rule)
hmaconfig.delete_config_by_type_and_name("ActionRule", old_name)
except Exception as e:
error_message = "Unexpected error."
handle_unexpected_error(e)
else:
error_message = f"An action rule named '{request.action_rule.name}' or '{old_name}' does not exist."
logger.warning(
"An attempt was made to update an action rule named either '%s' or '%s' but neither exist.",
request.action_rule.name,
old_name,
)
response.status = 500
return ActionRulesResponse(error_message)
@action_rules_api.delete("/<name>", apply=[jsoninator])
def delete_action_rule(name: str) -> ActionRulesResponse:
"""
Delete the action rule with name=<name>.
"""
logger.info("name: %s", name)
error_message = ""
if ActionRule.exists(name):
try:
hmaconfig.delete_config_by_type_and_name("ActionRule", name)
except Exception as e:
error_message = "Unexpected error."
handle_unexpected_error(e)
else:
error_message = f"An action rule named '{name}' does not exist."
logger.warning(
"An attempt was made to delete an action rule named '%s' that does not exist.",
name,
)
response.status = 500
return ActionRulesResponse(error_message)
return action_rules_api