github.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import os
  2. import httpx
  3. from tenacity import retry, stop_after_attempt, wait_exponential
  4. from openhands.core.logger import openhands_logger as logger
  5. from openhands.server.sheets_client import GoogleSheetsClient
  6. GITHUB_CLIENT_ID = os.getenv('GITHUB_CLIENT_ID', '').strip()
  7. GITHUB_CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET', '').strip()
  8. class UserVerifier:
  9. def __init__(self) -> None:
  10. logger.info('Initializing UserVerifier')
  11. self.file_users: list[str] | None = None
  12. self.sheets_client: GoogleSheetsClient | None = None
  13. self.spreadsheet_id: str | None = None
  14. # Initialize from environment variables
  15. self._init_file_users()
  16. self._init_sheets_client()
  17. def _init_file_users(self) -> None:
  18. """Load users from text file if configured"""
  19. waitlist = os.getenv('GITHUB_USER_LIST_FILE')
  20. if not waitlist:
  21. logger.info('GITHUB_USER_LIST_FILE not configured')
  22. return
  23. if not os.path.exists(waitlist):
  24. logger.error(f'User list file not found: {waitlist}')
  25. raise FileNotFoundError(f'User list file not found: {waitlist}')
  26. try:
  27. with open(waitlist, 'r') as f:
  28. self.file_users = [line.strip() for line in f if line.strip()]
  29. logger.info(
  30. f'Successfully loaded {len(self.file_users)} users from {waitlist}'
  31. )
  32. except Exception as e:
  33. logger.error(f'Error reading user list file {waitlist}: {str(e)}')
  34. def _init_sheets_client(self) -> None:
  35. """Initialize Google Sheets client if configured"""
  36. sheet_id = os.getenv('GITHUB_USERS_SHEET_ID')
  37. if not sheet_id:
  38. logger.info('GITHUB_USERS_SHEET_ID not configured')
  39. return
  40. logger.info('Initializing Google Sheets integration')
  41. self.sheets_client = GoogleSheetsClient()
  42. self.spreadsheet_id = sheet_id
  43. def is_active(self) -> bool:
  44. return bool(self.file_users or (self.sheets_client and self.spreadsheet_id))
  45. def is_user_allowed(self, username: str) -> bool:
  46. """Check if user is allowed based on file and/or sheet configuration"""
  47. if not self.is_active():
  48. return True
  49. logger.info(f'Checking if GitHub user {username} is allowed')
  50. if self.file_users:
  51. if username in self.file_users:
  52. logger.info(f'User {username} found in text file allowlist')
  53. return True
  54. logger.debug(f'User {username} not found in text file allowlist')
  55. if self.sheets_client and self.spreadsheet_id:
  56. sheet_users = self.sheets_client.get_usernames(self.spreadsheet_id)
  57. if username in sheet_users:
  58. logger.info(f'User {username} found in Google Sheets allowlist')
  59. return True
  60. logger.debug(f'User {username} not found in Google Sheets allowlist')
  61. logger.info(f'User {username} not found in any allowlist')
  62. return False
  63. async def authenticate_github_user(auth_token) -> bool:
  64. user_verifier = UserVerifier()
  65. if not user_verifier.is_active():
  66. logger.info('No user verification sources configured - allowing all users')
  67. return True
  68. logger.info('Checking GitHub token')
  69. if not auth_token:
  70. logger.warning('No GitHub token provided')
  71. return False
  72. login = await get_github_user(auth_token)
  73. if not user_verifier.is_user_allowed(login):
  74. logger.warning(f'GitHub user {login} not in allow list')
  75. return False
  76. logger.info(f'GitHub user {login} authenticated')
  77. return True
  78. @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=5))
  79. async def get_github_user(token: str) -> str:
  80. """Get GitHub user info from token.
  81. Args:
  82. token: GitHub access token
  83. Returns:
  84. github handle of the user
  85. """
  86. logger.info('Fetching GitHub user info from token')
  87. headers = {
  88. 'Accept': 'application/vnd.github+json',
  89. 'Authorization': f'Bearer {token}',
  90. }
  91. async with httpx.AsyncClient(
  92. timeout=httpx.Timeout(connect=5.0, read=5.0, write=5.0, pool=5.0)
  93. ) as client:
  94. try:
  95. response = await client.get('https://api.github.com/user', headers=headers)
  96. except httpx.RequestError as e:
  97. logger.error(f'Error making request to GitHub API: {str(e)}')
  98. logger.error(e)
  99. raise
  100. logger.info('Received response from GitHub API')
  101. logger.debug(f'Response status code: {response.status_code}')
  102. response.raise_for_status()
  103. user_data = response.json()
  104. login = user_data.get('login')
  105. logger.info(f'Successfully retrieved GitHub user: {login}')
  106. return login