Esempio n. 1
0
class FlowStageBinding(SerializerModel, PolicyBindingModel):
    """Relationship between Flow and Stage. Order is required and unique for
    each flow-stage Binding. Additionally, policies can be specified, which determine if
    this Binding applies to the current user"""

    fsb_uuid = models.UUIDField(primary_key=True,
                                editable=False,
                                default=uuid4)

    target = models.ForeignKey("Flow", on_delete=models.CASCADE)
    stage = InheritanceForeignKey(Stage, on_delete=models.CASCADE)

    evaluate_on_plan = models.BooleanField(
        default=True,
        help_text=_(("Evaluate policies during the Flow planning process. "
                     "Disable this for input-based policies.")),
    )
    re_evaluate_policies = models.BooleanField(
        default=False,
        help_text=_(
            "Evaluate policies when the Stage is present to the user."),
    )

    order = models.IntegerField()

    objects = InheritanceManager()

    @property
    def serializer(self) -> BaseSerializer:
        from authentik.flows.api.bindings import FlowStageBindingSerializer

        return FlowStageBindingSerializer

    def __str__(self) -> str:
        return f"{self.target} #{self.order}"

    class Meta:

        ordering = ["target", "order"]

        verbose_name = _("Flow Stage Binding")
        verbose_name_plural = _("Flow Stage Bindings")
        unique_together = (("target", "stage", "order"), )
Esempio n. 2
0
class Outpost(models.Model):
    """Outpost instance which manages a service user and token"""

    uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
    name = models.TextField()

    type = models.TextField(choices=OutpostType.choices,
                            default=OutpostType.PROXY)
    service_connection = InheritanceForeignKey(
        OutpostServiceConnection,
        default=None,
        null=True,
        blank=True,
        help_text=_((
            "Select Service-Connection authentik should use to manage this outpost. "
            "Leave empty if authentik should not handle the deployment.")),
        on_delete=models.SET_DEFAULT,
    )

    _config = models.JSONField(default=default_outpost_config)

    providers = models.ManyToManyField(Provider)

    @property
    def config(self) -> OutpostConfig:
        """Load config as OutpostConfig object"""
        return from_dict(OutpostConfig, self._config)

    @config.setter
    def config(self, value):
        """Dump config into json"""
        self._config = asdict(value)

    @property
    def state_cache_prefix(self) -> str:
        """Key by which the outposts status is saved"""
        return f"outpost_{self.uuid.hex}_state"

    @property
    def state(self) -> list["OutpostState"]:
        """Get outpost's health status"""
        return OutpostState.for_outpost(self)

    @property
    def user_identifier(self):
        """Username for service user"""
        return f"ak-outpost-{self.uuid.hex}"

    @property
    def user(self) -> User:
        """Get/create user with access to all required objects"""
        users = User.objects.filter(username=self.user_identifier)
        if not users.exists():
            user: User = User.objects.create(username=self.user_identifier)
            user.attributes[USER_ATTRIBUTE_SA] = True
            user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
            user.set_unusable_password()
            user.save()
        else:
            user = users.first()
        # To ensure the user only has the correct permissions, we delete all of them and re-add
        # the ones the user needs
        with transaction.atomic():
            UserObjectPermission.objects.filter(user=user).delete()
            user.user_permissions.clear()
            for model_or_perm in self.get_required_objects():
                if isinstance(model_or_perm, models.Model):
                    model_or_perm: models.Model
                    code_name = (f"{model_or_perm._meta.app_label}."
                                 f"view_{model_or_perm._meta.model_name}")
                    assign_perm(code_name, user, model_or_perm)
                else:
                    app_label, perm = model_or_perm.split(".")
                    permission = Permission.objects.filter(
                        codename=perm,
                        content_type__app_label=app_label,
                    )
                    if not permission.exists():
                        LOGGER.warning("permission doesn't exist",
                                       perm=model_or_perm)
                        continue
                    user.user_permissions.add(permission.first())
        LOGGER.debug(
            "Updated service account's permissions",
            perms=UserObjectPermission.objects.filter(user=user),
        )
        return user

    @property
    def token_identifier(self) -> str:
        """Get Token identifier"""
        return f"ak-outpost-{self.pk}-api"

    @property
    def token(self) -> Token:
        """Get/create token for auto-generated user"""
        token = Token.filter_not_expired(user=self.user,
                                         intent=TokenIntents.INTENT_API)
        if token.exists():
            return token.first()
        return Token.objects.create(
            user=self.user,
            identifier=self.token_identifier,
            intent=TokenIntents.INTENT_API,
            description=f"Autogenerated by authentik for Outpost {self.name}",
            expiring=False,
            managed=f"goauthentik.io/outpost/{self.token_identifier}",
        )

    def get_required_objects(self) -> Iterable[Union[models.Model, str]]:
        """Get an iterator of all objects the user needs read access to"""
        objects: list[Union[models.Model, str]] = [self]
        for provider in (Provider.objects.filter(
                outpost=self).select_related().select_subclasses()):
            if isinstance(provider, OutpostModel):
                objects.extend(provider.get_required_objects())
            else:
                objects.append(provider)
        return objects

    def __str__(self) -> str:
        return f"Outpost {self.name}"
