Esempio n. 1
0
 def get_queryset(self):
     queryset = super().get_queryset()
     if self.action == "get" or self.action == "list":
         if can_install_plugins_via_api(self.organization) or can_configure_plugins_via_api(self.organization):
             return queryset
     else:
         if can_install_plugins_via_api(self.organization):
             return queryset
     return queryset.none()
Esempio n. 2
0
 def get_queryset(self):
     queryset = super().get_queryset()
     if self.action == "get" or self.action == "list":  # type: ignore
         if can_install_plugins_via_api() or can_configure_plugins_via_api():
             return queryset
     else:
         if can_install_plugins_via_api():
             # block update/delete for plugins that come from posthog.json
             return queryset.filter(from_json=False)
     return queryset.none()
Esempio n. 3
0
 def repository(self, request: request.Request, **kwargs):
     if not can_install_plugins_via_api(self.organization):
         raise ValidationError(
             "Plugin installation via the web is disabled!")
     url = "https://raw.githubusercontent.com/PostHog/plugin-repository/main/repository.json"
     plugins = requests.get(url)
     return Response(json.loads(plugins.text))
Esempio n. 4
0
 def update(self, plugin: Plugin, validated_data: Dict, *args: Any, **kwargs: Any) -> Plugin:  # type: ignore
     if not can_install_plugins_via_api(self.context["organization_id"]):
         raise ValidationError("Plugin upgrades via the web are disabled!")
     if plugin.plugin_type != Plugin.PluginType.SOURCE:
         validated_data = self._update_validated_data_from_url(validated_data, validated_data["url"])
     response = super().update(plugin, validated_data)
     reload_plugins_on_workers()
     return response
Esempio n. 5
0
 def update(self, plugin: Plugin, validated_data: Dict, *args: Any,
            **kwargs: Any) -> Plugin:  # type: ignore
     if not can_install_plugins_via_api():
         raise ValidationError("Plugin upgrades via the web are disabled!")
     validated_data = self._get_validated_data_for_url(
         validated_data["url"])
     response = super().update(plugin, validated_data)
     reload_plugins_on_workers()
     return response
Esempio n. 6
0
 def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> Plugin:
     if not can_install_plugins_via_api(self.context["organization_id"]):
         raise ValidationError("Plugin installation via the web is disabled!")
     if validated_data.get("plugin_type", None) != Plugin.PluginType.SOURCE:
         self._update_validated_data_from_url(validated_data, validated_data["url"])
     if len(Plugin.objects.filter(name=validated_data["name"])) > 0:
         raise ValidationError('Plugin with name "{}" already installed!'.format(validated_data["name"]))
     validated_data["organization_id"] = self.context["organization_id"]
     plugin = super().create(validated_data)
     reload_plugins_on_workers()
     return plugin
Esempio n. 7
0
    def status(self, request: request.Request, **kwargs):
        if not can_install_plugins_via_api(self.organization):
            raise ValidationError("Plugin installation via the web is disabled!")

        ping = get_client().get("@posthog-plugin-server/ping")
        if ping:
            ping_datetime = parser.isoparse(ping)
            if ping_datetime > now() - relativedelta(seconds=30):
                return Response({"status": "online"})

        return Response({"status": "offline"})
Esempio n. 8
0
 def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> Plugin:
     validated_data["url"] = self.initial_data.get("url", None)
     if not can_install_plugins_via_api(self.context["organization_id"]):
         raise ValidationError("Plugin installation via the web is disabled!")
     if validated_data.get("plugin_type", None) != Plugin.PluginType.SOURCE:
         self._update_validated_data_from_url(validated_data, validated_data["url"])
         self._raise_if_plugin_installed(validated_data["url"])
     validated_data["organization_id"] = self.context["organization_id"]
     plugin = super().create(validated_data)
     reload_plugins_on_workers()
     return plugin
Esempio n. 9
0
    def check_for_updates(self, request: request.Request, **kwargs):
        if not can_install_plugins_via_api(self.organization):
            raise ValidationError("Plugin installation via the web is disabled!")

        plugin = self.get_object()
        latest_url = parse_url(plugin.url, get_latest_if_none=True)
        plugin.latest_tag = latest_url.get("tag", latest_url.get("version", None))
        plugin.latest_tag_checked_at = now()
        plugin.save()

        return Response({"plugin": PluginSerializer(plugin).data})
