feat(oauth): implement OAuth

This commit is contained in:
Harry
2025-07-07 14:04:58 +08:00
parent 2aedda0652
commit 0ec96aea2a
25 changed files with 607 additions and 25 deletions
+4
View File
@@ -0,0 +1,4 @@
INSTALL_METHOD=remote
REMOTE_INSTALL_HOST=debug-plugin.dify.dev
REMOTE_INSTALL_PORT=5003
REMOTE_INSTALL_KEY=********-****-****-****-************
+37
View File
@@ -0,0 +1,37 @@
# GitHub
## Overview
GitHub is a web-based platform for version control and collaboration, primarily used for software development. As a tool in Dify, it provides users the ability to search repositories by keywords.
## Configuration
### 1. Apply for API Key and Version
Please apply for an [API Key](https://github.com/settings/personal-access-tokens) and the [API version](https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28).
### 2. Get GitHub tools from Plugin Marketplace
The GitHub tools can be found in the Plugin Marketplace. Please install it first.
### 3. Fill in the configuration in Dify
On the Dify navigation page, click `Tools > GitHub > Authorize` and fill in the API Key.
![](./_assets/github_1.PNG)
### 4. Using the tool
You can use the GitHub tool in the following application types:
#### Chatflow / Workflow applications
Both Chatflow and Workflow applications support adding a GitHub tool node.
![](./_assets/github_2.PNG)
#### Agent applications
Add the GitHub tool in the Agent application, then enter repository search instructions to call this tool.
![](./_assets/github_3.PNG)
Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="800px" height="800px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>github [#142]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill="#000000">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399"
id="github-[#142]">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+6
View File
@@ -0,0 +1,6 @@
from dify_plugin import DifyPluginEnv, Plugin
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
if __name__ == "__main__":
plugin.run()
+37
View File
@@ -0,0 +1,37 @@
author: langgenius
created_at: '2024-09-20T08:03:44.658609186Z'
description:
en_US: GitHub is an online software source code hosting service.
pt_BR: GitHub é uma plataforma online para serviços de hospedagem de código fonte
de software.
zh_Hans: GitHub是一个在线软件源代码托管服务平台。
icon: icon.svg
label:
en_US: GitHub
pt_BR: GitHub
zh_Hans: GitHub
meta:
arch:
- amd64
- arm64
runner:
entrypoint: main
language: python
version: '3.12'
version: 0.0.1
name: github
plugins:
tools:
- provider/github.yaml
resource:
memory: 1048576
permission:
model:
enabled: true
llm: true
tool:
enabled: true
tags:
- utilities
type: plugin
version: 0.0.5
+69
View File
@@ -0,0 +1,69 @@
import secrets
import urllib.parse
from collections.abc import Mapping
from typing import Any
import requests
from werkzeug import Request
from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
class GithubProvider(ToolProvider):
_AUTH_URL = "https://github.com/login/oauth/authorize"
_TOKEN_URL = "https://github.com/login/oauth/access_token"
_API_USER_URL = "https://api.github.com/user"
def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Mapping[str, Any]) -> str:
"""
Generate the authorization URL for the Github OAuth.
"""
state = secrets.token_urlsafe(16)
params = {
"client_id": system_credentials["client_id"],
"redirect_uri": redirect_uri,
"scope": system_credentials.get("scope", "read:user"),
"state": state,
# Optionally: allow_signup, login, etc.
}
return f"{self._AUTH_URL}?{urllib.parse.urlencode(params)}"
def _oauth_get_credentials(
self, redirect_uri: str, system_credentials: Mapping[str, Any], request: Request
) -> Mapping[str, Any]:
"""
Exchange code for access_token.
"""
code = request.args.get("code")
if not code:
raise ValueError("No code provided")
# Optionally: validate state here
data = {
"client_id": system_credentials["client_id"],
"client_secret": system_credentials["client_secret"],
"code": code,
"redirect_uri": redirect_uri,
}
headers = {"Accept": "application/json"}
response = requests.post(self._TOKEN_URL, data=data, headers=headers, timeout=10)
response_json = response.json()
access_token = response_json.get("access_token")
if not access_token:
raise ValueError(f"Error in GitHub OAuth: {response_json}")
return {"access_token": access_token}
def _validate_credentials(self, credentials: dict) -> None:
try:
if "access_tokens" not in credentials or not credentials.get("access_tokens"):
raise ToolProviderCredentialValidationError("GitHub API Access Token is required.")
headers = {
"Authorization": f"Bearer {credentials['access_tokens']}",
"Accept": "application/vnd.github+json",
}
response = requests.get(self._API_USER_URL, headers=headers, timeout=10)
if response.status_code != 200:
raise ToolProviderCredentialValidationError(response.json().get("message"))
except Exception as e:
raise ToolProviderCredentialValidationError(str(e)) from e
@@ -0,0 +1,80 @@
credentials_for_provider:
access_tokens:
help:
en_US: Get your Access Tokens from GitHub
pt_BR: Obtenha sua chave da API do Google no Google
zh_Hans: 从 GitHub 获取您的 Access Tokens
label:
en_US: Access Tokens
pt_BR: Tokens de acesso
zh_Hans: Access Tokens
placeholder:
en_US: Please input your GitHub Access Tokens
pt_BR: Insira seus Tokens de Acesso do GitHub
zh_Hans: 请输入你的 GitHub Access Tokens
required: true
type: secret-input
url: https://github.com/settings/tokens?type=beta
api_version:
default: '2022-11-28'
help:
en_US: Get your API Version from GitHub
pt_BR: Obtenha sua versão da API do GitHub
zh_Hans: 从 GitHub 获取您的 API Version
label:
en_US: API Version
pt_BR: Versão da API
zh_Hans: API Version
placeholder:
en_US: Please input your GitHub API Version
pt_BR: Insira sua versão da API do GitHub
zh_Hans: 请输入你的 GitHub API Version
required: false
type: text-input
url: https://docs.github.com/en/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28
oauth_schema:
client_schema:
- name: "client_id"
type: "secret-input"
label:
zh_Hans: "Client ID"
en_US: "Client ID"
- name: "client_secret"
type: "secret-input"
label:
zh_Hans: "Client Secret"
en_US: "Client Secret"
- name: "redirect_uri"
type: "text-input"
label:
zh_Hans: "Redirect URI"
en_US: "Redirect URI"
credentials_schema:
- name: "access_token"
type: "secret-input"
label:
zh_Hans: "Access Token"
en_US: "Access Token"
extra:
python:
source: provider/github.py
identity:
author: CharlieWei
description:
en_US: GitHub is an online software source code hosting service.
pt_BR: GitHub é uma plataforma online para serviços de hospedagem de código fonte
de software.
zh_Hans: GitHub 是一个在线软件源代码托管服务平台。
icon: icon.svg
label:
en_US: GitHub
pt_BR: GitHub
zh_Hans: GitHub
name: github
tags:
- utilities
tools:
- tools/github_repositories.yaml
- tools/github_repository_readme.yaml
+1
View File
@@ -0,0 +1 @@
dify_plugin==0.5.0b3
@@ -0,0 +1,86 @@
import json
from collections.abc import Generator
from datetime import datetime
from typing import Any
from urllib.parse import quote
import requests
from dify_plugin import Tool
from dify_plugin.entities.provider_config import CredentialType
from dify_plugin.entities.tool import ToolInvokeMessage
class GithubRepositoriesTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
"""
invoke tools
"""
top_n = tool_parameters.get("top_n", 5)
query = tool_parameters.get("query", "")
credential_type = self.runtime.credential_type
if not query:
yield self.create_text_message("Please input symbol")
if (
credential_type == CredentialType.API_KEY and "access_tokens" not in self.runtime.credentials
) or not self.runtime.credentials.get("access_tokens"):
yield self.create_text_message("GitHub API Access Tokens is required.")
if (
credential_type == CredentialType.OAUTH and "access_tokens" not in self.runtime.credentials
) or not self.runtime.credentials.get("access_tokens"):
yield self.create_text_message("GitHub OAuth Access Tokens is required.")
if "api_version" not in self.runtime.credentials or not self.runtime.credentials.get("api_version"):
api_version = "2022-11-28"
else:
api_version = self.runtime.credentials.get("api_version")
try:
headers = {
"Content-Type": "application/vnd.github+json",
"Authorization": f"Bearer {self.runtime.credentials.get('access_tokens')}",
"X-GitHub-Api-Version": api_version,
}
s = requests.session()
api_domain = "https://api.github.com"
response = s.request(
method="GET",
headers=headers,
url=f"{api_domain}/search/repositories?q={quote(query)}&sort=stars&per_page={top_n}&order=desc",
)
response_data = response.json()
if response.status_code == 200 and isinstance(response_data.get("items"), list):
contents = []
if len(response_data.get("items")) > 0:
for item in response_data.get("items"):
content = {}
updated_at_object = datetime.strptime(item["updated_at"], "%Y-%m-%dT%H:%M:%SZ")
content["owner"] = item["owner"]["login"]
content["name"] = item["name"]
if item["description"] is not None:
content["description"] = (
item["description"][:100] + "..."
if len(item["description"]) > 100
else item["description"]
)
else:
content["description"] = ""
content["url"] = item["html_url"]
content["star"] = item["watchers"]
content["forks"] = item["forks"]
content["updated"] = updated_at_object.strftime("%Y-%m-%d")
contents.append(content)
s.close()
yield self.create_text_message(
self.session.model.summary.invoke(
text=json.dumps(contents, ensure_ascii=False),
instruction="Summarize the text",
)
)
else:
yield self.create_text_message(f"No items related to {query} were found.")
else:
yield self.create_text_message(response.json().get("message"))
except Exception as e:
yield self.create_text_message(f"GitHub API Key and Api Version is invalid. {e}")
@@ -0,0 +1,54 @@
description:
human:
en_US: Search the GitHub repository to retrieve the open source projects you need
pt_BR: Pesquise o repositório do GitHub para recuperar os projetos de código aberto
necessários.
zh_Hans: 搜索GitHub仓库,检索你需要的开源项目。
llm: A tool when you wants to search for popular warehouses or open source projects
for any keyword. format query condition like "keywords+language:js", language
can be other dev languages.
extra:
python:
source: tools/github_repositories.py
identity:
author: CharlieWei
icon: icon.svg
label:
en_US: Search Repositories
pt_BR: Pesquisar Repositórios
zh_Hans: 仓库搜索
name: github_repositories
parameters:
- form: llm
human_description:
en_US: You want to find the project development language, keywords, For example.
Find 10 Python developed PDF document parsing projects.
pt_BR: Você deseja encontrar a linguagem de desenvolvimento do projeto, palavras-chave,
Por exemplo. Encontre 10 projetos de análise de documentos PDF desenvolvidos
em Python.
zh_Hans: 你想要找的项目开发语言、关键字,如:找10个Python开发的PDF文档解析项目。
label:
en_US: query
pt_BR: consulta
zh_Hans: 关键字
llm_description: The query of you want to search, format query condition like "keywords+language:js",
language can be other dev languages.
name: query
required: true
type: string
- default: 5
form: llm
human_description:
en_US: Number of records returned by sorting based on stars. 5 is returned by
default.
pt_BR: Número de registros retornados por classificação com base em estrelas.
5 é retornado por padrão.
zh_Hans: 基于stars排序返回的记录数, 默认返回5条。
label:
en_US: Top N
pt_BR: Topo N
zh_Hans: Top N
llm_description: Extract the first N records from the returned result.
name: top_n
required: true
type: number
@@ -0,0 +1,66 @@
import base64
from collections.abc import Generator
from typing import Any
import requests
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from dify_plugin.errors.model import InvokeError
class GithubRepositoryReadmeTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage, None, None]:
"""
invoke tools
"""
owner = tool_parameters.get("owner", "")
repo = tool_parameters.get("repo", "")
ref = tool_parameters.get("ref", "")
dir_path = tool_parameters.get("dir", "")
if not owner:
yield self.create_text_message("Please input owner")
if not repo:
yield self.create_text_message("Please input repo")
if "access_tokens" not in self.runtime.credentials or not self.runtime.credentials.get("access_tokens"):
yield self.create_text_message("GitHub API Access Tokens is required.")
if "api_version" not in self.runtime.credentials or not self.runtime.credentials.get("api_version"):
api_version = "2022-11-28"
else:
api_version = self.runtime.credentials.get("api_version")
try:
headers = {
"Content-Type": "application/vnd.github+json",
"Authorization": f"Bearer {self.runtime.credentials.get('access_tokens')}",
"X-GitHub-Api-Version": api_version,
}
s = requests.session()
api_domain = "https://api.github.com"
url = f"{api_domain}/repos/{owner}/{repo}/readme"
if dir_path:
url = f"{url}/{dir_path}"
if ref:
url = f"{url}?ref={ref}"
response = s.request(
method="GET",
headers=headers,
url=url,
)
response_data = response.json()
if response.status_code == 200:
if response_data.get("encoding") != "base64":
raise InvokeError(
f"Can not get base64 encoded readme, response encoding is {response_data.get('encoding')}"
)
content = response_data.get("content")
if not content:
raise InvokeError("README content is empty")
decoded_bytes = base64.b64decode(content)
decoded_str = decoded_bytes.decode("utf-8")
yield self.create_text_message(decoded_str)
else:
raise InvokeError(f"Request failed: {response.status_code} {response_data.get('message')}")
except InvokeError as e:
raise e
except Exception as e:
raise InvokeError(f"Request failed: {e}") from e
@@ -0,0 +1,70 @@
description:
human:
en_US: Gets the README from a repository directory.
pt_BR: Obter o README de um diretório do repositório.
zh_Hans: 从存储库某个目录中获取README
llm: A tool when you wants to gets the README content from a repository directory.
extra:
python:
source: tools/github_repository_readme.py
identity:
author: itning
icon: icon.svg
label:
en_US: Get README content
pt_BR: Obter conteúdo do README
zh_Hans: 获取README内容
name: github_repository_readme
parameters:
- form: llm
human_description:
en_US: The account owner of the repository. The name is not case sensitive.
pt_BR: Proprietário da conta do repositório. O nome não diferencia maiúsculas de minúsculas.
zh_Hans: 仓库的账户所有者。名称不区分大小写。
label:
en_US: owner
pt_BR: owner
zh_Hans: owner
llm_description: The account owner of the repository. The name is not case sensitive.
name: owner
required: true
type: string
- form: llm
human_description:
en_US: The name of the repository without the .git extension. The name is not case sensitive.
pt_BR: O nome do repositório sem a extensão .git. O nome não diferencia maiúsculas de minúsculas.
zh_Hans: 仓库名称不带.git扩展名。名称不区分大小写。
label:
en_US: repo
pt_BR: repo
zh_Hans: repo
llm_description: The name of the repository without the .git extension. The name is not case sensitive.
name: repo
required: true
type: string
- form: llm
human_description:
en_US: "The name of the commit/branch/tag. Default: the repositorys default branch."
pt_BR: "O nome do commit/branch/tag. Valor padrão: a branch padrão do repositório."
zh_Hans: commit/branch/tag的名称。默认值:仓库的默认分支。
label:
en_US: ref
pt_BR: ref
zh_Hans: ref
llm_description: "The name of the commit/branch/tag. Default: the repositorys default branch."
name: ref
required: false
type: string
- form: llm
human_description:
en_US: The alternate path to look for a README file
pt_BR: O caminho alternativo para encontrar o arquivo README
zh_Hans: 查找README文件的备用路径。
label:
en_US: dir
pt_BR: dir
zh_Hans: dir
llm_description: The alternate path to look for a README file
name: dir
required: false
type: string