Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve /decide endpoint errors due to invalid token #4772

Merged
merged 7 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions posthog/api/capture.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import re
from datetime import datetime
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Tuple

from dateutil import parser
from django.conf import settings
Expand Down Expand Up @@ -78,31 +78,35 @@ def _get_sent_at(data, request) -> Optional[datetime]:
return parser.isoparse(sent_at)


def _get_token(data, request) -> Optional[str]:
# Support test_[apiKey] for users with multiple environments
def _clean_token(token) -> Tuple[Optional[str], bool]:
is_test_environment = token.startswith("test_")
token = token[5:] if is_test_environment else token
return token, is_test_environment


def get_token(data, request) -> Tuple[Optional[str], bool]:
token = None
if request.POST.get("api_key"):
return request.POST["api_key"]
if request.POST.get("token"):
return request.POST["token"]
if data:
token = request.POST["api_key"]
elif request.POST.get("token"):
token = request.POST["token"]
elif data:
if isinstance(data, list):
data = data[0] # Mixpanel Swift SDK
if isinstance(data, dict):
if data.get("$token"):
return data["$token"] # JS identify call
if data.get("token"):
return data["token"] # JS reloadFeatures call
if data.get("api_key"):
return data["api_key"] # server-side libraries like posthog-python and posthog-ruby
if data.get("properties") and data["properties"].get("token"):
return data["properties"]["token"] # JS capture call
return None
token = data["$token"] # JS identify call
elif data.get("token"):
token = data["token"] # JS reloadFeatures call
elif data.get("api_key"):
token = data["api_key"] # server-side libraries like posthog-python and posthog-ruby
elif data.get("properties") and data["properties"].get("token"):
token = data["properties"]["token"] # JS capture call


# Support test_[apiKey] for users with multiple environments
def _clean_token(token):
is_test_environment = token.startswith("test_")
token = token[5:] if is_test_environment else token
return token, is_test_environment
if token:
return _clean_token(token)
return None, False


def _get_project_id(data, request) -> Optional[int]:
Expand Down Expand Up @@ -160,7 +164,7 @@ def get_event(request):

sent_at = _get_sent_at(data, request)

token = _get_token(data, request)
token, is_test_environment = get_token(data, request)

if not token:
return cors_response(
Expand All @@ -174,9 +178,6 @@ def get_event(request):
),
)

token, is_test_environment = _clean_token(token)
assert token is not None

team = Team.objects.get_team_from_token(token)

if team is None:
Expand Down Expand Up @@ -280,7 +281,9 @@ def get_event(request):
capture_internal(event, distinct_id, ip, site_url, now, sent_at, team.pk)

timer.stop()
statsd.incr(f"posthog_cloud_raw_endpoint_success", tags={"endpoint": "capture",})
statsd.incr(
f"posthog_cloud_raw_endpoint_success", tags={"endpoint": "capture",},
)
return cors_response(request, JsonResponse({"status": 1}))


Expand Down
10 changes: 6 additions & 4 deletions posthog/api/decide.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from posthog.models.feature_flag import get_active_feature_flags
from posthog.utils import cors_response, load_data_from_request

from .capture import _clean_token, _get_project_id, _get_token
from .capture import _get_project_id, get_token


def on_permitted_domain(team: Team, request: HttpRequest) -> bool:
Expand Down Expand Up @@ -88,8 +88,8 @@ def get_decide(request: HttpRequest):
request,
generate_exception_response("decide", f"Malformed request data: {error}", code="malformed_data"),
)
token = _get_token(data, request)
token, is_test_environment = _clean_token(token)

token, _ = get_token(data, request)
team = Team.objects.get_team_from_token(token)
if team is None and token:
project_id = _get_project_id(data, request)
Expand Down Expand Up @@ -123,5 +123,7 @@ def get_decide(request: HttpRequest):
response["featureFlags"] = get_active_feature_flags(team, data["distinct_id"])
if team.session_recording_opt_in and (on_permitted_domain(team, request) or len(team.app_urls) == 0):
response["sessionRecording"] = {"endpoint": "/s/"}
statsd.incr(f"posthog_cloud_raw_endpoint_success", tags={"endpoint": "decide",})
statsd.incr(
f"posthog_cloud_raw_endpoint_success", tags={"endpoint": "decide",},
)
return cors_response(request, JsonResponse(response))
13 changes: 13 additions & 0 deletions posthog/api/test/test_decide.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ def test_personal_api_key_without_project_id(self):
},
)

def test_missing_token(self):
key = PersonalAPIKey(label="X", user=self.user)
key.save()
Person.objects.create(team=self.team, distinct_ids=["example_id"])
FeatureFlag.objects.create(
team=self.team, rollout_percentage=100, name="Test", key="test", created_by=self.user,
)
response = self._post_decide({"distinct_id": "example_id", "api_key": None, "project_id": self.team.id})
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_json = response.json()
self.assertEqual(response_json["featureFlags"], [])
self.assertFalse(response_json["sessionRecording"])

def test_invalid_payload_on_decide_endpoint(self):

invalid_payloads = [base64.b64encode("1-1".encode("utf-8")).decode("utf-8"), "1==1", "{distinct_id-1}"]
Expand Down