[REF] google_account : code cleaning

- remove unused helpers
- add command in case of deeper refactoring one day ...
- better variable naming
- use contants for Google services urls
- add docstrings
This commit is contained in:
Jérome Maes
2016-04-27 16:35:45 +02:00
parent 3e1fb87e93
commit c5baaa3bdc
2 changed files with 96 additions and 84 deletions
+1 -1
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from werkzeug.utils import redirect
@@ -12,7 +13,6 @@ class GoogleAuth(http.Controller):
@http.route('/google_account/authentication', type='http', auth="none")
def oauth2callback(self, **kw):
""" This route/function is called by Google when user Accept/Refuse the consent of Google """
state = json.loads(kw['state'])
dbname = state.get('d')
service = state.get('s')
+95 -83
View File
@@ -1,35 +1,52 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo import api, fields, models, registry, _
from odoo.http import request
import json
import logging
import urllib2
import werkzeug.urls
import logging
from odoo import api, fields, models, registry, _
from odoo.http import request
_logger = logging.getLogger(__name__)
TIMEOUT = 20
GOOGLE_AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/auth'
GOOGLE_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token'
GOOGLE_API_BASE_URL = 'https://www.googleapis.com'
# FIXME : this needs to become an AbstractModel, to be inhereted by google_calendar_service and google_drive_service
class GoogleService(models.TransientModel):
_name = 'google.service'
@api.model
def generate_refresh_token(self, service, authorization_code):
ICPSudo = self.env['ir.config_parameter'].sudo()
client_id = ICPSudo.get_param('google_%s_client_id' % service)
client_secret = ICPSudo.get_param('google_%s_client_secret' % service)
redirect_uri = ICPSudo.get_param('google_redirect_uri')
""" Call Google API to refresh the token, with the given authorization code
:param service : the name of the google service to actualize
:param authorization_code : the code to exchange against the new refresh token
:returns the new refresh token
"""
Parameters = self.env['ir.config_parameter'].sudo()
client_id = Parameters.get_param('google_%s_client_id' % service)
client_secret = Parameters.get_param('google_%s_client_secret' % service)
redirect_uri = Parameters.get_param('google_redirect_uri')
#Get the Refresh Token From Google And store it in ir.config_parameter
# Get the Refresh Token From Google And store it in ir.config_parameter
headers = {"Content-type": "application/x-www-form-urlencoded"}
data = dict(code=authorization_code, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, grant_type="authorization_code")
data = werkzeug.url_encode(data)
data = werkzeug.url_encode({
'code': authorization_code,
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': redirect_uri,
'grant_type': "authorization_code"
})
try:
req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers)
req = urllib2.Request(GOOGLE_TOKEN_ENDPOINT, data, headers)
content = urllib2.urlopen(req, timeout=TIMEOUT).read()
except urllib2.HTTPError:
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired")
@@ -40,97 +57,103 @@ class GoogleService(models.TransientModel):
@api.model
def _get_google_token_uri(self, service, scope):
ICPSudo = self.env['ir.config_parameter'].sudo()
params = {
Parameters = self.env['ir.config_parameter'].sudo()
encoded_params = werkzeug.url_encode({
'scope': scope,
'redirect_uri': ICPSudo.get_param('google_redirect_uri'),
'client_id': ICPSudo.get_param('google_%s_client_id' % service),
'redirect_uri': Parameters.get_param('google_redirect_uri'),
'client_id': Parameters.get_param('google_%s_client_id' % service),
'response_type': 'code',
}
uri = 'https://accounts.google.com/o/oauth2/auth?%s' % werkzeug.url_encode(params)
return uri
})
return '%s?%s' % (GOOGLE_AUTH_ENDPOINT, encoded_params)
# If no scope is passed, we use service by default to get a default scope
@api.model
def _get_authorize_uri(self, from_url, service, scope=False):
""" This method return the url needed to allow this instance of Odoo to access to the scope of gmail specified as parameters """
state_obj = dict(d=self.env.cr.dbname, s=service, f=from_url)
""" This method return the url needed to allow this instance of Odoo to access to the scope
of gmail specified as parameters
"""
state = {
'd': self.env.cr.dbname,
's': service,
'f': from_url
}
base_url = self.get_base_url()
client_id = self.get_client_id(service)
Parameters = self.env['ir.config_parameter']
base_url = Parameters.get_param('web.base.url', default='http://www.odoo.com?NoBaseUrl')
client_id = Parameters.sudo().get_param('google_%s_client_id' % (service,), default=False)
params = {
encoded_params = werkzeug.url_encode({
'response_type': 'code',
'client_id': client_id,
'state': json.dumps(state_obj),
'scope': scope or 'https://www.googleapis.com/auth/%s' % (service,),
'state': json.dumps(state),
'scope': scope or '%s/auth/%s' % (GOOGLE_API_BASE_URL, service), # If no scope is passed, we use service by default to get a default scope
'redirect_uri': base_url + '/google_account/authentication',
'approval_prompt': 'force',
'access_type': 'offline'
}
uri = self.get_uri_oauth(a='auth') + "?%s" % werkzeug.url_encode(params)
return uri
})
return "%s?%s" % (GOOGLE_AUTH_ENDPOINT, encoded_params)
@api.model
def _get_google_token_json(self, authorize_code, service):
res = False
base_url = self.get_base_url()
client_id = self.get_client_id(service)
client_secret = self.get_client_secret(service)
""" Call Google API to exchange authorization code against token, with POST request, to
not be redirected.
"""
Parameters = self.env['ir.config_parameter']
base_url = Parameters.get_param('web.base.url', default='http://www.odoo.com?NoBaseUrl')
client_id = Parameters.sudo().get_param('google_%s_client_id' % (service,), default=False)
client_secret = Parameters.sudo().get_param('google_%s_client_secret' % (service,), default=False)
headers = {"content-type": "application/x-www-form-urlencoded"}
params = {
data = werkzeug.url_encode({
'code': authorize_code,
'client_id': client_id,
'client_secret': client_secret,
'grant_type': 'authorization_code',
'redirect_uri': base_url + '/google_account/authentication'
}
})
try:
uri = self.get_uri_oauth(a='token')
data = werkzeug.url_encode(params)
st, res, ask_time = self._do_request(uri, params=data, headers=headers, type='POST', preuri='')
dummy, response, dummy = self._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, type='POST', preuri='')
return response
except urllib2.HTTPError:
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid")
raise self.env['res.config.settings'].get_config_warning(error_msg)
return res
# FIXME : this method update a field defined in google_calendar module. Since it is used only in that module, maybe it should be moved.
@api.model
def _refresh_google_token_json(self, refresh_token, service): # exchange_AUTHORIZATION vs Token (service = calendar)
res = False
client_id = self.get_client_id(service)
client_secret = self.get_client_secret(service)
Parameters = self.env['ir.config_parameter'].sudo()
client_id = Parameters.get_param('google_%s_client_id' % (service,), default=False)
client_secret = Parameters.get_param('google_%s_client_secret' % (service,), default=False)
params = {
headers = {"content-type": "application/x-www-form-urlencoded"}
data = werkzeug.url_encode({
'refresh_token': refresh_token,
'client_id': client_id,
'client_secret': client_secret,
'grant_type': 'refresh_token',
}
headers = {"content-type": "application/x-www-form-urlencoded"}
})
try:
uri = self.get_uri_oauth(a='token')
data = werkzeug.url_encode(params)
st, res, ask_time = self._do_request(uri, params=data, headers=headers, type='POST', preuri='')
except urllib2.HTTPError, e:
if e.code == 400: # invalid grant
dummy, response, dummy = self._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, type='POST', preuri='')
return response
except urllib2.HTTPError, error:
if error.code == 400: # invalid grant
with registry(request.session.db).cursor() as cur:
self.env(cur)['res.users'].browse(self.env.uid).write({'google_%s_rtoken' % service: False})
error_key = json.loads(e.read()).get("error", "nc")
_logger.exception("Bad google request : %s !" % error_key)
error_key = json.loads(error.read()).get("error", "nc")
_logger.exception("Bad google request : %s !", error_key)
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]") % error_key
raise self.env['res.config.settings'].get_config_warning(error_msg)
return res
# TODO JEM : remove preuri param, and rename type into method
@api.model
def _do_request(self, uri, params={}, headers={}, type='POST', preuri="https://www.googleapis.com"):
""" Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE') """
""" Execute the request to Google API. Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE')
:param uri : the url to contact
:param params : dict or already encoded parameters for the request to make
:param headers : headers of request
:param type : the method to use to make the request
:param preuri : pre url to prepend to param uri.
"""
_logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri, type, headers, werkzeug.url_encode(params) if type == 'GET' else params))
status = 418
@@ -143,45 +166,34 @@ class GoogleService(models.TransientModel):
elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT':
req = urllib2.Request(preuri + uri, params, headers)
else:
raise ('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!' % (type))
raise Exception(_('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!') % (type))
req.get_method = lambda: type.upper()
request = urllib2.urlopen(req, timeout=TIMEOUT)
status = request.getcode()
resp = urllib2.urlopen(req, timeout=TIMEOUT)
status = resp.getcode()
if int(status) in (204, 404): # Page not found, no response
response = False
else:
content = request.read()
content = resp.read()
response = json.loads(content)
try:
ask_time = datetime.strptime(request.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z")
ask_time = datetime.strptime(resp.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z")
except:
pass
except urllib2.HTTPError, e:
if e.code in (204, 404):
status = e.code
except urllib2.HTTPError, error:
if error.code in (204, 404):
status = error.code
response = ""
else:
_logger.exception("Bad google request : %s !" % e.read())
if e.code in (400, 401, 410):
raise e
_logger.exception("Bad google request : %s !", error.read())
if error.code in (400, 401, 410):
raise error
raise self.env['res.config.settings'].get_config_warning(_("Something went wrong with your request to google"))
return (status, response, ask_time)
def get_base_url(self):
return self.env['ir.config_parameter'].get_param('web.base.url', default='http://www.odoo.com?NoBaseUrl')
# TODO : remove me, it is only used in google calendar. Make google_calendar use the constants
@api.model
def get_client_id(self, service):
return self.env['ir.config_parameter'].sudo().get_param('google_%s_client_id' % (service,), default=False)
def get_client_secret(self, service):
return self.env['ir.config_parameter'].sudo().get_param('google_%s_client_secret' % (service,), default=False)
def get_uri_oauth(self, a=''): # a = optional action
return "https://accounts.google.com/o/oauth2/%s" % (a,)
def get_uri_api(self):
return 'https://www.googleapis.com'