Example #1
0
def test_get_project_config(default_project, insta_snapshot, full,
                            has_ops_breakdown):
    # We could use the default_project fixture here, but we would like to avoid 1) hitting the db 2) creating a mock
    default_project.update_option("sentry:relay_pii_config", PII_CONFIG)
    default_project.organization.update_option("sentry:relay_pii_config",
                                               PII_CONFIG)
    keys = ProjectKey.objects.filter(project=default_project)

    with Feature(
        {"organizations:performance-ops-breakdown": has_ops_breakdown}):
        cfg = get_project_config(default_project,
                                 full_config=full,
                                 project_keys=keys)
        cfg = cfg.to_dict()

        # Remove keys that change everytime
        cfg.pop("lastChange")
        cfg.pop("lastFetch")
        cfg.pop("rev")

        # public keys change every time
        assert cfg.pop("projectId") == default_project.id
        assert len(cfg.pop("publicKeys")) == len(keys)
        assert cfg.pop("organizationId") == default_project.organization.id

        insta_snapshot(cfg)
Example #2
0
 def test_cannot_add_error_created_hook_without_flag(self):
     self.login_as(user=self.user)
     with Feature({"organizations:integrations-event-hooks": False}):
         app = self.create_sentry_app(name="SampleApp", organization=self.org)
         url = reverse("sentry-api-0-sentry-app-details", args=[app.slug])
         response = self.client.put(url, data={"events": ("error",)}, format="json")
         assert response.status_code == 403
Example #3
0
    def test_redacted_symbol_source_secrets(self, create_audit_entry):
        with Feature(
            {"organizations:symbol-sources": True, "organizations:custom-symbol-sources": True}
        ):
            config = {
                "id": "honk",
                "name": "honk source",
                "layout": {
                    "type": "native",
                },
                "filetypes": ["pe"],
                "type": "http",
                "url": "http://honk.beep",
                "username": "******",
                "password": "******",
            }
            self.get_valid_response(
                self.org_slug, self.proj_slug, symbolSources=json.dumps([config])
            )
            assert self.project.get_option("sentry:symbol_sources") == json.dumps([config])

            # redact password
            redacted_source = config.copy()
            redacted_source["password"] = {"hidden-secret": True}

            # check that audit entry was created with redacted password
            assert create_audit_entry.called
            call = faux.faux(create_audit_entry)
            assert call.kwarg_equals("data", {"sentry:symbol_sources": [redacted_source]})

            self.get_valid_response(
                self.org_slug, self.proj_slug, symbolSources=json.dumps([redacted_source])
            )
            # on save the magic object should be replaced with the previously set password
            assert self.project.get_option("sentry:symbol_sources") == json.dumps([config])
    def test_approve_requires_invite_members_feature(self, mock_invite_email):
        self.login_as(user=self.user)

        with Feature({"organizations:invite-members": False}):
            resp = self.get_response(self.org.slug, self.invite_request.id, approve=1)
            assert resp.status_code == 400
            assert mock_invite_email.call_count == 0
Example #5
0
    def test_auth_setup(self, auth_log):
        self.auth_provider.delete()
        self.login_as(self.user)

        data = {"init": True, "provider": self.provider_name}

        with Feature(["organizations:sso-basic", "organizations:sso-saml2"]):
            setup = self.client.post(self.setup_path, data)

        assert setup.status_code == 302
        redirect = urlparse(setup.get("Location", ""))
        assert redirect.path == "/sso_url"

        auth = self.accept_auth(follow=True)

        messages = list(map(lambda m: str(m), auth.context["messages"]))

        assert len(messages) == 2
        assert messages[
            0] == "You have successfully linked your account to your SSO provider."
        assert messages[1].startswith(
            "SSO has been configured for your organization")

        # require 2FA disabled when saml is enabled
        org = Organization.objects.get(id=self.org.id)
        assert not org.flags.require_2fa.is_set

        event = AuditLogEntry.objects.get(target_object=org.id,
                                          event=AuditLogEntryEvent.ORG_EDIT,
                                          actor=self.user)
        assert "require_2fa to False when enabling SSO" in event.get_note()
        auth_log.info.assert_called_once_with(
            "Require 2fa disabled during sso setup",
            extra={"organization_id": self.org.id})