Esempio n. 10
0
 def create(self, validated_data: Dict, *args: Any,
            **kwargs: Any) -> Plugin:
     if not can_install_plugins_via_api():
         raise ValidationError(
             "Plugin installation via the web is disabled!")
     validated_data = self._get_validated_data_for_url(
         validated_data["url"])
     if len(Plugin.objects.filter(name=validated_data["name"])) > 0:
         raise ValidationError(
             'Plugin with name "{}" already installed!'.format(
                 validated_data["name"]))
     plugin = super().create(validated_data)
     reload_plugins_on_workers()
     return plugin
Esempio n. 11
0
    def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> Plugin:
        if not can_install_plugins_via_api():
            raise ValidationError("Plugin installation via the web is disabled!")

        local_plugin = validated_data.get("url", "").startswith("file:")

        if local_plugin:
            plugin_path = validated_data["url"][5:]
            json_path = os.path.join(plugin_path, "plugin.json")
            json = load_json_file(json_path)
            if not json:
                raise ValidationError("Could not load plugin.json from: {}".format(json_path))
            validated_data["name"] = json.get("name", json_path.split("/")[-2])
            validated_data["description"] = json.get("description", "")
            validated_data["config_schema"] = json.get("config", {})
        else:
            parsed_url = parse_url(validated_data["url"], get_latest_if_none=True)
            if parsed_url:
                validated_data["url"] = parsed_url["root_url"]
                validated_data["tag"] = parsed_url.get("version", parsed_url.get("tag", None))
                validated_data["archive"] = download_plugin_archive(validated_data["url"], validated_data["tag"])
                plugin_json = get_json_from_archive(validated_data["archive"], "plugin.json")
                if not plugin_json:
                    raise ValidationError("Could not find plugin.json in the plugin")
                validated_data["name"] = plugin_json["name"]
                validated_data["description"] = plugin_json.get("description", "")
                validated_data["config_schema"] = plugin_json.get("config", {})
            else:
                raise ValidationError("Must be a GitHub repository or a NPM package URL!")

        if len(Plugin.objects.filter(name=validated_data["name"])) > 0:
            raise ValidationError('Plugin with name "{}" already installed!'.format(validated_data["name"]))

        validated_data["from_web"] = True
        plugin = super().create(validated_data)
        reload_plugins_on_workers()
        return plugin
Esempio n. 12
0
 def get_error(self, plugin: Plugin) -> Optional[JSONField]:
     if plugin.error and can_install_plugins_via_api(self.context["organization_id"]):
         return plugin.error
     return None
