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

[Confluence] Add support for OAuth 2.0 #493

Merged
merged 5 commits into from
Oct 3, 2024
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
68 changes: 64 additions & 4 deletions confluence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ This package is a utility for connecting Cohere to Confluence, featuring a simpl
## Limitations

The Confluence connector will search within the space defined in your `.env`, and performs a case-insensitive full-text
search against all text fields Confluence indexes by default.
search against all text fields Confluence indexes by default.

Note: The search uses Confluence's advanced search language called [CQL](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/). If you wish to customize this connector's search experience, please refer to the above linked documentation for more details.

## Configuration

This connector requires the following environment variables:
There are two authentication methods available with this connector. You can either set it up using the service auth
method, or with OAuth.

### Service Auth

When using the service auth method, you must set the following env vars:

```
CONFLUENCE_USER: User email address
Expand All @@ -20,8 +25,63 @@ CONFLUENCE_PRODUCT_URL: URL to your Confluence instance, including https:// sche
CONFLUENCE_SPACE_NAME: Name of a space within your Confluence wiki
```

The API token can be generated by logging into Confluence and going
to the [API tokens page](https://id.atlassian.com/manage-profile/security/api-tokens).
The API token can be generated by logging into Confluence and going to the [API tokens page](https://id.atlassian.com/manage-profile/security/api-tokens).

### OAuth

When using OAuth for authentication, the connector does not require any additional environment variables. Instead,
the OAuth flow should occur outside of the Connector and Cohere's API will forward the user's access token to this
connector through the `Authorization` header.

To use OAuth, you must first create an OAuth 2.0 app in Confluence. To do this, go to the
Atlassian [Developer Console](https://developer.atlassian.com/console/myapps/), and use the option to create a new
OAuth 2.0 integration.

You must configure in the developer console the OAuth scopes that are allowed to be requested by the client. There are
two options in Confluence, classic scopes or granular scopes. Use the granular scopes option, and ensure that the
following are enabled:

* read:content:confluence
* read:content-details:confluence
* read:page:confluence
* read:custom-content:confluence

The `offline_access` scope must also be requested for refresh tokens to work. This scope does not appear in the
list of scopes in the Atlassian OAuth permissions page, but it must be included in the scopes added to the connector
configuration in Cohere dashboard.

You must also configure the authorization settings. Go to the Authorization page, and configure the app to use the
authorization type OAuth 2.0 (3L0). On the configuration page for the authorization page, enter the callback URL as:

https://api.cohere.com/v1/connectors/oauth/token

Go to the settings option for the app, enter the app name and description under general settings, and then take
note of the OAuth client id and secret from this page.

Once your Confluence OAuth credentials are ready, you can register the connector in Cohere's API with the following
configuration:

```bash
curl -X POST \
'https://api.cohere.ai/v1/connectors' \
--header 'Accept: */*' \
--header 'Authorization: Bearer {COHERE-API-KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Confluence",
"url": "{YOUR_CONNECTOR-URL}",
"oauth": {
"client_id": "{CONFLUENCE-OAUTH-CLIENT-ID}",
"client_secret": "{CONFLUENCE-OAUTH-CLIENT-SECRET}",
"authorize_url": "https://auth.atlassian.com/authorize?audience=api.atlassian.com&response_type=code&prompt=consent",
"token_url": "https://auth.atlassian.com/oauth/token",
"scope": "read:content:confluence read:content-details:confluence read:page:confluence read:custom-content:confluence offline_access"
}
}'
```

With OAuth the connector will be able to search any Confluence pages that the user has access to.

### Optional Configuration

```
Expand Down
85 changes: 73 additions & 12 deletions confluence/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions confluence/provider/app.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import logging

from connexion.exceptions import Unauthorized
from flask import abort
from flask import current_app as app
from flask import abort, request, current_app as app

from . import UpstreamProviderError, provider

logger = logging.getLogger(__name__)
AUTHORIZATION_HEADER = "Authorization"
BEARER_PREFIX = "Bearer "


def search(body):
logger.debug(f'Search request: {body["query"]}')
access_token = get_access_token()

if access_token == app.config.get("CONNECTOR_API_KEY", None):
access_token = None

try:
data = provider.search(body["query"])
data = provider.search(body["query"], access_token)
logger.info(f"Found {len(data)} results")
except UpstreamProviderError as error:
logger.error(f"Upstream search error: {error.message}")
Expand All @@ -22,6 +27,13 @@ def search(body):
return {"results": data}, 200, {"X-Connector-Id": app.config.get("APP_ID")}


def get_access_token() -> str | None:
authorization_header = request.headers.get(AUTHORIZATION_HEADER, "")
if authorization_header.startswith(BEARER_PREFIX):
return authorization_header.removeprefix(BEARER_PREFIX)
return None


def apikey_auth(token):
api_key = str(app.config.get("CONNECTOR_API_KEY", ""))
if api_key != "" and token != api_key:
Expand Down
Loading
Loading