Example #6
0
    def test_redacted_symbol_source_secrets_unknown_secret(self, create_audit_entry):
        with Feature(
            {"organizations:symbol-sources": True, "organizations:custom-symbol-sources": True}
        ):
            config = {
                "id": "honk",
                "name": "honk source",
                "layout": {
                    "type": "native",
                },
                "filetypes": ["pe"],
                "type": "http",
                "url": "http://honk.beep",
                "username": "******",
                "password": "******",
            }
            self.get_valid_response(
                self.org_slug, self.proj_slug, symbolSources=json.dumps([config])
            )
            assert self.project.get_option("sentry:symbol_sources") == json.dumps([config])

            # prepare new call, this secret is not known
            new_source = config.copy()
            new_source["password"] = {"hidden-secret": True}
            new_source["id"] = "oops"
            response = self.get_response(
                self.org_slug, self.proj_slug, symbolSources=json.dumps([new_source])
            )
            assert response.status_code == 400
            assert json.loads(response.content) == {
                "symbolSources": ["Sources contain unknown hidden secret"]
            }
Example #7
0
    def test_auth_setup(self, auth_log):
        self.auth_provider.delete()
        self.login_as(self.user)

        data = {'init': True, 'provider': self.provider_name}

        with Feature(['organizations:sso-basic', 'organizations:sso-saml2']):
            setup = self.client.post(self.setup_path, data)

        assert setup.status_code == 302
        redirect = urlparse(setup.get('Location', ''))
        assert redirect.path == '/sso_url'

        auth = self.accept_auth(follow=True)

        messages = map(lambda m: six.text_type(m), auth.context['messages'])

        assert len(messages) == 2
        assert messages[
            0] == 'You have successfully linked your account to your SSO provider.'
        assert messages[1].startswith(
            'SSO has been configured for your organization')

        # require 2FA disabled when saml is enabled
        org = Organization.objects.get(id=self.org.id)
        assert not org.flags.require_2fa.is_set

        event = AuditLogEntry.objects.get(target_object=org.id,
                                          event=AuditLogEntryEvent.ORG_EDIT,
                                          actor=self.user)
        assert 'require_2fa to False when enabling SAML SSO' in event.get_note(
        )
        auth_log.info.assert_called_once_with(
            'Require 2fa disabled during saml sso setup',
            extra={'organization_id': self.org.id})
Example #8
0
def test_project_config_satisfaction_thresholds(
    default_project,
    insta_snapshot,
    has_project_transaction_threshold_overrides,
    has_project_transaction_threshold,
):
    if has_project_transaction_threshold:
        default_project.projecttransactionthreshold_set.create(
            organization=default_project.organization,
            threshold=500,
            metric=TransactionMetric.LCP.value,
        )
    if has_project_transaction_threshold_overrides:
        default_project.projecttransactionthresholdoverride_set.create(
            organization=default_project.organization,
            transaction="foo",
            threshold=400,
            metric=TransactionMetric.DURATION.value,
        )
        default_project.projecttransactionthresholdoverride_set.create(
            organization=default_project.organization,
            transaction="bar",
            threshold=600,
            metric=TransactionMetric.LCP.value,
        )
    with Feature(
        {
            "organizations:transaction-metrics-extraction": True,
        }
    ):
        cfg = get_project_config(default_project, full_config=True)

    cfg = cfg.to_dict()
    insta_snapshot(cfg["config"]["transactionMetrics"]["satisfactionThresholds"])
Example #9
0
 def test_setting_dynamic_sampling_rules(self):
     """
     Test that we can set sampling rules
     """
     with Feature({"organizations:filters-and-sampling": True}):
         resp = self.client.put(self.path, data={"dynamicSampling": _dyn_sampling_data()})
         assert resp.status_code == 200, resp.content
     assert self.project.get_option("sentry:dynamic_sampling") == _dyn_sampling_data()
Example #10
0
def test_error_missing_feature(client, default_project):
    group = Group.objects.create(project=default_project)

    with Feature({"organizations:grouping-tree-ui": False}):
        response = client.get(f"/api/0/issues/{group.id}/grouping/levels/",
                              format="json")
        assert response.status_code == 403
        assert response.data["detail"]["code"] == "missing_feature"
Example #11
0
def test_sources_no_feature(default_project):
    features = {"organizations:symbol-sources": False, "organizations:custom-symbol-sources": False}

    with Feature(features):
        sources = get_sources_for_project(default_project)

    assert len(sources) == 1
    assert sources[0]["type"] == "sentry"
    assert sources[0]["id"] == "sentry:project"
Example #12
0
 def test_cannot_create_with_error_created_hook_without_flag(self):
     with Feature({"organizations:integrations-event-hooks": False}):
         response = self.get_error_response(**self.get_data(
             events=("error", )),
                                            status_code=403)
         assert response.data == {
             "non_field_errors": [
                 "Your organization does not have access to the 'error' resource subscription."
             ]
         }
