Example #1
0
    def process(
        self,
        plan: "FlowPlan",
        binding: FlowStageBinding,
        http_request: HttpRequest,
    ) -> Optional[FlowStageBinding]:
        """Re-evaluate policies bound to stage, and if they fail, remove from plan"""
        from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER

        LOGGER.debug(
            "f(plan_inst)[re-eval marker]: running re-evaluation",
            binding=binding,
            policy_binding=self.binding,
        )
        engine = PolicyEngine(
            self.binding, plan.context.get(PLAN_CONTEXT_PENDING_USER, http_request.user)
        )
        engine.use_cache = False
        engine.request.set_http_request(http_request)
        engine.request.context = plan.context
        engine.build()
        result = engine.result
        if result.passing:
            return binding
        LOGGER.warning(
            "f(plan_inst)[re-eval marker]: binding failed re-evaluation",
            binding=binding,
            messages=result.messages,
        )
        return None
Example #2
0
    def _build_plan(
        self,
        user: User,
        request: HttpRequest,
        default_context: Optional[dict[str, Any]],
    ) -> FlowPlan:
        """Build flow plan by checking each stage in their respective
        order and checking the applied policies"""
        with Hub.current.start_span(
            op="authentik.flow.planner.build_plan",
            description=self.flow.slug,
        ) as span, HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug).time():
            span: Span
            span.set_data("flow", self.flow)
            span.set_data("user", user)
            span.set_data("request", request)

            plan = FlowPlan(flow_pk=self.flow.pk.hex)
            if default_context:
                plan.context = default_context
            # Check Flow policies
            for binding in FlowStageBinding.objects.filter(target__pk=self.flow.pk).order_by(
                "order"
            ):
                binding: FlowStageBinding
                stage = binding.stage
                marker = StageMarker()
                if binding.evaluate_on_plan:
                    self._logger.debug(
                        "f(plan): evaluating on plan",
                        stage=binding.stage,
                    )
                    engine = PolicyEngine(binding, user, request)
                    engine.request.context = plan.context
                    engine.build()
                    if engine.passing:
                        self._logger.debug(
                            "f(plan): stage passing",
                            stage=binding.stage,
                        )
                    else:
                        stage = None
                else:
                    self._logger.debug(
                        "f(plan): not evaluating on plan",
                        stage=binding.stage,
                    )
                if binding.re_evaluate_policies and stage:
                    self._logger.debug(
                        "f(plan): stage has re-evaluate marker",
                        stage=binding.stage,
                    )
                    marker = ReevaluateMarker(binding=binding)
                if stage:
                    plan.append(binding, marker)
            HIST_FLOWS_PLAN_TIME.labels(flow_slug=self.flow.slug)
        self._logger.debug(
            "f(plan): finished building",
        )
        return plan
Example #3
0
 def check_access(self, request: Request, slug: str) -> Response:
     """Check access to a single application by slug"""
     application = self.get_object()
     engine = PolicyEngine(application, self.request.user, self.request)
     engine.build()
     if engine.passing:
         return Response(status=204)
     return Response(status=403)
Example #4
0
def event_trigger_handler(event_uuid: str, trigger_name: str):
    """Check if policies attached to NotificationRule match event"""
    events = Event.objects.filter(event_uuid=event_uuid)
    if not events.exists():
        LOGGER.warning("event doesn't exist yet or anymore", event_uuid=event_uuid)
        return
    event: Event = events.first()
    triggers: NotificationRule = NotificationRule.objects.filter(name=trigger_name)
    if not triggers.exists():
        return
    trigger = triggers.first()

    if "policy_uuid" in event.context:
        policy_uuid = event.context["policy_uuid"]
        if PolicyBinding.objects.filter(
            target__in=NotificationRule.objects.all().values_list("pbm_uuid", flat=True),
            policy=policy_uuid,
        ).exists():
            # If policy that caused this event to be created is attached
            # to *any* NotificationRule, we return early.
            # This is the most effective way to prevent infinite loops.
            LOGGER.debug("e(trigger): attempting to prevent infinite loop", trigger=trigger)
            return

    if not trigger.group:
        LOGGER.debug("e(trigger): trigger has no group", trigger=trigger)
        return

    LOGGER.debug("e(trigger): checking if trigger applies", trigger=trigger)
    try:
        user = User.objects.filter(pk=event.user.get("pk")).first() or get_anonymous_user()
    except User.DoesNotExist:
        LOGGER.warning("e(trigger): failed to get user", trigger=trigger)
        return
    policy_engine = PolicyEngine(trigger, user)
    policy_engine.mode = PolicyEngineMode.MODE_ANY
    policy_engine.empty_result = False
    policy_engine.use_cache = False
    policy_engine.request.context["event"] = event
    policy_engine.build()
    result = policy_engine.result
    if not result.passing:
        return

    LOGGER.debug("e(trigger): event trigger matched", trigger=trigger)
    # Create the notification objects
    for transport in trigger.transports.all():
        for user in trigger.group.users.all():
            LOGGER.debug("created notification")
            notification = Notification.objects.create(
                severity=trigger.severity, body=event.summary, event=event, user=user
            )
            notification_transport.apply_async(
                args=[notification.pk, transport.pk], queue="authentik_events"
            )
            if transport.send_once:
                break
