google_service.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from datetime import datetime
  4. import logging
  5. import json
  6. import requests
  7. from werkzeug import urls
  8. from odoo import api, fields, models, _
  9. _logger = logging.getLogger(__name__)
  10. TIMEOUT = 20
  11. GOOGLE_AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/auth'
  12. GOOGLE_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token'
  13. GOOGLE_API_BASE_URL = 'https://www.googleapis.com'
  14. def _get_client_secret(ICP_sudo, service):
  15. """ Return the client_secret for a specific service.
  16. Note: This method serves as a hook for modules that would like share their own keys.
  17. This method should never be callable from a method that return it in clear, it
  18. should only be used directly in a request.
  19. :param ICP_sudo: the model ir.config_parameters in sudo
  20. :param service: the service that we need the secret key
  21. :return: The ICP value
  22. :rtype: str
  23. """
  24. return ICP_sudo.get_param('google_%s_client_secret' % service)
  25. class GoogleService(models.AbstractModel):
  26. _name = 'google.service'
  27. _description = 'Google Service'
  28. def _get_client_id(self, service):
  29. # client id is not a secret, and can be leaked without risk. e.g. in clear in authorize uri.
  30. ICP = self.env['ir.config_parameter'].sudo()
  31. return ICP.get_param('google_%s_client_id' % service)
  32. @api.model
  33. def _get_authorize_uri(self, service, scope, redirect_uri, state=None, approval_prompt=None, access_type=None):
  34. """ This method return the url needed to allow this instance of Odoo to access to the scope
  35. of gmail specified as parameters
  36. """
  37. params = {
  38. 'response_type': 'code',
  39. 'client_id': self._get_client_id(service),
  40. 'scope': scope,
  41. 'redirect_uri': redirect_uri,
  42. }
  43. if state:
  44. params['state'] = state
  45. if approval_prompt:
  46. params['approval_prompt'] = approval_prompt
  47. if access_type:
  48. params['access_type'] = access_type
  49. encoded_params = urls.url_encode(params)
  50. return "%s?%s" % (GOOGLE_AUTH_ENDPOINT, encoded_params)
  51. @api.model
  52. def _get_google_tokens(self, authorize_code, service, redirect_uri):
  53. """ Call Google API to exchange authorization code against token, with POST request, to
  54. not be redirected.
  55. """
  56. ICP = self.env['ir.config_parameter'].sudo()
  57. headers = {"content-type": "application/x-www-form-urlencoded"}
  58. data = {
  59. 'code': authorize_code,
  60. 'client_id': self._get_client_id(service),
  61. 'client_secret': _get_client_secret(ICP, service),
  62. 'grant_type': 'authorization_code',
  63. 'redirect_uri': redirect_uri
  64. }
  65. try:
  66. dummy, response, dummy = self._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, method='POST', preuri='')
  67. return response.get('access_token'), response.get('refresh_token'), response.get('expires_in')
  68. except requests.HTTPError as e:
  69. _logger.error(e)
  70. error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired")
  71. raise self.env['res.config.settings'].get_config_warning(error_msg)
  72. @api.model
  73. def _do_request(self, uri, params=None, headers=None, method='POST', preuri="https://www.googleapis.com", timeout=TIMEOUT):
  74. """ Execute the request to Google API. Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE')
  75. :param uri : the url to contact
  76. :param params : dict or already encoded parameters for the request to make
  77. :param headers : headers of request
  78. :param method : the method to use to make the request
  79. :param preuri : pre url to prepend to param uri.
  80. """
  81. if params is None:
  82. params = {}
  83. if headers is None:
  84. headers = {}
  85. # Remove client_secret key from logs
  86. if isinstance(params, str):
  87. _log_params = json.loads(params) or {}
  88. else:
  89. _log_params = (params or {}).copy()
  90. if _log_params.get('client_secret'):
  91. _log_params['client_secret'] = _log_params['client_secret'][0:4] + 'x' * 12
  92. _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !", uri, method, headers, _log_params)
  93. ask_time = fields.Datetime.now()
  94. try:
  95. if method.upper() in ('GET', 'DELETE'):
  96. res = requests.request(method.lower(), preuri + uri, params=params, timeout=timeout)
  97. elif method.upper() in ('POST', 'PATCH', 'PUT'):
  98. res = requests.request(method.lower(), preuri + uri, data=params, headers=headers, timeout=timeout)
  99. else:
  100. raise Exception(_('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!') % (method))
  101. res.raise_for_status()
  102. status = res.status_code
  103. if int(status) == 204: # Page not found, no response
  104. response = False
  105. else:
  106. response = res.json()
  107. try:
  108. ask_time = datetime.strptime(res.headers.get('date', ''), "%a, %d %b %Y %H:%M:%S %Z")
  109. except ValueError:
  110. pass
  111. except requests.HTTPError as error:
  112. if error.response.status_code in (204, 404):
  113. status = error.response.status_code
  114. response = ""
  115. else:
  116. _logger.exception("Bad google request : %s !", error.response.content)
  117. raise error
  118. return (status, response, ask_time)