Skip to content

Commit

Permalink
feat: code links updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ntindle committed Oct 1, 2024
1 parent 02458f1 commit 995dc03
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 10 deletions.
4 changes: 4 additions & 0 deletions autogpt_platform/backend/backend/blocks/github/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)


# --8<-- [start:GithubCommentBlock]
class GithubCommentBlock(Block):
class Input(BlockSchema):
credentials: GithubCredentialsInput = GithubCredentialsField("repo")
Expand Down Expand Up @@ -104,6 +105,9 @@ def run(
yield "error", f"Failed to post comment: {str(e)}"


# --8<-- [end:GithubCommentBlock]


class GithubMakeIssueBlock(Block):
class Input(BlockSchema):
credentials: GithubCredentialsInput = GithubCredentialsField("repo")
Expand Down
3 changes: 2 additions & 1 deletion autogpt_platform/backend/backend/blocks/google/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from backend.data.model import CredentialsField, CredentialsMetaInput
from backend.util.settings import Secrets

# --8<-- [start:GoogleOAuthIsConfigured]
secrets = Secrets()
GOOGLE_OAUTH_IS_CONFIGURED = bool(
secrets.google_client_id and secrets.google_client_secret
)

# --8<-- [end: GoogleOAuthIsConfigured]
GoogleCredentials = OAuth2Credentials
GoogleCredentialsInput = CredentialsMetaInput[Literal["google"], Literal["oauth2"]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .google import GoogleOAuthHandler
from .notion import NotionOAuthHandler

# --8<-- [start:HANDLERS_BY_NAMEExample]
HANDLERS_BY_NAME: dict[str, type[BaseOAuthHandler]] = {
handler.PROVIDER_NAME: handler
for handler in [
Expand All @@ -11,5 +12,6 @@
NotionOAuthHandler,
]
}
# --8<-- [end: HANDLERS_BY_NAMEExample]

__all__ = ["HANDLERS_BY_NAME"]
10 changes: 10 additions & 0 deletions autogpt_platform/backend/backend/integrations/oauth/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,36 @@


class BaseOAuthHandler(ABC):
# --8<-- [start: BaseOAuthHandler1]
PROVIDER_NAME: ClassVar[str]
DEFAULT_SCOPES: ClassVar[list[str]] = []
# --8<-- [end: BaseOAuthHandler1]

@abstractmethod
# --8<-- [start: BaseOAuthHandler2]
def __init__(self, client_id: str, client_secret: str, redirect_uri: str): ...
# --8<-- [end: BaseOAuthHandler2]

@abstractmethod
# --8<-- [start: BaseOAuthHandler3]
def get_login_url(self, scopes: list[str], state: str) -> str:
# --8<-- [end: BaseOAuthHandler3]
"""Constructs a login URL that the user can be redirected to"""
...

@abstractmethod
# --8<-- [start: BaseOAuthHandler4]
def exchange_code_for_tokens(
self, code: str, scopes: list[str]
) -> OAuth2Credentials:
# --8<-- [end: BaseOAuthHandler4]
"""Exchanges the acquired authorization code from login for a set of tokens"""
...

@abstractmethod
# --8<-- [start: BaseOAuthHandler5]
def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials:
# --8<-- [end: BaseOAuthHandler5]
"""Implements the token refresh mechanism"""
...

Expand Down
4 changes: 4 additions & 0 deletions autogpt_platform/backend/backend/integrations/oauth/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .base import BaseOAuthHandler


# --8<-- [start:GithubOAuthHandler]
class GitHubOAuthHandler(BaseOAuthHandler):
"""
Based on the documentation at:
Expand Down Expand Up @@ -119,3 +120,6 @@ def _request_username(self, access_token: str) -> str | None:

# Get the login (username)
return response.json().get("login")


# --8<-- [end:GithubOAuthHandler]
3 changes: 3 additions & 0 deletions autogpt_platform/backend/backend/integrations/oauth/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
logger = logging.getLogger(__name__)


# --8<-- [start:GoogleOAuthHandler]
class GoogleOAuthHandler(BaseOAuthHandler):
"""
Based on the documentation at https://developers.google.com/identity/protocols/oauth2/web-server
Expand All @@ -27,6 +28,8 @@ class GoogleOAuthHandler(BaseOAuthHandler):
"openid",
]

# --8<-- [end: GoogleOAuthHandler]

def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
self.client_id = client_id
self.client_secret = client_secret
Expand Down
2 changes: 2 additions & 0 deletions autogpt_platform/backend/backend/util/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,12 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
)

