|
| 1 | +import logging |
| 2 | +from base64 import urlsafe_b64encode |
| 3 | + |
| 4 | +from satosa.context import Context |
| 5 | + |
| 6 | +from .base import RequestMicroService |
| 7 | +from ..exception import SATOSAConfigurationError |
| 8 | +from ..exception import SATOSAError |
| 9 | + |
| 10 | +logger = logging.getLogger(__name__) |
| 11 | + |
| 12 | + |
| 13 | +class FilterRequester(RequestMicroService): |
| 14 | + """ |
| 15 | + Decide whether a requester is allowed to send an authentication request to the target entity based on a whitelist |
| 16 | + """ |
| 17 | + def __init__(self, config, *args, **kwargs): |
| 18 | + super().__init__(*args, **kwargs) |
| 19 | + |
| 20 | + for target_entity, rules in config["rules"].items(): |
| 21 | + conflicting_rules = set(rules.get("deny", [])).intersection(rules.get("allow", [])) |
| 22 | + if conflicting_rules: |
| 23 | + raise SATOSAConfigurationError("Conflicting requester rules for FilterRequester," |
| 24 | + "{} is both denied and allowed".format(conflicting_rules)) |
| 25 | + |
| 26 | + self.rules = {self._b64_url(k): v for k, v in config["rules"].items()} |
| 27 | + self.conf_target_entity_id = config.get('target_entity_id', None) |
| 28 | + |
| 29 | + def process(self, context, data): |
| 30 | + target_entity_id = context.get_decoration(Context.KEY_TARGET_ENTITYID) or self.conf_target_entity_id |
| 31 | + if None is target_entity_id: |
| 32 | + msg_tpl = "{name} can only be used when a target entityid is set" |
| 33 | + msg = msg_tpl.format(name=self.__class__.__name__) |
| 34 | + logger.error(msg) |
| 35 | + raise SATOSAError(msg) |
| 36 | + |
| 37 | + target_specific_rules = self.rules.get(target_entity_id) |
| 38 | + # default to allowing everything if there are no specific rules |
| 39 | + if not target_specific_rules: |
| 40 | + logging.debug("Requester '%s' allowed by default to target entity '%s' due to no entity specific rules", |
| 41 | + data.requester, target_entity_id) |
| 42 | + return super().process(context, data) |
| 43 | + |
| 44 | + # deny rules takes precedence |
| 45 | + deny_rules = target_specific_rules.get("deny", []) |
| 46 | + if data.requester in deny_rules: |
| 47 | + logging.debug("Requester '%s' is not allowed by target entity '%s' due to deny rules '%s'", data.requester, |
| 48 | + target_entity_id, deny_rules) |
| 49 | + raise SATOSAError("Requester is not allowed by target provider") |
| 50 | + |
| 51 | + allow_rules = target_specific_rules.get("allow", []) |
| 52 | + allow_all = "*" in allow_rules |
| 53 | + if data.requester in allow_rules or allow_all: |
| 54 | + logging.debug("Requester '%s' allowed by target entity '%s' due to allow rules '%s", |
| 55 | + data.requester, target_entity_id, allow_rules) |
| 56 | + return super().process(context, data) |
| 57 | + |
| 58 | + logger.debug("Requester '%s' is not allowed by target entity '%s' due to final deny all rule in '%s'", |
| 59 | + data.requester, target_entity_id, deny_rules) |
| 60 | + raise SATOSAError("Requester is not allowed by target provider") |
0 commit comments