Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 41 additions & 2 deletions ee/identitymanager/identity_managers/auth0/auth0_authverifier.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,55 @@
import logging
import os

import jwt
import requests
from fastapi import HTTPException

from keep.identitymanager.authenticatedentity import AuthenticatedEntity
from keep.identitymanager.authverifierbase import AuthVerifierBase
from keep.identitymanager.rbac import Admin as AdminRole

logger = logging.getLogger(__name__)


def _discover_jwks_uri(auth_domain: str) -> str:
"""Discover the JWKS URI via the OpenID Connect Discovery endpoint.

Per the OpenID Connect Discovery 1.0 specification
(https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3),
the ``jwks_uri`` should be obtained from the provider's discovery document
at ``{issuer}/.well-known/openid-configuration``.

Falls back to the Auth0-style ``/.well-known/jwks.json`` path when the
discovery document is unavailable or does not contain ``jwks_uri``.
"""
discovery_url = f"https://{auth_domain}/.well-known/openid-configuration"
try:
resp = requests.get(discovery_url, timeout=10)
resp.raise_for_status()
discovered_uri = resp.json().get("jwks_uri")
if discovered_uri:
return discovered_uri
logger.warning(
"OpenID discovery document at %s did not contain jwks_uri, "
"falling back to /.well-known/jwks.json",
discovery_url,
)
except Exception:
logger.warning(
"Failed to fetch OpenID discovery document from %s, "
"falling back to /.well-known/jwks.json",
discovery_url,
exc_info=True,
)
# Fallback: Auth0's conventional JWKS endpoint
return f"https://{auth_domain}/.well-known/jwks.json"


