res_config_settings.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import requests
  4. import json
  5. from odoo import fields, models, _
  6. from odoo.exceptions import UserError
  7. class ResConfigSettings(models.TransientModel):
  8. _inherit = 'res.config.settings'
  9. # Google API Configuration
  10. google_api_enabled = fields.Boolean(
  11. string='Google API Integration',
  12. config_parameter='google_api.enabled',
  13. help='Enable integration with Google Drive and Google Calendar'
  14. )
  15. # Google API Credentials (shared for all services)
  16. google_api_client_id = fields.Char(
  17. string='Google API Client ID',
  18. config_parameter='google_api.client_id',
  19. help='Client ID for Google APIs (Drive, Calendar, etc.)'
  20. )
  21. google_api_client_secret = fields.Char(
  22. string='Google API Client Secret',
  23. config_parameter='google_api.client_secret',
  24. help='Client Secret for Google APIs (Drive, Calendar, etc.)'
  25. )
  26. def action_test_google_api_connection(self):
  27. """Test Google API connection by checking if credentials are valid"""
  28. self.ensure_one()
  29. if not self.google_api_enabled:
  30. raise UserError(_('Google API Integration is not enabled'))
  31. if not self.google_api_client_id or not self.google_api_client_secret:
  32. raise UserError(_('Please provide both Client ID and Client Secret'))
  33. try:
  34. # Test 1: Verify credentials format
  35. if len(self.google_api_client_id) < 10:
  36. raise UserError(_('Client ID appears to be invalid (too short)'))
  37. if len(self.google_api_client_secret) < 10:
  38. raise UserError(_('Client Secret appears to be invalid (too short)'))
  39. # Test 2: Try to reach Google APIs (basic connectivity test)
  40. google_api_url = "https://www.googleapis.com"
  41. response = requests.get(google_api_url, timeout=10)
  42. if response.status_code not in [200, 404]: # 404 is expected for root URL
  43. raise UserError(_('Cannot reach Google APIs. Please check your internet connection.'))
  44. # Test 3: Check if Drive API is available
  45. drive_api_url = "https://www.googleapis.com/drive/v3/about"
  46. drive_response = requests.get(drive_api_url, timeout=10)
  47. # Drive API requires authentication, so 401 is expected
  48. if drive_response.status_code not in [401, 403]:
  49. raise UserError(_('Google Drive API is not accessible'))
  50. # Test 4: Check if Calendar API is available
  51. calendar_api_url = "https://www.googleapis.com/calendar/v3/users/me/calendarList"
  52. calendar_response = requests.get(calendar_api_url, timeout=10)
  53. # Calendar API requires authentication, so 401 is expected
  54. if calendar_response.status_code not in [401, 403]:
  55. raise UserError(_('Google Calendar API is not accessible'))
  56. # Test 5: Validate OAuth2 endpoints
  57. oauth_auth_url = "https://accounts.google.com/o/oauth2/auth"
  58. oauth_response = requests.get(oauth_auth_url, timeout=10)
  59. if oauth_response.status_code not in [200, 405]: # 405 Method Not Allowed is expected for GET
  60. raise UserError(_('Google OAuth2 endpoints are not accessible'))
  61. # All tests passed
  62. return {
  63. 'type': 'ir.actions.client',
  64. 'tag': 'display_notification',
  65. 'params': {
  66. 'title': _('Success'),
  67. 'message': _('Google API connection test successful! All APIs are accessible.'),
  68. 'type': 'success',
  69. 'sticky': False,
  70. }
  71. }
  72. except requests.exceptions.Timeout:
  73. raise UserError(_('Connection timeout. Please check your internet connection.'))
  74. except requests.exceptions.ConnectionError:
  75. raise UserError(_('Connection error. Please check your internet connection.'))
  76. except Exception as e:
  77. raise UserError(_('Connection test failed: %s') % str(e))
  78. def action_validate_credentials(self):
  79. """Validate that the credentials are properly formatted"""
  80. self.ensure_one()
  81. if not self.google_api_client_id:
  82. raise UserError(_('Client ID is required'))
  83. if not self.google_api_client_secret:
  84. raise UserError(_('Client Secret is required'))
  85. # Basic format validation
  86. if not self.google_api_client_id.endswith('.apps.googleusercontent.com'):
  87. raise UserError(_('Client ID should end with .apps.googleusercontent.com'))
  88. if len(self.google_api_client_secret) < 20:
  89. raise UserError(_('Client Secret appears to be too short'))
  90. return {
  91. 'type': 'ir.actions.client',
  92. 'tag': 'display_notification',
  93. 'params': {
  94. 'title': _('Success'),
  95. 'message': _('Credentials format is valid!'),
  96. 'type': 'success',
  97. 'sticky': False,
  98. }
  99. }
  100. def action_connect_google_account(self):
  101. """Connect Google account using manual OAuth flow"""
  102. self.ensure_one()
  103. if not self.google_api_client_id or not self.google_api_client_secret:
  104. raise UserError(_('Please configure Google API credentials first'))
  105. # Get base URL
  106. base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
  107. # Create a custom redirect URI for our manual flow
  108. redirect_uri = f"{base_url}/web/google_oauth_callback"
  109. # Build OAuth authorization URL
  110. auth_url = "https://accounts.google.com/o/oauth2/auth"
  111. params = {
  112. 'client_id': self.google_api_client_id,
  113. 'redirect_uri': redirect_uri,
  114. 'scope': 'https://www.googleapis.com/auth/drive',
  115. 'response_type': 'code',
  116. 'access_type': 'offline',
  117. 'prompt': 'consent',
  118. 'state': 'google_drive_integration'
  119. }
  120. # Build URL with parameters
  121. param_string = '&'.join([f"{k}={v}" for k, v in params.items()])
  122. oauth_url = f"{auth_url}?{param_string}"
  123. # Store the redirect URI for later use
  124. self.env['ir.config_parameter'].sudo().set_param('google_api.manual_redirect_uri', redirect_uri)
  125. return {
  126. 'type': 'ir.actions.act_url',
  127. 'url': oauth_url,
  128. 'target': 'new',
  129. }
  130. def action_show_oauth_redirect_uris(self):
  131. """Show the redirect URIs that need to be configured in Google Cloud Console"""
  132. self.ensure_one()
  133. base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
  134. redirect_uri = f"{base_url}/web/google_oauth_callback"
  135. return {
  136. 'type': 'ir.actions.client',
  137. 'tag': 'display_notification',
  138. 'params': {
  139. 'title': _('OAuth Redirect URI'),
  140. 'message': _('Configure this redirect URI in Google Cloud Console:\n\n%s\n\nCopy this URL and add it to your OAuth 2.0 client configuration.') % redirect_uri,
  141. 'type': 'info',
  142. 'sticky': True,
  143. }
  144. }
  145. def action_test_google_oauth_connection(self):
  146. """Test OAuth connection using manual OAuth flow"""
  147. self.ensure_one()
  148. # Check for manual OAuth token
  149. access_token = self.env['ir.config_parameter'].sudo().get_param('google_api.access_token')
  150. if not access_token:
  151. return {
  152. 'type': 'ir.actions.client',
  153. 'tag': 'display_notification',
  154. 'params': {
  155. 'title': _('Info'),
  156. 'message': _('No OAuth token found. Please use "Connect Google Account" to connect your Google account.'),
  157. 'type': 'info',
  158. 'sticky': False,
  159. }
  160. }
  161. try:
  162. # Test Google Drive API access
  163. headers = {
  164. 'Authorization': f'Bearer {access_token}',
  165. }
  166. # Try to get user info (this will test the token)
  167. response = requests.get(
  168. 'https://www.googleapis.com/drive/v3/about?fields=user',
  169. headers=headers,
  170. timeout=10
  171. )
  172. if response.status_code == 200:
  173. user_info = response.json()
  174. user_email = user_info.get('user', {}).get('emailAddress', 'Unknown')
  175. return {
  176. 'type': 'ir.actions.client',
  177. 'tag': 'display_notification',
  178. 'params': {
  179. 'title': _('Success'),
  180. 'message': _('OAuth connection successful! Connected as: %s') % user_email,
  181. 'type': 'success',
  182. 'sticky': False,
  183. }
  184. }
  185. elif response.status_code == 401:
  186. # Try to refresh the token
  187. if self._refresh_access_token():
  188. return {
  189. 'type': 'ir.actions.client',
  190. 'tag': 'display_notification',
  191. 'params': {
  192. 'title': _('Token Refreshed'),
  193. 'message': _('Access token was refreshed successfully. Please try the test again.'),
  194. 'type': 'success',
  195. }
  196. }
  197. else:
  198. raise UserError(_('OAuth token has expired and could not be refreshed. Please reconnect your Google account.'))
  199. else:
  200. raise UserError(_('Google Drive API test failed. Status: %s') % response.status_code)
  201. except Exception as e:
  202. raise UserError(_('OAuth connection test failed: %s') % str(e))
  203. def _refresh_access_token(self):
  204. """Refresh the access token using the refresh token"""
  205. try:
  206. refresh_token = self.env['ir.config_parameter'].sudo().get_param('google_api.refresh_token')
  207. client_id = self.env['ir.config_parameter'].sudo().get_param('google_api.client_id')
  208. client_secret = self.env['ir.config_parameter'].sudo().get_param('google_api.client_secret')
  209. if not all([refresh_token, client_id, client_secret]):
  210. return False
  211. # Exchange refresh token for new access token
  212. token_url = 'https://oauth2.googleapis.com/token'
  213. data = {
  214. 'client_id': client_id,
  215. 'client_secret': client_secret,
  216. 'refresh_token': refresh_token,
  217. 'grant_type': 'refresh_token'
  218. }
  219. response = requests.post(token_url, data=data, timeout=30)
  220. if response.status_code == 200:
  221. token_data = response.json()
  222. new_access_token = token_data.get('access_token')
  223. if new_access_token:
  224. # Store the new access token
  225. self.env['ir.config_parameter'].sudo().set_param('google_api.access_token', new_access_token)
  226. return True
  227. return False
  228. except Exception as e:
  229. _logger.error(f"Failed to refresh access token: {str(e)}")
  230. return False