import json import uuid from core.plugin.impl.base import BasePluginClient from extensions.ext_redis import redis_client class OAuthProxyService(BasePluginClient): # Default max age for proxy context parameter in seconds __MAX_AGE__ = 5 * 60 # 5 minutes __KEY_PREFIX__ = "oauth_proxy_context:" @staticmethod def create_proxy_context(user_id: str, tenant_id: str, plugin_id: str, provider: str): """ Create a proxy context for an OAuth 2.0 authorization request. This parameter is a crucial security measure to prevent Cross-Site Request Forgery (CSRF) attacks. It works by generating a unique nonce and storing it in a distributed cache (Redis) along with the user's session context. The returned nonce should be included as the 'proxy_context' parameter in the authorization URL. Upon callback, the `use_proxy_context` method is used to verify the state, ensuring the request's integrity and authenticity, and mitigating replay attacks. """ context_id = str(uuid.uuid4()) data = { "user_id": user_id, "plugin_id": plugin_id, "tenant_id": tenant_id, "provider": provider, } redis_client.setex( f"{OAuthProxyService.__KEY_PREFIX__}{context_id}", OAuthProxyService.__MAX_AGE__, json.dumps(data), ) return context_id @staticmethod def use_proxy_context(context_id: str): """ Validate the proxy context parameter. This checks if the context_id is valid and not expired. """ if not context_id: raise ValueError("context_id is required") # get data from redis data = redis_client.getdel(f"{OAuthProxyService.__KEY_PREFIX__}{context_id}") if not data: raise ValueError("context_id is invalid") return json.loads(data)