Example #13
0
 def test_setting_dynamic_sampling_rules_roundtrip(self):
     """
     Tests that we get the same dynamic sampling rules that previously set
     """
     with Feature({"organizations:filters-and-sampling": True}):
         resp = self.client.put(self.path, data={"dynamicSampling": _dyn_sampling_data()})
         assert resp.status_code == 200, resp.content
     response = self.client.get(self.path)
     assert response.status_code == 200
     assert response.data["dynamicSampling"] == _dyn_sampling_data()
def test_exposes_features(call_endpoint, task_runner):
    with Feature({"organizations:metrics-extraction": True}):
        with task_runner():
            result, status_code = call_endpoint(full_config=True)
            assert status_code < 400

        for config in result["configs"].values():
            config = config["config"]
            assert "features" in config
            assert config["features"] == ["organizations:metrics-extraction"]
Example #15
0
def test_sources_builtin_disabled(default_project):
    features = {"organizations:symbol-sources": False, "organizations:custom-symbol-sources": False}

    default_project.update_option("sentry:builtin_symbol_sources", ["microsoft"])

    with Feature(features):
        sources = get_sources_for_project(default_project)

    source_ids = map(lambda s: s["id"], sources)
    assert source_ids == ["sentry:project"]
Example #16
0
def test_project_config_with_span_attributes(default_project, insta_snapshot):
    # The span attributes config is not set with the flag turnd off
    cfg = get_project_config(default_project, full_config=True)
    cfg = cfg.to_dict()
    assert "spanAttributes" not in cfg["config"]

    with Feature("projects:performance-suspect-spans-ingestion"):
        cfg = get_project_config(default_project, full_config=True)

    cfg = cfg.to_dict()
    insta_snapshot(cfg["config"]["spanAttributes"])
    def test_respects_feature_flag(self):
        self.login_as(user=self.owner_user)

        user = self.create_user("*****@*****.**")

        with Feature({"organizations:invite-members": False}):
            resp = self.client.post(
                self.url, {"email": user.email, "role": "member", "teams": [self.team.slug]}
            )

        assert resp.status_code == 403
Example #18
0
 def test_setting_dynamic_sampling_rules_roundtrip(self):
     """
     Tests that we get the same dynamic sampling rules that previously set
     """
     data = _dyn_sampling_data()
     with Feature({"organizations:filters-and-sampling": True}):
         self.get_valid_response(self.org_slug, self.proj_slug, dynamicSampling=data)
         response = self.get_valid_response(self.org_slug, self.proj_slug, method="get")
     saved_config = _remove_ids_from_dynamic_rules(response.data["dynamicSampling"])
     original_data = _remove_ids_from_dynamic_rules(data)
     assert saved_config == original_data
Example #19
0
def test_sources_builtin(default_project):
    features = {"organizations:symbol-sources": True, "organizations:custom-symbol-sources": False}

    default_project.update_option("sentry:builtin_symbol_sources", ["microsoft"])

    with Feature(features):
        sources = get_sources_for_project(default_project)

    # XXX: The order matters here! Project is always first, then builtin sources
    source_ids = map(lambda s: s["id"], sources)
    assert source_ids == ["sentry:project", "sentry:microsoft"]
Example #20
0
def test_sources_custom_disabled(default_project):
    features = {"organizations:symbol-sources": True, "organizations:custom-symbol-sources": False}

    default_project.update_option("sentry:builtin_symbol_sources", [])
    default_project.update_option("sentry:symbol_sources", CUSTOM_SOURCE_CONFIG)

    with Feature(features):
        sources = get_sources_for_project(default_project)

    source_ids = map(lambda s: s["id"], sources)
    assert source_ids == ["sentry:project"]
Example #21
0
    def test_cannot_create_with_error_created_hook_without_flag(self):
        self.login_as(user=self.user)

        with Feature({"organizations:integrations-event-hooks": False}):
            kwargs = {"events": ("error",)}
            response = self._post(**kwargs)

            assert response.status_code == 403, response.content
            assert (
                response.content
                == '{"non_field_errors":["Your organization does not have access to the \'error\' resource subscription."]}'
            )
Example #22
0
    def test_respects_feature_flag(self):
        user = self.create_user("*****@*****.**")

        with Feature({"organizations:invite-members": False}):
            data = {
                "email": user.email,
                "role": "member",
                "teams": [self.team.slug]
            }
            self.get_error_response(self.organization.slug,
                                    **data,
                                    status_code=403)
def test_sources_builtin_unknown(default_project):
    features = {
        "organizations:symbol-sources": True,
        "organizations:custom-symbol-sources": False
    }

    default_project.update_option("sentry:builtin_symbol_sources", ["invalid"])

    with Feature(features):
        sources = get_sources_for_project(default_project)

    source_ids = list(map(lambda s: s["id"], sources))
    assert source_ids == ["sentry:project"]