Example #5
0
 def test_engine_policy_type(self):
     """Test invalid policy type"""
     pbm = PolicyBindingModel.objects.create()
     PolicyBinding.objects.create(target=pbm,
                                  policy=self.policy_wrong_type,
                                  order=0)
     with self.assertRaises(TypeError):
         engine = PolicyEngine(pbm, self.user)
         engine.build()
Example #6
0
 def _get_allowed_applications(self,
                               queryset: QuerySet) -> list[Application]:
     applications = []
     for application in queryset:
         engine = PolicyEngine(application, self.request.user, self.request)
         engine.build()
         if engine.passing:
             applications.append(application)
     return applications
Example #7
0
 def check_access(self, request: Request, slug: str) -> Response:
     """Check access to a single application by slug"""
     # Don't use self.get_object as that checks for view_application permission
     # which the user might not have, even if they have access
     application = get_object_or_404(Application, slug=slug)
     engine = PolicyEngine(application, self.request.user, self.request)
     engine.build()
     if engine.passing:
         return Response(status=204)
     return Response(status=403)
Example #8
0
    def plan(
        self, request: HttpRequest, default_context: Optional[dict[str, Any]] = None
    ) -> FlowPlan:
        """Check each of the flows' policies, check policies for each stage with PolicyBinding
        and return ordered list"""
        with Hub.current.start_span(
            op="authentik.flow.planner.plan", description=self.flow.slug
        ) as span:
            span: Span
            span.set_data("flow", self.flow)
            span.set_data("request", request)

            self._logger.debug(
                "f(plan): starting planning process",
            )
            # Bit of a workaround here, if there is a pending user set in the default context
            # we use that user for our cache key
            # to make sure they don't get the generic response
            if default_context and PLAN_CONTEXT_PENDING_USER in default_context:
                user = default_context[PLAN_CONTEXT_PENDING_USER]
            else:
                user = request.user
            # First off, check the flow's direct policy bindings
            # to make sure the user even has access to the flow
            engine = PolicyEngine(self.flow, user, request)
            if default_context:
                span.set_data("default_context", cleanse_dict(default_context))
                engine.request.context = default_context
            engine.build()
            result = engine.result
            if not result.passing:
                exc = FlowNonApplicableException(",".join(result.messages))
                exc.policy_result = result
                raise exc
            # User is passing so far, check if we have a cached plan
            cached_plan_key = cache_key(self.flow, user)
            cached_plan = cache.get(cached_plan_key, None)
            if cached_plan and self.use_cache:
                self._logger.debug(
                    "f(plan): taking plan from cache",
                    key=cached_plan_key,
                )
                # Reset the context as this isn't factored into caching
                cached_plan.context = default_context or {}
                return cached_plan
            self._logger.debug(
                "f(plan): building plan",
            )
            plan = self._build_plan(user, request, default_context)
            cache.set(cache_key(self.flow, user), plan, CACHE_TIMEOUT)
            if not plan.bindings and not self.allow_empty_flows:
                raise EmptyFlowException()
            return plan
Example #9
0
 def test_engine_cache(self):
     """Ensure empty policy list passes"""
     pbm = PolicyBindingModel.objects.create()
     binding = PolicyBinding.objects.create(target=pbm,
                                            policy=self.policy_false,
                                            order=0)
     engine = PolicyEngine(pbm, self.user)
     self.assertEqual(
         len(cache.keys(f"policy_{binding.policy_binding_uuid.hex}*")), 0)
     self.assertEqual(engine.build().passing, False)
     self.assertEqual(
         len(cache.keys(f"policy_{binding.policy_binding_uuid.hex}*")), 1)
     self.assertEqual(engine.build().passing, False)
     self.assertEqual(
         len(cache.keys(f"policy_{binding.policy_binding_uuid.hex}*")), 1)
Example #10
0
 def test_engine_empty(self):
     """Ensure empty policy list passes"""
     pbm = PolicyBindingModel.objects.create()
     engine = PolicyEngine(pbm, self.user)
     result = engine.build().result
     self.assertEqual(result.passing, True)
     self.assertEqual(result.messages, ())
Example #11
0
    def with_policy(request: HttpRequest, **flow_filter) -> Optional["Flow"]:
        """Get a Flow by `**flow_filter` and check if the request from `request` can access it."""
        from authentik.policies.engine import PolicyEngine

        flows = Flow.objects.filter(**flow_filter).order_by("slug")
        for flow in flows:
            engine = PolicyEngine(flow, request.user, request)
            engine.build()
            result = engine.result
            if result.passing:
                LOGGER.debug("with_policy: flow passing", flow=flow)
                return flow
            LOGGER.warning("with_policy: flow not passing",
                           flow=flow,
                           messages=result.messages)
        LOGGER.debug("with_policy: no flow found", filters=flow_filter)
        return None
