diff --git a/base_json_request/README.rst b/base_json_request/README.rst new file mode 100644 index 0000000..5451ed3 --- /dev/null +++ b/base_json_request/README.rst @@ -0,0 +1,50 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +================= +Base JSON Request +================= + +This module allows you to receive JSON requests in Odoo that are not +RPC. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/210/10.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Dave Lasley + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/base_json_request/__init__.py b/base_json_request/__init__.py new file mode 100644 index 0000000..c484ab2 --- /dev/null +++ b/base_json_request/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from .hooks import post_load diff --git a/base_json_request/__manifest__.py b/base_json_request/__manifest__.py new file mode 100644 index 0000000..05ee396 --- /dev/null +++ b/base_json_request/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + 'name': 'Base JSON Request', + 'summary': 'Allows you to receive JSON requests that are not RPC.', + 'version': '10.0.1.0.0', + 'category': 'Authentication', + 'website': 'https://laslabs.com/', + 'author': 'LasLabs, Odoo Community Association (OCA)', + 'license': 'LGPL-3', + 'installable': True, + 'depends': [ + 'web', + ], + 'post_load': 'post_load', +} diff --git a/base_json_request/hooks.py b/base_json_request/hooks.py new file mode 100644 index 0000000..84e827e --- /dev/null +++ b/base_json_request/hooks.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import http + +from .http import _handle_exception, __init__ + + +def post_load(): + """Monkey patch HTTP methods.""" + http.JsonRequest._handle_exception = _handle_exception + http.JsonRequest.__init__ = __init__ diff --git a/base_json_request/http.py b/base_json_request/http.py new file mode 100644 index 0000000..f5b1713 --- /dev/null +++ b/base_json_request/http.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import json + +from werkzeug.exceptions import BadRequest + +from odoo import http + + +old_handle_exception = http.JsonRequest._handle_exception +old_init = http.JsonRequest.__init__ + + +def __init__(self, *args): + try: + old_init(self, *args) + except BadRequest as e: + try: + args = self.httprequest.args + self.jsonrequest = args + self.params = json.loads(self.jsonrequest.get('params', "{}")) + self.context = self.params.pop('context', + dict(self.session.context)) + except ValueError: + raise e + + +def _handle_exception(self, exception): + """ Override the original method to handle Werkzeug exceptions. + + Args: + exception (Exception): Exception object that is being thrown. + + Returns: + BaseResponse: JSON Response. + """ + + # For some reason a try/except here still raised... + code = getattr(exception, 'code', None) + if code is None: + return old_handle_exception( + self, exception, + ) + + error = { + 'data': http.serialize_exception(exception), + 'code': code, + } + + try: + error['message'] = exception.description + except AttributeError: + try: + error['message'] = exception.message + except AttributeError: + error['message'] = 'Internal Server Error' + + return self._json_response(error=error) diff --git a/base_web_hook/README.rst b/base_web_hook/README.rst new file mode 100644 index 0000000..1f4ccda --- /dev/null +++ b/base_web_hook/README.rst @@ -0,0 +1,55 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 + +============= +Base Web Hook +============= + +This module provides an abstract core for receiving and processing web hooks. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/210/10.0 + +Known Issues +============ + +* Security is too lax; public can read too much. Maybe should also add a group for hooks.. +* Buffer length should be checked before ``httprequest.get_data`` calls + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Dave Lasley + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/base_web_hook/__init__.py b/base_web_hook/__init__.py new file mode 100644 index 0000000..489ebcc --- /dev/null +++ b/base_web_hook/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import controllers +from . import models diff --git a/base_web_hook/__manifest__.py b/base_web_hook/__manifest__.py new file mode 100644 index 0000000..4d4c411 --- /dev/null +++ b/base_web_hook/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + 'name': 'Base Web Hook', + 'summary': 'Provides an abstract system for defining and receiving web ' + 'hooks.', + 'version': '10.0.1.0.0', + 'category': 'Tools', + 'website': 'https://laslabs.com/', + 'author': 'LasLabs, Odoo Community Association (OCA)', + 'license': 'LGPL-3', + 'installable': True, + 'external_dependencies': { + 'python': ['slugify'], + }, + 'depends': [ + 'base_json_request', + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/web_hook_view.xml', + ], +} diff --git a/base_web_hook/controllers/__init__.py b/base_web_hook/controllers/__init__.py new file mode 100644 index 0000000..c02fd56 --- /dev/null +++ b/base_web_hook/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from . import main diff --git a/base_web_hook/controllers/main.py b/base_web_hook/controllers/main.py new file mode 100644 index 0000000..1eb9619 --- /dev/null +++ b/base_web_hook/controllers/main.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import json + +from odoo import http + + +class WebHookController(http.Controller): + + @http.route( + ['/base_web_hook/json/.json'], + type='json', + auth='public', + ) + def json_receive(self, *args, **kwargs): + return self._receive(*args, **kwargs) + + @http.route( + ['/base_web_hook/'], + type='http', + auth='public', + csrf=False, + ) + def http_receive(self, *args, **kwargs): + return json.dumps( + self._receive(*args, **kwargs), + ) + + @http.route( + ['/base_web_hook/authenticated/'], + type='http', + auth='user', + csrf=False, + ) + def http_receive_authenticated(self, *args, **kwargs): + return self.http_receive(*args, **kwargs) + + def _receive(self, slug, **kwargs): + hook = http.request.env['web.hook'].search_by_slug(slug) + return hook.receive( + data=kwargs, + data_string=http.request.httprequest.get_data(), + headers=http.request.httprequest.headers, + ) diff --git a/base_web_hook/models/__init__.py b/base_web_hook/models/__init__.py new file mode 100644 index 0000000..85b1ad1 --- /dev/null +++ b/base_web_hook/models/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +# Concrete models +from . import web_hook +from . import web_hook_token + +# Adapters +from . import web_hook_adapter +from . import web_hook_token_adapter + +# Token Interfaces +from . import web_hook_token_none +from . import web_hook_token_plain +from . import web_hook_token_user + +# Request Bin Hook +from . import web_hook_request_bin +from . import web_hook_request_bin_request diff --git a/base_web_hook/models/web_hook.py b/base_web_hook/models/web_hook.py new file mode 100644 index 0000000..cbec076 --- /dev/null +++ b/base_web_hook/models/web_hook.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging +import random +import string + +from werkzeug.exceptions import Unauthorized + +from odoo import api, fields, models, _ + +_logger = logging.getLogger(__name__) + +try: + from slugify import slugify +except ImportError: + _logger.info('`python-slugify` Python library not installed.') + + +class WebHook(models.Model): + + _name = 'web.hook' + _description = 'Web Hook' + + name = fields.Char( + required=True, + ) + interface = fields.Reference( + selection='_get_interface_types', + readonly=True, + help='This is the interface that the web hook represents. It is ' + 'created automatically upon creation of the web hook, and ' + 'is also deleted with it.', + ) + interface_type = fields.Selection( + selection='_get_interface_types', + required=True, + ) + uri_path_json = fields.Char( + help='This is the URI path that is used to call the web hook with ' + 'a JSON request.', + compute='_compute_uri_path', + store=True, + readonly=True, + ) + uri_path_http = fields.Char( + help='This is the URI path that is used to call the web hook with ' + 'a form encoded request.', + compute='_compute_uri_path', + store=True, + readonly=True, + ) + uri_path_http_authenticated = fields.Char( + help='This is the URI path that is used to call the web hook with ' + 'an authenticated, form encoded request.', + compute='_compute_uri_path', + store=True, + readonly=True, + ) + uri_json = fields.Char( + string='JSON Endpoint', + help='This is the URI that is used to call the web hook externally. ' + 'This endpoint only accepts requests with a JSON mime-type.', + compute='_compute_uri', + ) + uri_http = fields.Char( + string='Form-Encoded Endpoint', + help='This is the URI that is used to call the web hook externally. ' + 'This endpoint should be used with requests that are form ' + 'encoded, not JSON.', + compute='_compute_uri', + ) + uri_http_authenticated = fields.Char( + string='Authenticated Endpoint', + help='This is the URI that is used to call the web hook externally. ' + 'This endpoint should be used with requests that are form ' + 'encoded, not JSON. This endpoint will require that a user is ' + 'authenticated, which is good for application hooks.', + compute='_compute_uri', + ) + token_id = fields.Many2one( + string='Token', + comodel_name='web.hook.token', + readonly=True, + groups='base.group_system', + ) + token_type = fields.Selection( + selection=lambda s: s._get_token_types(), + required=True, + ) + token_secret = fields.Char( + help='This is the secret that is configured for the token exchange. ' + 'This configuration is typically performed when setting the ' + 'token up in the remote system. For ease, a secure random value ' + 'has been provided as a default.', + default=lambda s: s._default_secret(), + groups='base.group_system', + ) + company_id = fields.Many2one( + string='Company', + comodel_name='res.company', + required=True, + default=lambda s: s.env.user.company_id.id, + ) + + @api.model + def _get_interface_types(self): + """Return the web hook interface models that are installed.""" + adapter = self.env['web.hook.adapter'] + return [ + (m, self.env[m]._description) for m in adapter._inherit_children + ] + + @api.multi + @api.depends('name') + def _compute_uri_path(self): + for record in self: + if isinstance(record.id, models.NewId): + # Do not compute slug until saved + continue + name = slugify(record.name or '').strip().strip('-') + slug = '%s-%d' % (name, record.id) + record.uri_path_json = '/base_web_hook/%s.json' % slug + record.uri_path_http = '/base_web_hook/%s' % slug + authenticated = '/base_web_hook/authenticated/%s' % slug + record.uri_path_http_authenticated = authenticated + + @api.multi + @api.depends('uri_path_http', 'uri_path_json') + def _compute_uri(self): + base_uri = self.env['ir.config_parameter'].get_param('web.base.url') + for record in self.filtered(lambda r: r.uri_path_json): + record.uri_json = '%s%s' % (base_uri, record.uri_path_json) + record.uri_http = '%s%s' % (base_uri, record.uri_path_http) + record.uri_http_authenticated = '%s%s' % ( + base_uri, record.uri_path_http_authenticated, + ) + + @api.model + def _get_token_types(self): + """Return the web hook token interface models that are installed.""" + adapter = self.env['web.hook.token.adapter'] + return [ + (m, self.env[m]._description) for m in adapter._inherit_children + ] + + @api.model + def _default_secret(self, length=254): + characters = string.printable.split()[0] + return ''.join( + random.choice(characters) for _ in range(length) + ) + + @api.model + def create(self, vals): + """Create the interface and token.""" + record = super(WebHook, self).create(vals) + if not self._context.get('web_hook_no_interface'): + record.interface = self.env[vals['interface_type']].create({ + 'hook_id': record.id, + }) + token = self.env['web.hook.token'].create({ + 'hook_id': record.id, + 'token_type': record.token_type, + 'secret': record.token_secret, + }) + record.token_id = token.id + return record + + @api.model + def search_by_slug(self, slug): + _, record_id = slug.strip().rsplit('-', 1) + return self.browse(int(record_id)) + + @api.multi + def receive(self, data=None, data_string=None, headers=None): + """This method is used to receive a web hook. + + First it extracts the token, then validates using ``token.validate`` + and raises as ``Unauthorized`` if it is invalid. It then passes the + received data to the underlying interface's ``receive`` method for + processing, and returns the result. The result returned by the + interface must be JSON serializable. + + Args: + data (dict, optional): Parsed data that was received in the + request. + data_string (str, optional): The raw data that was received in the + request body. + headers (dict, optional): Dictionary of headers that were + received with the request. + + Returns: + mixed: A JSON serializable return from the interface's + ``receive`` method. + """ + + self.ensure_one() + + # Convert optional args to proper types for interfaces + if data is None: + data = {} + if data_string is None: + data_string = '' + if headers is None: + headers = {} + + token = self.interface.sudo().extract_token(data, headers) + + if not self.token_id.validate(token, data, data_string, headers): + raise Unauthorized(_( + 'The request could not be processed: ' + 'An invalid token was received.' + )) + + return self.interface.receive(data, headers) diff --git a/base_web_hook/models/web_hook_adapter.py b/base_web_hook/models/web_hook_adapter.py new file mode 100644 index 0000000..0a6067f --- /dev/null +++ b/base_web_hook/models/web_hook_adapter.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, fields, models + + +class WebHookAdapter(models.AbstractModel): + """This is the model that should be inherited for new web hooks.""" + + _name = 'web.hook.adapter' + _description = 'Web Hook Adapter' + _inherits = {'web.hook': 'hook_id'} + + hook_id = fields.Many2one( + string='Hook', + comodel_name='web.hook', + required=True, + ondelete='cascade', + ) + + @api.model + def create(self, vals): + """If creating from the adapter level, circumvent adapter creation. + + An adapter is implicitly created and managed from within the + ``web.hook`` model. This is desirable in most instances, but it should + also be possible to create an adapter directly. + """ + context_self = self.with_context(web_hook_no_interface=True) + return super(WebHookAdapter, context_self).create(vals) + + @api.multi + def receive(self, data, headers): + """This should be overridden by inherited models to receive web hooks. + + The data has already been authenticated at this point in the workflow. + + It can expect a singleton, although can ``self.ensure_one()`` if + desired. + + Args: + data (dict): Data that was received with the hook. + headers (dict): Headers that were received with the request. + + Returns: + mixed: A JSON serializable return, or ``None``. + """ + raise NotImplementedError() + + @api.multi + def extract_token(self, data, headers): + """Extract the token from the data and return it. + + Args: + data (dict): Data that was received with the hook. + headers (dict): Headers that were received with the request. + + Returns: + mixed: The token data. Should be compatible with the hook's token + interface (the ``token`` parameter of ``token_id.validate``). + """ + raise NotImplementedError() diff --git a/base_web_hook/models/web_hook_request_bin.py b/base_web_hook/models/web_hook_request_bin.py new file mode 100644 index 0000000..6004e87 --- /dev/null +++ b/base_web_hook/models/web_hook_request_bin.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import pprint + +from odoo import api, fields, models +from odoo.http import request + + +class WebHookRequestBin(models.Model): + """This is an abstract Request Bin to be used for testing web hooks. + + It simply saves the request data for later evaluation. This is incredibly + useful when implementing web hook from undocumented sources. + """ + + _name = 'web.hook.request.bin' + _description = 'Web Hook - Request Bin' + _inherit = 'web.hook.adapter' + + request_ids = fields.One2many( + string='Requests', + comodel_name='web.hook.request.bin.request', + inverse_name='bin_id', + ) + + @api.multi + def receive(self, data, headers): + """Capture the request. + + Args: + data (dict): Data that was received with the hook. + headers (dict): Headers that were received with the request. + """ + self.env['web.hook.request.bin.request'].create({ + 'bin_id': self.id, + 'uri': request.httprequest.url, + 'method': request.httprequest.method, + 'headers': pprint.pformat(dict(headers), indent=4), + 'data_parsed': pprint.pformat(data, indent=4), + 'data_raw': request.httprequest.get_data(), + 'cookies': pprint.pformat(request.httprequest.cookies, + indent=4), + }) + + @api.multi + def extract_token(self, data, headers): + """No tokens are required in this implementation.""" + return True diff --git a/base_web_hook/models/web_hook_request_bin_request.py b/base_web_hook/models/web_hook_request_bin_request.py new file mode 100644 index 0000000..e1687cd --- /dev/null +++ b/base_web_hook/models/web_hook_request_bin_request.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class WebHookRequestBinRequest(models.Model): + """This is a single request, saved by the RequestBin""" + + _name = 'web.hook.request.bin.request' + _description = 'Web Hook Request Bin Request' + + bin_id = fields.Many2one( + string='Request Bin', + comodel_name='web.hook.request.bin', + required=True, + ondelete='cascade', + readonly=True, + ) + uri = fields.Char( + readonly=True, + ) + method = fields.Char( + readonly=True, + ) + headers = fields.Text( + readonly=True, + ) + data_parsed = fields.Text( + readonly=True, + ) + data_raw = fields.Text( + readonly=True, + ) + cookies = fields.Text( + readonly=True, + ) + user_id = fields.Many2one( + string='User', + comodel_name='res.users', + default=lambda s: s.env.user.id, + readonly=True, + ) diff --git a/base_web_hook/models/web_hook_token.py b/base_web_hook/models/web_hook_token.py new file mode 100644 index 0000000..6018dc6 --- /dev/null +++ b/base_web_hook/models/web_hook_token.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, fields, models + + +class WebHookToken(models.Model): + """This represents a generic token for use in a secure web hook exchange. + + It serves as an interface for token adapters. No logic should need to be + added to this model in inherited modules. + """ + + _name = 'web.hook.token' + _description = 'Web Hook Token' + + hook_id = fields.Many2one( + string='Hook', + comodel_name='web.hook', + required=True, + ondelete='cascade', + ) + token = fields.Reference( + selection=lambda s: s.env['web.hook']._get_token_types(), + readonly=True, + help='This is the token used for hook authentication. It is ' + 'created automatically upon creation of the web hook, and ' + 'is also deleted with it.', + ) + token_type = fields.Selection( + related='hook_id.token_type', + ) + secret = fields.Char( + related='hook_id.token_secret', + ) + + @api.model + def create(self, vals): + """Create the token.""" + record = super(WebHookToken, self).create(vals) + if not self._context.get('web_hook_no_token'): + record.token = self.env[vals['token_type']].create({ + 'token_id': record.id, + }) + return record + + @api.multi + def validate(self, token, data, data_string, headers): + """This method is used to validate a web hook. + + It simply passes the received data to the underlying token's + ``validate`` method for processing, and returns the result. + + Args: + token (mixed): The "secure" token string that should be validated + against the dataset. Typically a string. + data (dict): Parsed data that was received with the + request. + data_string (str): Raw form data that was received in + the request. This is useful for computation of hashes, because + Python dictionaries do not maintain sort order and thus are + useless for crypto. + headers (dict): Dictionary of headers that were + received with the request. + + Returns: + bool: If the token is valid or not. + """ + self.ensure_one() + return self.token.validate(token, data, data_string, headers) diff --git a/base_web_hook/models/web_hook_token_adapter.py b/base_web_hook/models/web_hook_token_adapter.py new file mode 100644 index 0000000..9a19074 --- /dev/null +++ b/base_web_hook/models/web_hook_token_adapter.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, fields, models + + +class WebHookTokenAdapter(models.AbstractModel): + """This should be inherited by all token interfaces.""" + + _name = 'web.hook.token.adapter' + _description = 'Web Hook Token Adapter' + _inherits = {'web.hook.token': 'token_id'} + + token_id = fields.Many2one( + string='Token', + comodel_name='web.hook.token', + required=True, + ondelete='cascade', + ) + + @api.multi + def validate(self, token_string, data, data_string, headers): + """Return ``True`` if the token is valid. Otherwise, ``False``. + + Child models should inherit this method to provide token validation + logic. + + Args: + token_string (str): The "secure" token string that should be + validated against the dataset. + data (dict): Parsed data that was received with the request. + data_string (str): Raw form data that was received in + the request. This is useful for computation of hashes, because + Python dictionaries do not maintain sort order and thus are + useless for crypto. + headers (dict): Dictionary of headers that were received with the + request. + + Returns: + bool: If the token is valid or not. + """ + raise NotImplementedError() diff --git a/base_web_hook/models/web_hook_token_none.py b/base_web_hook/models/web_hook_token_none.py new file mode 100644 index 0000000..ccfc188 --- /dev/null +++ b/base_web_hook/models/web_hook_token_none.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, models + + +class WebHookTokenNone(models.Model): + """This is a token that will validate under all circumstances.""" + + _name = 'web.hook.token.none' + _inherit = 'web.hook.token.adapter' + _description = 'Web Hook Token - None' + + @api.multi + def validate(self, token_string, *_, **__): + """Return ``True`` if the received token is the same as configured. + """ + return True diff --git a/base_web_hook/models/web_hook_token_plain.py b/base_web_hook/models/web_hook_token_plain.py new file mode 100644 index 0000000..28a384d --- /dev/null +++ b/base_web_hook/models/web_hook_token_plain.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, models + + +class WebHookTokenUser(models.Model): + """This is a token that requires a valid user session.""" + + _name = 'web.hook.token.user' + _inherit = 'web.hook.token.adapter' + _description = 'Web Hook Token - User Session' + + @api.multi + def validate(self, *_, **__): + """Return ``True`` if the received token is the same as configured. + """ + return bool(self.env.user) diff --git a/base_web_hook/models/web_hook_token_user.py b/base_web_hook/models/web_hook_token_user.py new file mode 100644 index 0000000..99a27cd --- /dev/null +++ b/base_web_hook/models/web_hook_token_user.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 LasLabs Inc. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, models + + +class WebHookTokenPlain(models.Model): + """This is a plain text token.""" + + _name = 'web.hook.token.plain' + _inherit = 'web.hook.token.adapter' + _description = 'Web Hook Token - Plain' + + @api.multi + def validate(self, token_string, *_, **__): + """Return ``True`` if the received token is the same as configured. + """ + return token_string == self.token_id.secret diff --git a/base_web_hook/security/ir.model.access.csv b/base_web_hook/security/ir.model.access.csv new file mode 100644 index 0000000..17ecabe --- /dev/null +++ b/base_web_hook/security/ir.model.access.csv @@ -0,0 +1,11 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_web_hook_system,access_web_hook_system,model_web_hook,base.group_system,1,1,1,1 +access_web_hook_request_bin_system,access_web_hook_request_bin_system,model_web_hook_request_bin,base.group_system,1,1,1,1 +access_web_hook_request_bin_request_system,access_web_hook_request_bin_request_system,model_web_hook_request_bin_request,base.group_system,1,1,1,1 +access_web_hook_token_system,access_web_hook_token_system,model_web_hook_token,base.group_system,1,1,1,1 +access_web_hook_token_none_system,access_web_hook_token_none_system,model_web_hook_token_none,base.group_system,1,1,1,1 +access_web_hook_token_plain_system,access_web_hook_token_plain_system,model_web_hook_token_plain,base.group_system,1,1,1,1 +access_web_hook_token_user_system,access_web_hook_token_user_system,model_web_hook_token_user,base.group_system,1,1,1,1 +access_web_hook_public,access_web_hook_public,model_web_hook,base.group_public,1,0,0,0 +access_web_hook_token_public,access_web_hook_token_public,model_web_hook_token,base.group_public,1,0,0,0 +access_web_hook_request_bin_request_public,access_web_hook_request_bin_request_public,model_web_hook_request_bin_request,base.group_public,0,0,1,0 diff --git a/base_web_hook/views/web_hook_view.xml b/base_web_hook/views/web_hook_view.xml new file mode 100644 index 0000000..2f1823f --- /dev/null +++ b/base_web_hook/views/web_hook_view.xml @@ -0,0 +1,58 @@ + + + + + + + web.hook.form + web.hook + +
+ +
+ + + + + + + + + + + + + + + + + + + + web.hook.tree + web.hook + + + + + + + + + + + + Web Hooks + web.hook + form + tree,form + + + + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..794a00a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-slugify