You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
10 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 SYLEAM
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. import base64
  5. import logging
  6. from datetime import datetime, timedelta
  7. from openerp import http
  8. from openerp import fields
  9. _logger = logging.getLogger(__name__)
  10. try:
  11. from oauthlib.oauth2 import RequestValidator
  12. except ImportError:
  13. _logger.debug('Cannot `import oauthlib`.')
  14. class OdooValidator(RequestValidator):
  15. """ OAuth2 validator to be used in Odoo
  16. This is an implementation of oauthlib's RequestValidator interface
  17. https://github.com/idan/oauthlib/oauthlib/oauth2/rfc6749/request_validator.py
  18. """
  19. def _load_client(self, request, client_id=None):
  20. """ Returns a client instance for the request """
  21. client = request.client
  22. if not client:
  23. request.client = http.request.env['oauth.provider.client'].search([
  24. ('identifier', '=', client_id or request.client_id),
  25. ])
  26. request.odoo_user = http.request.env.user
  27. request.client.client_id = request.client.identifier
  28. def _extract_auth(self, request):
  29. """ Extract auth string from request headers """
  30. auth = request.headers.get('Authorization', ' ')
  31. auth_type, auth_string = auth.split(' ', 1)
  32. if auth_type != 'Basic':
  33. return ''
  34. return auth_string
  35. def authenticate_client(self, request, *args, **kwargs):
  36. """ Authenticate the client """
  37. auth_string = self._extract_auth(request)
  38. auth_string_decoded = base64.b64decode(auth_string)
  39. # If we don't have a proper auth string, get values in the request body
  40. if ':' not in auth_string_decoded:
  41. client_id = request.client_id
  42. client_secret = request.client_secret
  43. else:
  44. client_id, client_secret = auth_string_decoded.split(':', 1)
  45. self._load_client(request)
  46. return (request.client.identifier == client_id) and \
  47. (request.client.secret or '') == (client_secret or '')
  48. def authenticate_client_id(self, client_id, request, *args, **kwargs):
  49. """ Ensure client_id belong to a non-confidential client """
  50. self._load_client(request, client_id=client_id)
  51. return bool(request.client) and not request.client.secret
  52. def client_authentication_required(self, request, *args, **kwargs):
  53. """ Determine if the client authentication is required for the request
  54. """
  55. # If an auth string was specified, unconditionnally authenticate
  56. if self._extract_auth(request):
  57. return True
  58. self._load_client(request)
  59. return request.client.grant_type in (
  60. 'password',
  61. 'authorization_code',
  62. 'refresh_token',
  63. ) or request.client_secret or \
  64. not request.odoo_user.active
  65. def confirm_redirect_uri(
  66. self, client_id, code, redirect_uri, client, *args, **kwargs):
  67. """ Ensure that the authorization process' redirect URI
  68. The authorization process corresponding to the code must begin by using
  69. this redirect_uri
  70. """
  71. code = http.request.env['oauth.provider.authorization.code'].search([
  72. ('client_id.identifier', '=', client_id),
  73. ('code', '=', code),
  74. ])
  75. return redirect_uri == code.redirect_uri_id.name
  76. def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
  77. """ Returns the default redirect URI for the client """
  78. client = http.request.env['oauth.provider.client'].search([
  79. ('identifier', '=', client_id),
  80. ])
  81. return client.redirect_uri_ids and client.redirect_uri_ids[0].name \
  82. or ''
  83. def get_default_scopes(self, client_id, request, *args, **kwargs):
  84. """ Returns a list of default scoprs for the client """
  85. client = http.request.env['oauth.provider.client'].search([
  86. ('identifier', '=', client_id),
  87. ])
  88. return ' '.join(client.scope_ids.mapped('code'))
  89. def get_original_scopes(self, refresh_token, request, *args, **kwargs):
  90. """ Returns the list of scopes associated to the refresh token """
  91. token = http.request.env['oauth.provider.token'].search([
  92. ('client_id', '=', request.client.id),
  93. ('refresh_token', '=', refresh_token),
  94. ])
  95. return token.scope_ids.mapped('code')
  96. def invalidate_authorization_code(
  97. self, client_id, code, request, *args, **kwargs):
  98. """ Invalidates an authorization code """
  99. code = http.request.env['oauth.provider.authorization.code'].search([
  100. ('client_id.identifier', '=', client_id),
  101. ('code', '=', code),
  102. ])
  103. code.sudo().write({'active': False})
  104. def is_within_original_scope(
  105. self, request_scopes, refresh_token, request, *args, **kwargs):
  106. """ Check if the requested scopes are within a scope of the token """
  107. token = http.request.env['oauth.provider.token'].search([
  108. ('client_id', '=', request.client.id),
  109. ('refresh_token', '=', refresh_token),
  110. ])
  111. return set(request_scopes).issubset(
  112. set(token.scope_ids.mapped('code')))
  113. def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
  114. """ Revoke an access of refresh token """
  115. db_token = http.request.env['oauth.provider.token'].search([
  116. ('token', '=', token),
  117. ])
  118. # If we revoke a full token, simply unlink it
  119. if db_token:
  120. db_token.sudo().unlink()
  121. # If we revoke a refresh token, empty it in the corresponding token
  122. else:
  123. db_token = http.request.env['oauth.provider.token'].search([
  124. ('refresh_token', '=', token),
  125. ])
  126. db_token.sudo().refresh_token = False
  127. def rotate_refresh_token(self, request):
  128. """ Determine if the refresh token has to be renewed
  129. Called after refreshing an access token
  130. Always refresh the token by default, but child classes could override
  131. this method to change this behaviour.
  132. """
  133. return True
  134. def save_authorization_code(
  135. self, client_id, code, request, *args, **kwargs):
  136. """ Store the authorization code into the database """
  137. redirect_uri = http.request.env['oauth.provider.redirect.uri'].search([
  138. ('name', '=', request.redirect_uri),
  139. ])
  140. http.request.env['oauth.provider.authorization.code'].sudo().create({
  141. 'code': code['code'],
  142. 'client_id': request.client.id,
  143. 'user_id': request.odoo_user.id,
  144. 'redirect_uri_id': redirect_uri.id,
  145. 'scope_ids': [(6, 0, request.client.scope_ids.filtered(
  146. lambda record: record.code in request.scopes).ids)],
  147. })
  148. def save_bearer_token(self, token, request, *args, **kwargs):
  149. """ Store the bearer token into the database """
  150. scopes = token.get('scope', '').split()
  151. http.request.env['oauth.provider.token'].sudo().create({
  152. 'token': token['access_token'],
  153. 'token_type': token['token_type'],
  154. 'refresh_token': token.get('refresh_token'),
  155. 'client_id': request.client.id,
  156. 'user_id': token.get('odoo_user_id', request.odoo_user.id),
  157. 'scope_ids': [(6, 0, request.client.scope_ids.filtered(
  158. lambda record: record.code in scopes).ids)],
  159. 'expires_at': fields.Datetime.to_string(
  160. datetime.now() + timedelta(seconds=token['expires_in'])),
  161. })
  162. return request.client.redirect_uri_ids[0].name
  163. def validate_bearer_token(self, token, scopes, request):
  164. """ Ensure the supplied bearer token is valid, and allowed for the scopes
  165. """
  166. token = http.request.env['oauth.provider.token'].search([
  167. ('token', '=', token),
  168. ])
  169. if scopes is None:
  170. scopes = ''
  171. return set(scopes.split()).issubset(
  172. set(token.scope_ids.mapped('code')))
  173. def validate_client_id(self, client_id, request, *args, **kwargs):
  174. """ Ensure client_id belong to a valid and active client """
  175. self._load_client(request)
  176. return bool(request.client)
  177. def validate_code(self, client_id, code, client, request, *args, **kwargs):
  178. """ Check that the code is valid, and assigned to the given client """
  179. code = http.request.env['oauth.provider.authorization.code'].search([
  180. ('client_id.identifier', '=', client_id),
  181. ('code', '=', code),
  182. ])
  183. request.odoo_user = code.user_id
  184. return bool(code)
  185. def validate_grant_type(
  186. self, client_id, grant_type, client, request, *args, **kwargs):
  187. """ Ensure the client is authorized to use the requested grant_type """
  188. return client.identifier == client_id and grant_type in (
  189. client.grant_type, 'refresh_token'
  190. )
  191. def validate_redirect_uri(
  192. self, client_id, redirect_uri, request, *args, **kwargs):
  193. """ Ensure the client is allowed to use the requested redurect_uri """
  194. return request.client.identifier == client_id and \
  195. redirect_uri in request.client.mapped('redirect_uri_ids.name')
  196. def validate_refresh_token(
  197. self, refresh_token, client, request, *args, **kwargs):
  198. """ Ensure the refresh token is valid and associated to the client """
  199. token = http.request.env['oauth.provider.token'].search([
  200. ('client_id', '=', client.id),
  201. ('refresh_token', '=', refresh_token),
  202. ])
  203. return bool(token)
  204. def validate_response_type(
  205. self, client_id, response_type, client, request, *args, **kwargs):
  206. """ Ensure the client is allowed to use the requested response_type """
  207. return request.client.identifier == client_id and \
  208. response_type == request.client.response_type
  209. def validate_scopes(
  210. self, client_id, scopes, client, request, *args, **kwargs):
  211. """ Ensure the client is allowed to access all requested scopes """
  212. return request.client.identifier == client_id and set(scopes).issubset(
  213. set(request.client.mapped('scope_ids.code')))
  214. def validate_user(
  215. self, username, password, client, request, *args, **kwargs):
  216. """ Ensure the usernamd and password are valid """
  217. uid = http.request.session.authenticate(
  218. http.request.session.db, username, password)
  219. request.odoo_user = http.request.env['res.users'].browse(uid)
  220. return bool(uid)