Example #12
0
 def user_has_access(self, user: Optional[User] = None) -> PolicyResult:
     """Check if user has access to application."""
     user = user or self.request.user
     policy_engine = PolicyEngine(self.application, user
                                  or self.request.user, self.request)
     policy_engine.build()
     result = policy_engine.result
     LOGGER.debug(
         "PolicyAccessView user_has_access",
         user=user,
         app=self.application,
         result=result,
     )
     if not result.passing:
         for message in result.messages:
             messages.error(self.request, _(message))
     return result
Example #13
0
 def process(self, plan: "FlowPlan", stage: Stage,
             http_request: Optional[HttpRequest]) -> Optional[Stage]:
     """Re-evaluate policies bound to stage, and if they fail, remove from plan"""
     engine = PolicyEngine(self.binding, self.user)
     engine.use_cache = False
     if http_request:
         engine.request.set_http_request(http_request)
     engine.request.context = plan.context
     engine.build()
     result = engine.result
     if result.passing:
         return stage
     LOGGER.warning(
         "f(plan_inst)[re-eval marker]: stage failed re-evaluation",
         stage=stage,
         messages=result.messages,
     )
     return None
Example #14
0
 def check_access(self, request: Request, slug: str) -> Response:
     """Check access to a single application by slug"""
     # Don't use self.get_object as that checks for view_application permission
     # which the user might not have, even if they have access
     application = get_object_or_404(Application, slug=slug)
     # If the current user is superuser, they can set `for_user`
     for_user = self.request.user
     if self.request.user.is_superuser and "for_user" in request.data:
         for_user = get_object_or_404(User, pk=request.data.get("for_user"))
     engine = PolicyEngine(application, for_user, self.request)
     engine.build()
     result = engine.result
     response = PolicyTestResultSerializer(PolicyResult(False))
     if result.passing:
         response = PolicyTestResultSerializer(PolicyResult(True))
     if self.request.user.is_superuser:
         response = PolicyTestResultSerializer(result)
     return Response(response.data)
Example #15
0
 def test_engine_policy_error(self):
     """Test policy raising an error flag"""
     pbm = PolicyBindingModel.objects.create()
     PolicyBinding.objects.create(target=pbm,
                                  policy=self.policy_raises,
                                  order=0)
     engine = PolicyEngine(pbm, self.user)
     result = engine.build().result
     self.assertEqual(result.passing, False)
     self.assertEqual(result.messages, ("division by zero", ))
Example #16
0
 def test_engine_simple(self):
     """Ensure simplest use-case"""
     pbm = PolicyBindingModel.objects.create()
     PolicyBinding.objects.create(target=pbm,
                                  policy=self.policy_true,
                                  order=0)
     engine = PolicyEngine(pbm, self.user)
     result = engine.build().result
     self.assertEqual(result.passing, True)
     self.assertEqual(result.messages, ("dummy", ))
Example #17
0
 def user_settings(self, request: Request) -> Response:
     """Get all sources the user can configure"""
     _all_sources: Iterable[Source] = Source.objects.filter(
         enabled=True).select_subclasses()
     matching_sources: list[UserSettingSerializer] = []
     for source in _all_sources:
         user_settings = source.ui_user_settings
         if not user_settings:
             continue
         policy_engine = PolicyEngine(source, request.user, request)
         policy_engine.build()
         if not policy_engine.passing:
             continue
         source_settings = source.ui_user_settings
         source_settings.initial_data["object_uid"] = source.slug
         if not source_settings.is_valid():
             LOGGER.warning(source_settings.errors)
         matching_sources.append(source_settings.validated_data)
     return Response(matching_sources)
Example #18
0
 def test_engine_negate(self):
     """Test negate flag"""
     pbm = PolicyBindingModel.objects.create()
     PolicyBinding.objects.create(target=pbm,
                                  policy=self.policy_true,
                                  negate=True,
                                  order=0)
     engine = PolicyEngine(pbm, self.user)
     result = engine.build().result
     self.assertEqual(result.passing, False)
     self.assertEqual(result.messages, ("dummy", ))
Example #19
0
 def test_engine_mode_any(self):
     """Ensure all policies passes with OR mode (false and true -> true)"""
     pbm = PolicyBindingModel.objects.create(
         policy_engine_mode=PolicyEngineMode.MODE_ANY)
     PolicyBinding.objects.create(target=pbm,
                                  policy=self.policy_false,
                                  order=0)
     PolicyBinding.objects.create(target=pbm,
                                  policy=self.policy_true,
                                  order=1)
     engine = PolicyEngine(pbm, self.user)
     result = engine.build().result
     self.assertEqual(result.passing, True)
     self.assertEqual(
         result.messages,
         (
             "dummy",
             "dummy",
         ),
     )