Esempio n. 13
0
def user(request):
    organization = request.user.organization
    organizations = list(
        request.user.organizations.order_by("-created_at").values(
            "name", "id"))
    team = request.user.team
    teams = list(
        request.user.teams.order_by("-created_at").values("name", "id"))
    user = cast(User, request.user)

    if request.method == "PATCH":
        data = json.loads(request.body)

        if "team" in data:
            team.app_urls = data["team"].get("app_urls", team.app_urls)
            team.opt_out_capture = data["team"].get("opt_out_capture",
                                                    team.opt_out_capture)
            team.slack_incoming_webhook = data["team"].get(
                "slack_incoming_webhook", team.slack_incoming_webhook)
            team.anonymize_ips = data["team"].get("anonymize_ips",
                                                  team.anonymize_ips)
            team.session_recording_opt_in = data["team"].get(
                "session_recording_opt_in", team.session_recording_opt_in)
            if data["team"].get("plugins_opt_in") is not None:
                reload_plugins_on_workers()
            team.plugins_opt_in = data["team"].get("plugins_opt_in",
                                                   team.plugins_opt_in)
            team.completed_snippet_onboarding = data["team"].get(
                "completed_snippet_onboarding",
                team.completed_snippet_onboarding,
            )
            team.save()

        if "user" in data:
            try:
                user.current_organization = user.organizations.get(
                    id=data["user"]["current_organization_id"])
                user.current_team = user.organization.teams.first()
            except KeyError:
                pass
            except ObjectDoesNotExist:
                return JsonResponse(
                    {"detail": "Organization not found for user."}, status=404)
            except KeyError:
                pass
            except ObjectDoesNotExist:
                return JsonResponse(
                    {"detail": "Organization not found for user."}, status=404)
            try:
                user.current_team = user.organization.teams.get(
                    id=int(data["user"]["current_team_id"]))
            except (KeyError, TypeError):
                pass
            except ValueError:
                return JsonResponse({"detail": "Team ID must be an integer."},
                                    status=400)
            except ObjectDoesNotExist:
                return JsonResponse(
                    {
                        "detail":
                        "Team not found for user's current organization."
                    },
                    status=404)
            user.email_opt_in = data["user"].get("email_opt_in",
                                                 user.email_opt_in)
            user.anonymize_data = data["user"].get("anonymize_data",
                                                   user.anonymize_data)
            user.toolbar_mode = data["user"].get("toolbar_mode",
                                                 user.toolbar_mode)
            posthoganalytics.identify(
                user.distinct_id,
                {
                    "email_opt_in":
                    user.email_opt_in,
                    "anonymize_data":
                    user.anonymize_data,
                    "email":
                    user.email if not user.anonymize_data else None,
                    "is_signed_up":
                    True,
                    "toolbar_mode":
                    user.toolbar_mode,
                    "billing_plan":
                    user.organization.billing_plan,
                    "is_team_unique_user": (team.users.count() == 1),
                    "team_setup_complete": (team.completed_snippet_onboarding
                                            and team.ingested_event),
                },
            )
            user.save()

    return JsonResponse({
        "id":
        user.pk,
        "distinct_id":
        user.distinct_id,
        "name":
        user.first_name,
        "email":
        user.email,
        "email_opt_in":
        user.email_opt_in,
        "anonymize_data":
        user.anonymize_data,
        "toolbar_mode":
        user.toolbar_mode,
        "organization": {
            "id":
            organization.id,
            "name":
            organization.name,
            "billing_plan":
            organization.billing_plan,
            "available_features":
            organization.available_features,
            "created_at":
            organization.created_at,
            "updated_at":
            organization.updated_at,
            "teams": [{
                "id": team.id,
                "name": team.name
            } for team in organization.teams.all().only("id", "name")],
        },
        "organizations":
        organizations,
        "team":
        team and {
            "id": team.id,
            "name": team.name,
            "app_urls": team.app_urls,
            "api_token": team.api_token,
            "opt_out_capture": team.opt_out_capture,
            "anonymize_ips": team.anonymize_ips,
            "slack_incoming_webhook": team.slack_incoming_webhook,
            "event_names": team.event_names,
            "event_properties": team.event_properties,
            "event_properties_numerical": team.event_properties_numerical,
            "completed_snippet_onboarding": team.completed_snippet_onboarding,
            "session_recording_opt_in": team.session_recording_opt_in,
            "plugins_opt_in": team.plugins_opt_in,
            "ingested_event": team.ingested_event,
        },
        "teams":
        teams,
        "has_password":
        user.has_usable_password(),
        "opt_out_capture":
        os.environ.get("OPT_OUT_CAPTURE"),
        "posthog_version":
        VERSION,
        "is_multi_tenancy":
        getattr(settings, "MULTI_TENANCY", False),
        "ee_available":
        user.ee_available,
        "plugin_access": {
            "install": can_install_plugins_via_api(),
            "configure": can_configure_plugins_via_api()
        },
    })
