main.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import base64
  4. import functools
  5. import json
  6. import logging
  7. import os
  8. import werkzeug.urls
  9. import werkzeug.utils
  10. from werkzeug.exceptions import BadRequest
  11. from odoo import api, http, SUPERUSER_ID, _
  12. from odoo.exceptions import AccessDenied
  13. from odoo.http import request, Response
  14. from odoo import registry as registry_get
  15. from odoo.tools.misc import clean_context
  16. from odoo.addons.auth_signup.controllers.main import AuthSignupHome as Home
  17. from odoo.addons.web.controllers.utils import ensure_db, _get_login_redirect_url
  18. _logger = logging.getLogger(__name__)
  19. #----------------------------------------------------------
  20. # helpers
  21. #----------------------------------------------------------
  22. def fragment_to_query_string(func):
  23. @functools.wraps(func)
  24. def wrapper(self, *a, **kw):
  25. kw.pop('debug', False)
  26. if not kw:
  27. return Response("""<html><head><script>
  28. var l = window.location;
  29. var q = l.hash.substring(1);
  30. var r = l.pathname + l.search;
  31. if(q.length !== 0) {
  32. var s = l.search ? (l.search === '?' ? '' : '&') : '?';
  33. r = l.pathname + l.search + s + q;
  34. }
  35. if (r == l.pathname) {
  36. r = '/';
  37. }
  38. window.location = r;
  39. </script></head><body></body></html>""")
  40. return func(self, *a, **kw)
  41. return wrapper
  42. #----------------------------------------------------------
  43. # Controller
  44. #----------------------------------------------------------
  45. class OAuthLogin(Home):
  46. def list_providers(self):
  47. try:
  48. providers = request.env['auth.oauth.provider'].sudo().search_read([('enabled', '=', True)])
  49. except Exception:
  50. providers = []
  51. for provider in providers:
  52. return_url = request.httprequest.url_root + 'auth_oauth/signin'
  53. state = self.get_state(provider)
  54. params = dict(
  55. response_type='token',
  56. client_id=provider['client_id'],
  57. redirect_uri=return_url,
  58. scope=provider['scope'],
  59. state=json.dumps(state),
  60. # nonce=base64.urlsafe_b64encode(os.urandom(16)),
  61. )
  62. provider['auth_link'] = "%s?%s" % (provider['auth_endpoint'], werkzeug.urls.url_encode(params))
  63. return providers
  64. def get_state(self, provider):
  65. redirect = request.params.get('redirect') or 'web'
  66. if not redirect.startswith(('//', 'http://', 'https://')):
  67. redirect = '%s%s' % (request.httprequest.url_root, redirect[1:] if redirect[0] == '/' else redirect)
  68. state = dict(
  69. d=request.session.db,
  70. p=provider['id'],
  71. r=werkzeug.urls.url_quote_plus(redirect),
  72. )
  73. token = request.params.get('token')
  74. if token:
  75. state['t'] = token
  76. return state
  77. @http.route()
  78. def web_login(self, *args, **kw):
  79. ensure_db()
  80. if request.httprequest.method == 'GET' and request.session.uid and request.params.get('redirect'):
  81. # Redirect if already logged in and redirect param is present
  82. return request.redirect(request.params.get('redirect'))
  83. providers = self.list_providers()
  84. response = super(OAuthLogin, self).web_login(*args, **kw)
  85. if response.is_qweb:
  86. error = request.params.get('oauth_error')
  87. if error == '1':
  88. error = _("Sign up is not allowed on this database.")
  89. elif error == '2':
  90. error = _("Access Denied")
  91. elif error == '3':
  92. error = _("You do not have access to this database or your invitation has expired. Please ask for an invitation and be sure to follow the link in your invitation email.")
  93. else:
  94. error = None
  95. response.qcontext['providers'] = providers
  96. if error:
  97. response.qcontext['error'] = error
  98. return response
  99. def get_auth_signup_qcontext(self):
  100. result = super(OAuthLogin, self).get_auth_signup_qcontext()
  101. result["providers"] = self.list_providers()
  102. return result
  103. class OAuthController(http.Controller):
  104. @http.route('/auth_oauth/signin', type='http', auth='none')
  105. @fragment_to_query_string
  106. def signin(self, **kw):
  107. state = json.loads(kw['state'])
  108. # make sure request.session.db and state['d'] are the same,
  109. # update the session and retry the request otherwise
  110. dbname = state['d']
  111. if not http.db_filter([dbname]):
  112. return BadRequest()
  113. ensure_db(db=dbname)
  114. provider = state['p']
  115. request.update_context(**clean_context(state.get('c', {})))
  116. try:
  117. # auth_oauth may create a new user, the commit makes it
  118. # visible to authenticate()'s own transaction below
  119. _, login, key = request.env['res.users'].with_user(SUPERUSER_ID).auth_oauth(provider, kw)
  120. request.env.cr.commit()
  121. action = state.get('a')
  122. menu = state.get('m')
  123. redirect = werkzeug.urls.url_unquote_plus(state['r']) if state.get('r') else False
  124. url = '/web'
  125. if redirect:
  126. url = redirect
  127. elif action:
  128. url = '/web#action=%s' % action
  129. elif menu:
  130. url = '/web#menu_id=%s' % menu
  131. pre_uid = request.session.authenticate(dbname, login, key)
  132. resp = request.redirect(_get_login_redirect_url(pre_uid, url), 303)
  133. resp.autocorrect_location_header = False
  134. # Since /web is hardcoded, verify user has right to land on it
  135. if werkzeug.urls.url_parse(resp.location).path == '/web' and not request.env.user._is_internal():
  136. resp.location = '/'
  137. return resp
  138. except AttributeError: # TODO juc master: useless since ensure_db()
  139. # auth_signup is not installed
  140. _logger.error("auth_signup not installed on database %s: oauth sign up cancelled.", dbname)
  141. url = "/web/login?oauth_error=1"
  142. except AccessDenied:
  143. # oauth credentials not valid, user could be on a temporary session
  144. _logger.info('OAuth2: access denied, redirect to main page in case a valid session exists, without setting cookies')
  145. url = "/web/login?oauth_error=3"
  146. except Exception:
  147. # signup error
  148. _logger.exception("Exception during request handling")
  149. url = "/web/login?oauth_error=2"
  150. redirect = request.redirect(url, 303)
  151. redirect.autocorrect_location_header = False
  152. return redirect
  153. @http.route('/auth_oauth/oea', type='http', auth='none')
  154. def oea(self, **kw):
  155. """login user via Odoo Account provider"""
  156. dbname = kw.pop('db', None)
  157. if not dbname:
  158. dbname = request.db
  159. if not dbname:
  160. raise BadRequest()
  161. if not http.db_filter([dbname]):
  162. raise BadRequest()
  163. registry = registry_get(dbname)
  164. with registry.cursor() as cr:
  165. try:
  166. env = api.Environment(cr, SUPERUSER_ID, {})
  167. provider = env.ref('auth_oauth.provider_openerp')
  168. except ValueError:
  169. redirect = request.redirect(f'/web?db={dbname}', 303)
  170. redirect.autocorrect_location_header = False
  171. return redirect
  172. assert provider._name == 'auth.oauth.provider'
  173. state = {
  174. 'd': dbname,
  175. 'p': provider.id,
  176. 'c': {'no_user_creation': True},
  177. }
  178. kw['state'] = json.dumps(state)
  179. return self.signin(**kw)