Esempio n. 3
0
class Outpost(ManagedModel):
    """Outpost instance which manages a service user and token"""

    uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
    name = models.TextField()

    type = models.TextField(choices=OutpostType.choices,
                            default=OutpostType.PROXY)
    service_connection = InheritanceForeignKey(
        OutpostServiceConnection,
        default=None,
        null=True,
        blank=True,
        help_text=_((
            "Select Service-Connection authentik should use to manage this outpost. "
            "Leave empty if authentik should not handle the deployment.")),
        on_delete=models.SET_DEFAULT,
    )

    _config = models.JSONField(default=default_outpost_config)

    providers = models.ManyToManyField(Provider)

    @property
    def config(self) -> OutpostConfig:
        """Load config as OutpostConfig object"""
        return from_dict(OutpostConfig, self._config)

    @config.setter
    def config(self, value):
        """Dump config into json"""
        self._config = asdict(value)

    @property
    def state_cache_prefix(self) -> str:
        """Key by which the outposts status is saved"""
        return f"outpost_{self.uuid.hex}_state"

    @property
    def state(self) -> list["OutpostState"]:
        """Get outpost's health status"""
        return OutpostState.for_outpost(self)

    @property
    def user_identifier(self):
        """Username for service user"""
        return f"ak-outpost-{self.uuid.hex}"

    def build_user_permissions(self, user: User):
        """Create per-object and global permissions for outpost service-account"""
        # To ensure the user only has the correct permissions, we delete all of them and re-add
        # the ones the user needs
        with transaction.atomic():
            UserObjectPermission.objects.filter(user=user).delete()
            user.user_permissions.clear()
            for model_or_perm in self.get_required_objects():
                if isinstance(model_or_perm, models.Model):
                    model_or_perm: models.Model
                    code_name = (f"{model_or_perm._meta.app_label}."
                                 f"view_{model_or_perm._meta.model_name}")
                    try:
                        assign_perm(code_name, user, model_or_perm)
                    except (Permission.DoesNotExist, AttributeError) as exc:
                        LOGGER.warning(
                            "permission doesn't exist",
                            code_name=code_name,
                            user=user,
                            model=model_or_perm,
                        )
                        Event.new(
                            action=EventAction.SYSTEM_EXCEPTION,
                            message=
                            ("While setting the permissions for the service-account, a "
                             "permission was not found: Check "
                             "https://goauthentik.io/docs/troubleshooting/missing_permission"
                             ) + exception_to_string(exc),
                        ).set_user(user).save()
                else:
                    app_label, perm = model_or_perm.split(".")
                    permission = Permission.objects.filter(
                        codename=perm,
                        content_type__app_label=app_label,
                    )
                    if not permission.exists():
                        LOGGER.warning("permission doesn't exist",
                                       perm=model_or_perm)
                        continue
                    user.user_permissions.add(permission.first())
        LOGGER.debug(
            "Updated service account's permissions",
            obj_perms=UserObjectPermission.objects.filter(user=user),
            perms=user.user_permissions.all(),
        )

    @property
    def user(self) -> User:
        """Get/create user with access to all required objects"""
        users = User.objects.filter(username=self.user_identifier)
        should_create_user = not users.exists()
        if should_create_user:
            user: User = User.objects.create(username=self.user_identifier)
            user.set_unusable_password()
            user.save()
        else:
            user = users.first()
        user.attributes[USER_ATTRIBUTE_SA] = True
        user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
        user.name = f"Outpost {self.name} Service-Account"
        user.save()
        if should_create_user:
            self.build_user_permissions(user)
        return user

    @property
    def token_identifier(self) -> str:
        """Get Token identifier"""
        return f"ak-outpost-{self.pk}-api"

    @property
    def token(self) -> Token:
        """Get/create token for auto-generated user"""
        managed = f"goauthentik.io/outpost/{self.token_identifier}"
        tokens = Token.filter_not_expired(
            identifier=self.token_identifier,
            intent=TokenIntents.INTENT_API,
            managed=managed,
        )
        if tokens.exists():
            return tokens.first()
        try:
            return Token.objects.create(
                user=self.user,
                identifier=self.token_identifier,
                intent=TokenIntents.INTENT_API,
                description=
                f"Autogenerated by authentik for Outpost {self.name}",
                expiring=False,
                managed=managed,
            )
        except IntegrityError:
            # Integrity error happens mostly when managed is re-used
            Token.objects.filter(managed=managed).delete()
            Token.objects.filter(identifier=self.token_identifier).delete()
            return self.token

    def get_required_objects(self) -> Iterable[models.Model | str]:
        """Get an iterator of all objects the user needs read access to"""
        objects: list[models.Model | str] = [
            self,
            "authentik_events.add_event",
        ]
        for provider in Provider.objects.filter(
                outpost=self).select_related().select_subclasses():
            if isinstance(provider, OutpostModel):
                objects.extend(provider.get_required_objects())
            else:
                objects.append(provider)
        if self.managed:
            for tenant in Tenant.objects.filter(web_certificate__isnull=False):
                objects.append(tenant)
                objects.append(tenant.web_certificate)
        return objects

    def __str__(self) -> str:
        return f"Outpost {self.name}"