Esempio n. 14
0
def user(request):
    organization: Optional[Organization] = request.user.organization
    organizations = list(
        request.user.organizations.order_by("-created_at").values(
            "name", "id"))
    team: Optional[Team] = request.user.team
    teams = list(
        request.user.teams.order_by("-created_at").values("name", "id"))
    user = cast(User, request.user)

    if request.method == "PATCH":
        data = json.loads(request.body)

        if team is not None and "team" in data:
            team.app_urls = data["team"].get("app_urls", team.app_urls)
            team.opt_out_capture = data["team"].get("opt_out_capture",
                                                    team.opt_out_capture)
            team.slack_incoming_webhook = data["team"].get(
                "slack_incoming_webhook", team.slack_incoming_webhook)
            team.anonymize_ips = data["team"].get("anonymize_ips",
                                                  team.anonymize_ips)
            team.session_recording_opt_in = data["team"].get(
                "session_recording_opt_in", team.session_recording_opt_in)
            team.session_recording_retention_period_days = data["team"].get(
                "session_recording_retention_period_days",
                team.session_recording_retention_period_days)
            if data["team"].get("plugins_opt_in") is not None:
                reload_plugins_on_workers()
            team.plugins_opt_in = data["team"].get("plugins_opt_in",
                                                   team.plugins_opt_in)
            team.completed_snippet_onboarding = data["team"].get(
                "completed_snippet_onboarding",
                team.completed_snippet_onboarding,
            )
            team.save()

        if "user" in data:
            try:
                user.current_organization = user.organizations.get(
                    id=data["user"]["current_organization_id"])
                assert user.organization is not None, "Organization should have been just set"
                user.current_team = user.organization.teams.first()
            except (KeyError, ValueError):
                pass
            except ObjectDoesNotExist:
                return JsonResponse(
                    {"detail": "Organization not found for user."}, status=404)
            except KeyError:
                pass
            except ObjectDoesNotExist:
                return JsonResponse(
                    {"detail": "Organization not found for user."}, status=404)
            if user.organization is not None:
                try:
                    user.current_team = user.organization.teams.get(
                        id=int(data["user"]["current_team_id"]))
                except (KeyError, TypeError):
                    pass
                except ValueError:
                    return JsonResponse(
                        {"detail": "Team ID must be an integer."}, status=400)
                except ObjectDoesNotExist:
                    return JsonResponse(
                        {
                            "detail":
                            "Team not found for user's current organization."
                        },
                        status=404)
            user.email_opt_in = data["user"].get("email_opt_in",
                                                 user.email_opt_in)
            user.anonymize_data = data["user"].get("anonymize_data",
                                                   user.anonymize_data)
            user.toolbar_mode = data["user"].get("toolbar_mode",
                                                 user.toolbar_mode)
            user.save()

    user_identify.identify_task.delay(user_id=user.id)

    return JsonResponse({
        "id":
        user.pk,
        "distinct_id":
        user.distinct_id,
        "name":
        user.first_name,
        "email":
        user.email,
        "email_opt_in":
        user.email_opt_in,
        "anonymize_data":
        user.anonymize_data,
        "toolbar_mode":
        user.toolbar_mode,
        "organization":
        None if organization is None else {
            "id":
            organization.id,
            "name":
            organization.name,
            "billing_plan":
            organization.billing_plan,
            "available_features":
            organization.available_features,
            "created_at":
            organization.created_at,
            "updated_at":
            organization.updated_at,
            "teams": [{
                "id": team.id,
                "name": team.name
            } for team in organization.teams.all().only("id", "name")],
        },
        "organizations":
        organizations,
        "team":
        None if team is None else {
            "id":
            team.id,
            "name":
            team.name,
            "app_urls":
            team.app_urls,
            "api_token":
            team.api_token,
            "opt_out_capture":
            team.opt_out_capture,
            "anonymize_ips":
            team.anonymize_ips,
            "slack_incoming_webhook":
            team.slack_incoming_webhook,
            "event_names":
            team.event_names,
            "event_names_with_usage":
            team.event_names_with_usage or [{
                "event": event,
                "volume": None,
                "usage_count": None
            } for event in team.event_names],
            "event_properties":
            team.event_properties,
            "event_properties_numerical":
            team.event_properties_numerical,
            "event_properties_with_usage":
            team.event_properties_with_usage or [{
                "key": key,
                "volume": None,
                "usage_count": None
            } for key in team.event_properties],
            "completed_snippet_onboarding":
            team.completed_snippet_onboarding,
            "session_recording_opt_in":
            team.session_recording_opt_in,
            "session_recording_retention_period_days":
            team.session_recording_retention_period_days,
            "plugins_opt_in":
            team.plugins_opt_in,
            "ingested_event":
            team.ingested_event,
        },
        "teams":
        teams,
        "has_password":
        user.has_usable_password(),
        "opt_out_capture":
        os.environ.get("OPT_OUT_CAPTURE"),
        "posthog_version":
        VERSION,
        "is_multi_tenancy":
        getattr(settings, "MULTI_TENANCY", False),
        "ee_available":
        settings.EE_AVAILABLE,
        "ee_enabled":
        is_ee_enabled(),
        "email_service_available":
        is_email_available(with_absolute_urls=True),
        "is_debug":
        getattr(settings, "DEBUG", False),
        "is_staff":
        user.is_staff,
        "is_impersonated":
        is_impersonated_session(request),
        "plugin_access": {
            "install": can_install_plugins_via_api(user.organization),
            "configure": can_configure_plugins_via_api(user.organization),
        },
    })
Esempio n. 15
0
 def status(self, request: request.Request, **kwargs):
     if not can_install_plugins_via_api(self.organization):
         raise ValidationError(
             "Plugin installation via the web is disabled!")
     return Response(
         {"status": "online" if is_plugin_server_alive() else "offline"})