Example #24
0
def test_sources_custom(default_project):
    features = {"organizations:symbol-sources": True, "organizations:custom-symbol-sources": True}

    # Remove builtin sources explicitly to avoid defaults
    default_project.update_option("sentry:builtin_symbol_sources", [])
    default_project.update_option("sentry:symbol_sources", CUSTOM_SOURCE_CONFIG)

    with Feature(features):
        sources = get_sources_for_project(default_project)

    # XXX: The order matters here! Project is always first, then custom sources
    source_ids = map(lambda s: s["id"], sources)
    assert source_ids == ["sentry:project", "custom"]
def test_relays_dyamic_sampling(client, call_endpoint, default_project, dyn_sampling_data):
    """
    Tests that dynamic sampling configuration set in project details are retrieved in relay configs
    """
    default_project.update_option("sentry:dynamic_sampling", dyn_sampling_data())

    with Feature({"organizations:filters-and-sampling": True}):
        result, status_code = call_endpoint(full_config=False)
        assert status_code < 400
        dynamic_sampling = safe.get_path(
            result, "configs", str(default_project.id), "config", "dynamicSampling"
        )
        assert dynamic_sampling == dyn_sampling_data()
    def test_respects_feature_flag(self):
        self.login_as(user=self.owner_user)

        user = self.create_user('*****@*****.**')

        with Feature({'organizations:invite-members': False}):
            resp = self.client.post(self.url, {
                'email': user.email,
                'role': 'member',
                'teams': [
                    self.team.slug,
                ]
            })

        assert resp.status_code == 403
Example #27
0
def test_project_config_with_breakdown(default_project, insta_snapshot, transaction_metrics):
    with Feature(
        {
            "organizations:performance-ops-breakdown": True,
            "organizations:transaction-metrics-extraction": transaction_metrics == "with_metrics",
        }
    ):
        cfg = get_project_config(default_project, full_config=True)

    cfg = cfg.to_dict()
    insta_snapshot(
        {
            "breakdownsV2": cfg["config"]["breakdownsV2"],
            "transactionMetrics": cfg["config"].get("transactionMetrics"),
        }
    )
Example #28
0
def test_project_config_uses_filters_and_sampling_feature(
        default_project, dyn_sampling_data, has_dyn_sampling, full_config):
    """
    Tests that dynamic sampling information is retrieved for both "full config" and "restricted config"
    but only when the organization has "organizations:filter-and-sampling" feature enabled.
    """
    default_project.update_option("sentry:dynamic_sampling",
                                  dyn_sampling_data())

    with Feature({"organizations:filters-and-sampling": has_dyn_sampling}):
        cfg = get_project_config(default_project, full_config=full_config)

    cfg = cfg.to_dict()
    dynamic_sampling = get_path(cfg, "config", "dynamicSampling")

    if has_dyn_sampling:
        assert dynamic_sampling == dyn_sampling_data()
    else:
        assert dynamic_sampling is None
Example #29
0
def test_project_config_uses_filter_features(default_project, has_custom_filters):
    error_messages = ["some_error"]
    releases = ["1.2.3", "4.5.6"]
    default_project.update_option("sentry:error_messages", error_messages)
    default_project.update_option("sentry:releases", releases)

    with Feature({"projects:custom-inbound-filters": has_custom_filters}):
        cfg = get_project_config(default_project, full_config=True)

    cfg = cfg.to_dict()
    cfg_error_messages = get_path(cfg, "config", "filterSettings", "errorMessages")
    cfg_releases = get_path(cfg, "config", "filterSettings", "releases")

    if has_custom_filters:
        assert {"patterns": error_messages} == cfg_error_messages
        assert {"releases": releases} == cfg_releases
    else:
        assert cfg_releases is None
        assert cfg_error_messages is None
Example #30
0
 def test_setting_dynamic_sampling_rules(self):
     """
     Test that we can set sampling rules
     """
     with Feature({"organizations:filters-and-sampling": True}):
         self.get_valid_response(
             self.org_slug, self.proj_slug, dynamicSampling=_dyn_sampling_data()
         )
     original_config = _dyn_sampling_data()
     saved_config = self.project.get_option("sentry:dynamic_sampling")
     # test that we have unique ids
     ids = set()
     for rule in saved_config["rules"]:
         rid = rule["id"]
         assert rid not in ids
         ids.add(rid)
     next_id = saved_config["next_id"]
     assert next_id not in ids
     # short of ids and next_id the saved config should be the same as the original one
     _remove_ids_from_dynamic_rules(saved_config)
     _remove_ids_from_dynamic_rules(original_config)
     assert original_config == saved_config