Skip to content

Commit

Permalink
[Integration][Jira] Added support for oauth2 for live events (#1429)
Browse files Browse the repository at this point in the history
### **User description**
# Description

What - Added support for live events using OAuth2 bearer token for
authentication

Why - Enabling oauth flow for creating integrations including live
events

How - Adding a new flow for creating webhooks using the REST API v3 of
Jira for creating dynamic webhooks

## Type of change

Please leave one option from the following and delete the rest:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] New Integration (non-breaking change which adds a new integration)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Non-breaking change (fix of existing functionality that will not
change current behavior)
- [ ] Documentation (added/updated documentation)

<h4> All tests should be run against the port production
environment(using a testing org). </h4>

### Core testing checklist

- [ ] Integration able to create all default resources from scratch
- [ ] Resync finishes successfully
- [ ] Resync able to create entities
- [ ] Resync able to update entities
- [ ] Resync able to detect and delete entities
- [ ] Scheduled resync able to abort existing resync and start a new one
- [ ] Tested with at least 2 integrations from scratch
- [ ] Tested with Kafka and Polling event listeners
- [ ] Tested deletion of entities that don't pass the selector


### Integration testing checklist

- [ ] Integration able to create all default resources from scratch
- [ ] Resync able to create entities
- [ ] Resync able to update entities
- [ ] Resync able to detect and delete entities
- [ ] Resync finishes successfully
- [ ] If new resource kind is added or updated in the integration, add
example raw data, mapping and expected result to the `examples` folder
in the integration directory.
- [ ] If resource kind is updated, run the integration with the example
data and check if the expected result is achieved
- [ ] If new resource kind is added or updated, validate that
live-events for that resource are working as expected
- [ ] Docs PR link [here](#)

### Preflight checklist

- [ ] Handled rate limiting
- [ ] Handled pagination
- [ ] Implemented the code in async
- [ ] Support Multi account

## Screenshots

Include screenshots from your environment showing how the resources of
the integration will look.

## API Documentation

Provide links to the API documentation used for this integration.


___

### **PR Type**
Enhancement, Tests


___

### **Description**
- Added OAuth2 support for Jira live events using webhooks.

- Refactored webhook creation logic for OAuth and non-OAuth hosts.

- Updated tests to validate new webhook creation flow.

- Incremented version and updated changelog for release.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>client.py</strong><dd><code>Add OAuth2 webhook creation
and refactor logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

integrations/jira/jira/client.py

<li>Added OAuth2-specific webhook creation logic.<br> <li> Introduced
<code>is_oauth_host</code> method to determine host type.<br> <li>
Refactored webhook creation into separate methods for OAuth and
<br>non-OAuth.<br> <li> Updated request authentication handling for
OAuth hosts.


</details>


  </td>
<td><a
href="https://github.com/port-labs/ocean/pull/1429/files#diff-a98c78f4ca9c522a3bfdb5542ddb9f19e863b787dff0b39169b91f02facb53be">+51/-10</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>main.py</strong><dd><code>Update webhook setup to use
new method</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

integrations/jira/main.py

- Updated to use the new `create_webhooks` method.


</details>


  </td>
<td><a
href="https://github.com/port-labs/ocean/pull/1429/files#diff-2b2f2f1d5fe354b3995d362c13d0ef5e3ffe4161ab827726410de8039337e56f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>test_client.py</strong><dd><code>Update tests for new
webhook creation logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

integrations/jira/tests/test_client.py

<li>Updated tests to use <code>create_webhooks</code> instead of
<code>create_events_webhook</code>.<br> <li> Verified OAuth and
non-OAuth webhook creation flows.


</details>


  </td>
<td><a
href="https://github.com/port-labs/ocean/pull/1429/files#diff-60921df7af01aa40bb94b945d9226ba1137e1e4c7f55aa67678f3e26a6e299a0">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>CHANGELOG.md</strong><dd><code>Update changelog for
OAuth live events support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

integrations/jira/CHANGELOG.md

<li>Documented addition of OAuth live events support.<br> <li> Added
version 0.3.2 release notes.


</details>


  </td>
<td><a
href="https://github.com/port-labs/ocean/pull/1429/files#diff-7a3a5e341adb6a81f8d0177dfc07d8b2d4230c39ede5d93de91c7b205a0e29fa">+9/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>pyproject.toml</strong><dd><code>Bump version to
0.3.2</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

integrations/jira/pyproject.toml

- Incremented version to 0.3.2.


</details>


  </td>
<td><a
href="https://github.com/port-labs/ocean/pull/1429/files#diff-58a3ec0700a093f3b96dda2068e48b8e26665e1c83dd8207ef3c8cac8480e8cc">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about Qodo Merge
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
  • Loading branch information
matan84 authored Feb 24, 2025
1 parent f274ccd commit ace9a32
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 13 deletions.
9 changes: 9 additions & 0 deletions integrations/jira/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- towncrier release notes start -->

## 0.3.2 (2025-02-24)


### Improvements

- Added support for OAuth live events for Jira using the webhooks api.
- https://developer.atlassian.com/cloud/jira/platform/webhooks/#registering-a-webhook-using-the-rest-api--for-connect-and-oauth-2-0-apps-


## 0.3.1 (2025-02-23)


Expand Down
57 changes: 48 additions & 9 deletions integrations/jira/jira/client.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import asyncio
import uuid
from typing import Any, AsyncGenerator, Generator

import httpx
from httpx import Auth, BasicAuth, Request, Response, Timeout
from loguru import logger
from port_ocean.clients.auth.oauth_client import (
OAuthClient,
)

from port_ocean.clients.auth.oauth_client import OAuthClient
from port_ocean.context.ocean import ocean
from port_ocean.utils import http_async_client

Expand All @@ -30,6 +30,12 @@
"user_deleted",
]