Esempio n. 16
0
def system_status(request):
    team = request.user.team
    is_multitenancy: bool = getattr(settings, "MULTI_TENANCY", False)

    if is_multitenancy and not request.user.is_staff:
        raise AuthenticationFailed(detail="You're not authorized.")

    from .models import Element, Event, SessionRecordingEvent

    redis_alive = is_redis_alive()
    postgres_alive = is_postgres_alive()

    metrics: List[Dict[str, Union[str, bool, int, float]]] = []

    metrics.append({
        "key": "analytics_database",
        "metric": "Analytics database in use",
        "value": "ClickHouse" if is_ee_enabled() else "Postgres",
    })

    metrics.append({
        "key":
        "ingestion_server",
        "metric":
        "Event ingestion via",
        "value":
        "Plugin Server" if settings.PLUGIN_SERVER_INGESTION else "Django",
    })

    metrics.append({
        "key": "db_alive",
        "metric": "Postgres database alive",
        "value": postgres_alive
    })
    if postgres_alive:
        postgres_version = connection.cursor().connection.server_version
        metrics.append({
            "key":
            "pg_version",
            "metric":
            "Postgres version",
            "value":
            f"{postgres_version // 10000}.{(postgres_version // 100) % 100}.{postgres_version % 100}",
        })
        event_table_count = get_table_approx_count(
            Event._meta.db_table)[0]["approx_count"]
        event_table_size = get_table_size(Event._meta.db_table)[0]["size"]

        element_table_count = get_table_approx_count(
            Element._meta.db_table)[0]["approx_count"]
        element_table_size = get_table_size(Element._meta.db_table)[0]["size"]

        session_recording_event_table_count = get_table_approx_count(
            SessionRecordingEvent._meta.db_table)[0]["approx_count"]
        session_recording_event_table_size = get_table_size(
            SessionRecordingEvent._meta.db_table)[0]["size"]

        metrics.append({
            "metric":
            "Postgres elements table size",
            "value":
            f"~{element_table_count} rows (~{element_table_size})"
        })
        metrics.append({
            "metric":
            "Postgres events table size",
            "value":
            f"~{event_table_count} rows (~{event_table_size})"
        })
        metrics.append({
            "metric":
            "Postgres session recording table size",
            "value":
            f"~{session_recording_event_table_count} rows (~{session_recording_event_table_size})",
        })

    metrics.append({
        "key": "redis_alive",
        "metric": "Redis alive",
        "value": redis_alive
    })
    if redis_alive:
        import redis

        try:
            redis_info = get_redis_info()
            redis_queue_depth = get_redis_queue_depth()
            metrics.append({
                "metric": "Redis version",
                "value": f"{redis_info.get('redis_version')}"
            })
            metrics.append({
                "metric": "Redis current queue depth",
                "value": f"{redis_queue_depth}"
            })
            metrics.append({
                "metric": "Redis connected client count",
                "value": f"{redis_info.get('connected_clients')}"
            })
            metrics.append({
                "metric":
                "Redis memory used",
                "value":
                f"{redis_info.get('used_memory_human', '?')}B"
            })
            metrics.append({
                "metric":
                "Redis memory peak",
                "value":
                f"{redis_info.get('used_memory_peak_human', '?')}B"
            })
            metrics.append({
                "metric":
                "Redis total memory available",
                "value":
                f"{redis_info.get('total_system_memory_human', '?')}B",
            })
        except redis.exceptions.ConnectionError as e:
            metrics.append({
                "metric":
                "Redis metrics",
                "value":
                f"Redis connected but then failed to return metrics: {e}"
            })

    metrics.append({
        "key": "plugin_sever_alive",
        "metric": "Plugin server alive",
        "value": is_plugin_server_alive()
    })
    metrics.append({
        "key": "plugin_sever_version",
        "metric": "Plugin server version",
        "value": get_plugin_server_version() or "unknown",
    })
    metrics.append({
        "key": "plugins_install",
        "metric": "Plugins can be installed",
        "value": can_install_plugins_via_api(team.organization),
    })
    metrics.append({
        "key": "plugins_configure",
        "metric": "Plugins can be configured",
        "value": can_configure_plugins_via_api(team.organization),
    })

    return JsonResponse({"results": metrics})
Esempio n. 17
0
 def get_error(self, plugin: Plugin) -> Optional[JSONField]:
     if plugin.error and can_install_plugins_via_api():
         return plugin.error
     return None