# Note: cache_keys is set to True to avoid fetching the jwks keys on every request
auth_domain = os.environ.get("AUTH0_DOMAIN")
if auth_domain:
jwks_uri = f"https://{auth_domain}/.well-known/jwks.json"
jwks_uri = _discover_jwks_uri(auth_domain)
jwks_client = jwt.PyJWKClient(
jwks_uri, cache_keys=True, headers={"User-Agent": "keep-api"}
)
Expand All @@ -29,7 +68,7 @@ def __init__(self, scopes: list[str] = []) -> None:
self.auth_domain = os.environ.get("AUTH0_DOMAIN")
if not self.auth_domain:
raise Exception("Missing AUTH0_DOMAIN environment variable")
self.jwks_uri = f"https://{self.auth_domain}/.well-known/jwks.json"
self.jwks_uri = _discover_jwks_uri(self.auth_domain)
# Note: cache_keys is set to True to avoid fetching the jwks keys on every request
# but it currently caches only per-route. After moving this auth verifier to be a singleton, we can cache it globally
self.issuer = f"https://{self.auth_domain}/"
Expand Down
19 changes: 18 additions & 1 deletion keep/providers/jira_provider/jira_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,24 @@ def __create_issue(
fields["components"] = [{"name": component} for component in components]

if custom_fields:
fields.update(custom_fields)
# Filter out priority field if it's set to "none" or empty
filtered_fields = {}
for key, value in custom_fields.items():
if key == "priority" and (not value or str(value).lower() in ["none", "", "null"]):
self.logger.info(f"Skipping priority field with value '{value}' as it may not be available on the issue screen")
continue
filtered_fields[key] = value
fields.update(filtered_fields)

# Also handle priority that might come through kwargs
if kwargs:
filtered_kwargs = {}
for key, value in kwargs.items():
if key == "priority" and (not value or str(value).lower() in ["none", "", "null"]):
self.logger.info(f"Skipping priority field from kwargs with value '{value}' as it may not be available on the issue screen")
continue
filtered_kwargs[key] = value
fields.update(filtered_kwargs)

request_body = {"fields": fields}

Expand Down
7 changes: 4 additions & 3 deletions keep/providers/kafka_provider/kafka_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ def get_client_id_from_caller(self):
# Here, you should implement the logic to extract client_id based on the caller.
# This can be tricky and might require you to traverse the call stack.
# Return a default or None if you can't find it.
import copy

frame = inspect.currentframe()
client_id = None
while frame:
local_vars = copy.copy(frame.f_locals)
# Use dict() to convert frame.f_locals into a plain dict.
# In Python 3.13+, frame.f_locals returns a FrameLocalsProxy
# which cannot be copied via copy.copy() (pickle fails).
local_vars = dict(frame.f_locals)
for var_name, var_value in local_vars.items():
if isinstance(var_value, KafkaProvider):
client_id = var_value.context_manager.tenant_id
Expand Down
7 changes: 7 additions & 0 deletions keep/rulesengine/rulesengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,13 @@ def _get_or_create_incident(
# update the incident name template
# note that it will be commited later, when the incident is commited
incident_name = re.sub(pattern, var_to_replace, incident_name)
# Re-apply the incident prefix after template regeneration.
# The template generates a plain name without the prefix, which
# would otherwise overwrite the prefixed name set during creation
# or the earlier prefix check.
# See: https://github.com/keephq/keep/issues/5450
if rule.incident_prefix and rule.incident_prefix not in incident_name:
incident_name = f"{rule.incident_prefix}-{existed_incident.running_number} - {incident_name}"
# we are done
if existed_incident.user_generated_name != incident_name:
existed_incident.user_generated_name = incident_name
Expand Down
168 changes: 168 additions & 0 deletions tests/providers/jira_provider/test_jira_priority_fix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""
Test for Jira provider priority field handling.
"""

import json
import pytest
from unittest.mock import Mock, patch
import responses

from keep.contextmanager.contextmanager import ContextManager
from keep.providers.jira_provider.jira_provider import JiraProvider
from keep.providers.models.provider_config import ProviderConfig


@pytest.fixture
def jira_provider():
"""Fixture for Jira provider."""
context_manager = ContextManager(tenant_id="test", workflow_id="test")
config = ProviderConfig(
authentication={
"email": "test@test.com",
"api_token": "test_token",
"host": "https://test.atlassian.net"
},
name="test-jira"
)

provider = JiraProvider(context_manager, "jira", config)
return provider


class TestJiraPriorityHandling:
"""Test class for Jira priority field handling."""

@responses.activate
def test_create_issue_excludes_none_priority(self, jira_provider):
"""Test that priority with 'none' value is excluded from request."""

# Mock the create issue endpoint
responses.add(
responses.POST,
"https://test.atlassian.net/rest/api/2/issue",
json={"id": "123", "key": "TEST-123", "self": "https://test.atlassian.net/rest/api/2/issue/123"},
status=201
)

# Call the create issue method with priority: "none"
result = jira_provider._JiraProvider__create_issue(
project_key="TEST",
summary="Test Issue",
description="Test Description",
issue_type="Bug",
custom_fields={"priority": "none"}
)

# Verify the request was made without priority field
assert len(responses.calls) == 1
request_body = json.loads(responses.calls[0].request.body)

# Priority should not be in the fields
assert "priority" not in request_body["fields"]
assert "summary" in request_body["fields"]
assert request_body["fields"]["summary"] == "Test Issue"

@responses.activate
def test_create_issue_excludes_empty_priority(self, jira_provider):
"""Test that priority with empty value is excluded from request."""

responses.add(
responses.POST,
"https://test.atlassian.net/rest/api/2/issue",
json={"id": "124", "key": "TEST-124", "self": "https://test.atlassian.net/rest/api/2/issue/124"},
status=201
)

# Test with empty string priority
jira_provider._JiraProvider__create_issue(
project_key="TEST",
summary="Test Issue",
description="Test Description",
issue_type="Bug",
custom_fields={"priority": ""}
)

request_body = json.loads(responses.calls[0].request.body)
assert "priority" not in request_body["fields"]

@responses.activate
def test_create_issue_excludes_null_priority(self, jira_provider):
"""Test that priority with null value is excluded from request."""

responses.add(
responses.POST,
"https://test.atlassian.net/rest/api/2/issue",
json={"id": "125", "key": "TEST-125", "self": "https://test.atlassian.net/rest/api/2/issue/125"},
status=201
)

# Test with None priority
jira_provider._JiraProvider__create_issue(
project_key="TEST",
summary="Test Issue",
description="Test Description",
issue_type="Bug",
custom_fields={"priority": None}
)

request_body = json.loads(responses.calls[0].request.body)
assert "priority" not in request_body["fields"]

@responses.activate
def test_create_issue_includes_valid_priority(self, jira_provider):
"""Test that valid priority values are included in request."""

responses.add(
responses.POST,
"https://test.atlassian.net/rest/api/2/issue",
json={"id": "126", "key": "TEST-126", "self": "https://test.atlassian.net/rest/api/2/issue/126"},
status=201
)

# Test with valid priority
jira_provider._JiraProvider__create_issue(
project_key="TEST",
summary="Test Issue",
description="Test Description",
issue_type="Bug",
custom_fields={"priority": {"name": "High"}}
)

request_body = json.loads(responses.calls[0].request.body)
assert "priority" in request_body["fields"]
assert request_body["fields"]["priority"] == {"name": "High"}

@responses.activate
def test_create_issue_preserves_other_custom_fields(self, jira_provider):
"""Test that other custom fields are preserved when priority is filtered."""

responses.add(
responses.POST,
"https://test.atlassian.net/rest/api/2/issue",
json={"id": "127", "key": "TEST-127", "self": "https://test.atlassian.net/rest/api/2/issue/127"},
status=201
)

# Test with priority: none and other custom fields
jira_provider._JiraProvider__create_issue(
project_key="TEST",
summary="Test Issue",
description="Test Description",
issue_type="Bug",
custom_fields={
"priority": "none",
"customfield_12345": "Custom Value",
"environment": "Production"
}
)

request_body = json.loads(responses.calls[0].request.body)

# Priority should be filtered out
assert "priority" not in request_body["fields"]

# Other custom fields should be preserved
assert "customfield_12345" in request_body["fields"]
assert "environment" in request_body["fields"]
assert request_body["fields"]["customfield_12345"] == "Custom Value"
assert request_body["fields"]["environment"] == "Production"
Loading