OAUTH2_WEBHOOK_EVENTS = [
"jira:issue_created",
"jira:issue_updated",
"jira:issue_deleted",
]


class BearerAuth(Auth):
def __init__(self, token: str):
Expand All @@ -51,20 +57,24 @@ def __init__(self, jira_url: str, jira_email: str, jira_token: str) -> None:
self.jira_token = jira_token

# If the Jira URL is directing to api.atlassian.com, we use OAuth2 Bearer Auth
if "api.atlassian.com" in self.jira_url:
if self.is_oauth_host():
self.jira_api_auth = self._get_bearer()
self.webhooks_url = f"{self.jira_rest_url}/api/3/webhook"
else:
self.jira_api_auth = BasicAuth(self.jira_email, self.jira_token)
self.webhooks_url = f"{self.jira_rest_url}/webhooks/1.0/webhook"

self.api_url = f"{self.jira_rest_url}/api/3"
self.teams_base_url = f"{self.jira_url}/gateway/api/public/teams/v1/org"
self.webhooks_url = f"{self.jira_rest_url}/webhooks/1.0/webhook"

self.client = http_async_client
self.client.auth = self.jira_api_auth
self.client.timeout = Timeout(30)
self._semaphore = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)

def is_oauth_host(self) -> bool:
return "api.atlassian.com" in self.jira_url

def _get_bearer(self) -> BearerAuth:
try:
return BearerAuth(self.external_access_token)
Expand Down Expand Up @@ -169,12 +179,41 @@ def _generate_base_req_params(
"startAt": startAt,
}

async def _get_webhooks(self) -> list[dict[str, Any]]:
return await self._send_api_request("GET", url=self.webhooks_url)
async def _create_events_webhook_oauth(self, app_host: str) -> None:
webhook_target_app_host = f"{app_host}/integration/webhook"
webhooks = (await self._send_api_request("GET", url=self.webhooks_url)).get(
"values"
)

if webhooks:
logger.info("Ocean real time reporting webhook already exists")
return

# We search a random project to get data from all projects
random_project = str(uuid.uuid4())

body = {
"url": webhook_target_app_host,
"webhooks": [
{
"jqlFilter": f"project not in ({random_project})",
"events": OAUTH2_WEBHOOK_EVENTS,
}
],
}

await self._send_api_request("POST", self.webhooks_url, json=body)
logger.info("Ocean real time reporting webhook created")

async def create_webhooks(self, app_host: str) -> None:
if self.is_oauth_host():
await self._create_events_webhook_oauth(app_host)
else:
await self._create_events_webhook(app_host)

async def create_events_webhook(self, app_host: str) -> None:
async def _create_events_webhook(self, app_host: str) -> None:
webhook_target_app_host = f"{app_host}/integration/webhook"
webhooks = await self._get_webhooks()
webhooks = await self._send_api_request("GET", url=self.webhooks_url)

for webhook in webhooks:
if webhook.get("url") == webhook_target_app_host:
Expand Down
2 changes: 1 addition & 1 deletion integrations/jira/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async def setup_application() -> None:
return

client = create_jira_client()
await client.create_events_webhook(base_url)
await client.create_webhooks(base_url)


@ocean.on_resync(Kinds.PROJECT)
Expand Down
2 changes: 1 addition & 1 deletion integrations/jira/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "jira"
version = "0.3.1"
version = "0.3.2"
description = "Integration to bring information from Jira into Port"
authors = ["Mor Paz <mor@getport.io>"]

Expand Down
4 changes: 2 additions & 2 deletions integrations/jira/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ async def test_create_events_webhook(mock_jira_client: JiraClient) -> None:
{"id": "new_webhook"}, # Creation response
]

await mock_jira_client.create_events_webhook(app_host)
await mock_jira_client.create_webhooks(app_host)

# Verify webhook creation call
create_call = mock_request.call_args_list[1]
Expand All @@ -288,5 +288,5 @@ async def test_create_events_webhook(mock_jira_client: JiraClient) -> None:
) as mock_request:
mock_request.return_value = [{"url": webhook_url}]

await mock_jira_client.create_events_webhook(app_host)
await mock_jira_client.create_webhooks(app_host)
mock_request.assert_called_once() # Only checks for existence

0 comments on commit ace9a32

Please sign in to comment.