github_utils.py 4.4 KB

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