# OAuth server credentials for integrations
# --8<-- [start: OAuthServerCredentialsExample]
github_client_id: str = Field(default="", description="GitHub OAuth client ID")
github_client_secret: str = Field(
default="", description="GitHub OAuth client secret"
)
# --8<-- [end: OAuthServerCredentialsExample]
google_client_id: str = Field(default="", description="Google OAuth client ID")
google_client_secret: str = Field(
default="", description="Google OAuth client secret"
Expand Down
58 changes: 51 additions & 7 deletions docs/content/server/new_blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ for a service that we already have OAuth2 support for.

Implementing the block itself is relatively simple. On top of the instructions above,
you're going to add a `credentials` parameter to the `Input` model and the `run` method:

```python
from autogpt_libs.supabase_integration_credentials_store.types import (
APIKeyCredentials,
Expand Down Expand Up @@ -191,10 +192,12 @@ class BlockWithAPIKeyAndOAuth(Block):
) -> BlockOutput:
...
```

The credentials will be automagically injected by the executor in the back end.

The `APIKeyCredentials` and `OAuth2Credentials` models are defined [here](https://github.com/Significant-Gravitas/AutoGPT/blob/master/rnd/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/types.py).
To use them in e.g. an API request, you can either access the token directly:

```python
# credentials: APIKeyCredentials
response = requests.post(
Expand All @@ -212,7 +215,9 @@ response = requests.post(
},
)
```

or use the shortcut `credentials.bearer()`:

```python
# credentials: APIKeyCredentials | OAuth2Credentials
response = requests.post(
Expand All @@ -227,25 +232,64 @@ To add support for a new OAuth2-authenticated service, you'll need to add an `OA
All our existing handlers and the base class can be found [here][OAuth2 handlers].

Every handler must implement the following parts of the [`BaseOAuthHandler`] interface:
- `PROVIDER_NAME`
- `__init__(client_id, client_secret, redirect_uri)`
- `get_login_url(scopes, state)`
- `exchange_code_for_tokens(code)`
- `_refresh_tokens(credentials)`

```python
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler1"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler2"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler3"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler4"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/base.py:BaseOAuthHandler5"
```

As you can see, this is modeled after the standard OAuth2 flow.

Aside from implementing the `OAuthHandler` itself, adding a handler into the system requires two more things:
- Adding the handler class to `HANDLERS_BY_NAME` [here](https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt_platform/backend/backend/integrations/oauth/__init__.py)
- Adding `{provider}_client_id` and `{provider}_client_secret` to the application's `Secrets` [here](https://github.com/Significant-Gravitas/AutoGPT/blob/e3f35d79c7e9fc6ee0cabefcb73e0fad15a0ce2d/autogpt_platform/backend/backend/util/settings.py#L132)

- Adding the handler class to `HANDLERS_BY_NAME` under [`integrations/oauth/__init__.py`](https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt_platform/backend/backend/integrations/oauth/__init__.py)

```python title="autogpt_platform/backend/backend/integrations/oauth/__init__.py"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/__init__.py:HANDLERS_BY_NAMEExample"
```

- Adding `{provider}_client_id` and `{provider}_client_secret` to the application's `Secrets` under [`util/settings.py`](https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt_platform/backend/backend/util/settings.py)

```python title="autogpt_platform/backend/backend/util/settings.py"
--8<-- "autogpt_platform/backend/backend/util/settings.py:OAuthServerCredentialsExample"
```

[OAuth2 handlers]: https://github.com/Significant-Gravitas/AutoGPT/tree/master/autogpt_platform/backend/backend/integrations/oauth
[`BaseOAuthHandler`]: https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt_platform/backend/backend/integrations/oauth/base.py

#### Example: GitHub integration

- GitHub blocks with API key + OAuth2 support: [`blocks/github`](https://github.com/Significant-Gravitas/AutoGPT/tree/master/autogpt_platform/backend/backend/blocks/github/)

```python title="blocks/github/issues.py"
--8<-- "autogpt_platform/backend/backend/blocks/github/issues.py:GithubCommentBlock"
```

- GitHub OAuth2 handler: [`integrations/oauth/github.py`](https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt_platform/backend/backend/integrations/oauth/github.py)

```python title="blocks/github/github.py"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/github.py:GithubOAuthHandler"
```

#### Example: Google integration

- Google OAuth2 handler: [`integrations/oauth/google.py`](https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpt_platform/backend/backend/integrations/oauth/google.py)

```python title="integrations/oauth/google.py"
--8<-- "autogpt_platform/backend/backend/integrations/oauth/google.py:GoogleOAuthHandler"
```

You can see that google has defined a `DEFAULT_SCOPES` variable, this is used to set the scopes that are requested no matter what the user asks for.

```python title="blocks/google/_auth.py"
--8<-- "autogpt_platform/backend/backend/blocks/google/_auth.py:GoogleOAuthIsConfigured"
```

You can also see that `GOOGLE_OAUTH_IS_CONFIGURED` is used to disable the blocks that require OAuth if the oauth is not configured. This is in the `__init__` method of each block. This is because there is no api key fallback for google blocks so we need to make sure that the oauth is configured before we allow the user to use the blocks.

## Key Points to Remember

- **Unique ID**: Give your block a unique ID in the **init** method.
Expand Down
4 changes: 2 additions & 2 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ markdown_extensions:
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.snippets:
auto_append:
- includes/abbreviations.md
base_path: ['.','../']
check_paths: true
- pymdownx.superfences:
custom_fences:
- name: mermaid
Expand Down

0 comments on commit 995dc03

Please sign in to comment.