Esempio n. 4
0
class PolicyBinding(SerializerModel):
    """Relationship between a Policy and a PolicyBindingModel."""

    policy_binding_uuid = models.UUIDField(primary_key=True,
                                           editable=False,
                                           default=uuid4)

    enabled = models.BooleanField(default=True)

    policy = InheritanceForeignKey(
        "Policy",
        on_delete=models.CASCADE,
        related_name="+",
        default=None,
        null=True,
        blank=True,
    )
    group = models.ForeignKey(
        # This is quite an ugly hack to prevent pylint from trying
        # to resolve authentik_core.models.Group
        # as python import path
        "authentik_core." + "Group",
        on_delete=models.CASCADE,
        default=None,
        null=True,
        blank=True,
    )
    user = models.ForeignKey(
        "authentik_core." + "User",
        on_delete=models.CASCADE,
        default=None,
        null=True,
        blank=True,
    )

    target = InheritanceForeignKey(PolicyBindingModel,
                                   on_delete=models.CASCADE,
                                   related_name="+")
    negate = models.BooleanField(
        default=False,
        help_text=_(
            "Negates the outcome of the policy. Messages are unaffected."),
    )
    timeout = models.IntegerField(
        default=30,
        help_text=_("Timeout after which Policy execution is terminated."))

    order = models.IntegerField()

    def passes(self, request: PolicyRequest) -> PolicyResult:
        """Check if request passes this PolicyBinding, check policy, group or user"""
        if self.policy:
            self.policy: Policy
            return self.policy.passes(request)
        if self.group:
            return PolicyResult(
                self.group.users.filter(pk=request.user.pk).exists())
        if self.user:
            return PolicyResult(request.user == self.user)
        return PolicyResult(False)

    @property
    def serializer(self) -> BaseSerializer:
        from authentik.policies.api.bindings import PolicyBindingSerializer

        return PolicyBindingSerializer

    @property
    def target_type(self) -> str:
        """Get the target type this binding is applied to"""
        if self.policy:
            return "policy"
        if self.group:
            return "group"
        if self.user:
            return "user"
        return "invalid"

    @property
    def target_name(self) -> str:
        """Get the target name this binding is applied to"""
        if self.policy:
            return self.policy.name
        if self.group:
            return self.group.name
        if self.user:
            return self.user.name
        return "invalid"

    def __str__(self) -> str:
        suffix = f"{self.target_type.title()} {self.target_name}"
        try:
            return f"Binding from {self.target} #{self.order} to {suffix}"
        except PolicyBinding.target.RelatedObjectDoesNotExist:  # pylint: disable=no-member
            return f"Binding - #{self.order} to {suffix}"

    class Meta:

        verbose_name = _("Policy Binding")
        verbose_name_plural = _("Policy Bindings")
        unique_together = ("policy", "target", "order")