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.entities.oauth import ToolOAuthCredentials from dify_plugin.errors.tool import ToolProviderCredentialValidationError, ToolProviderOAuthError 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 ) -> ToolOAuthCredentials: """ Exchange code for access_token. """ code = request.args.get("code") if not code: raise ToolProviderOAuthError("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_tokens = response_json.get("access_token") if not access_tokens: raise ToolProviderOAuthError(f"Error in GitHub OAuth: {response_json}") return ToolOAuthCredentials(credentials={"access_tokens": access_tokens}, expires_at=-1) def _oauth_refresh_credentials( self, redirect_uri: str, system_credentials: Mapping[str, Any], credentials: Mapping[str, Any] ) -> ToolOAuthCredentials: """ Refresh the credentials """ # TODO: Implement the refresh credentials logic return ToolOAuthCredentials(credentials=credentials, expires_at=-1) 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