Search code examples
pythonjsonhttpodoowerkzeug

In the controllers.py file of odoo, how to get the json string in the post request body?


I have defined a class in the controllers.py file to receive HTTP requests. The remote server sends a POST request and the data in the request body is a JSON string.

I can get the data in the request body directly by converting the JSON string to a dictionary via method http.request.jsonrequest, but for now, I need to get the original JSON string in the request body directly instead of a dictionary to verify a signature.

The method(json.dumps()) of directly converting to JSON strings cannot be used, as the string obtained in this way is not the same as the JSON string in the original request body, which can lead to a failure when verifying the signature.

What should I do about it? Please help me. Thank you.

this is my controllers.py

# -*- coding: utf-8 -*-
from odoo import http
class CallbackNotification(http.Controller):

    def safety_judgement(self):
        """
        :return:
        """
        header = http.request.httprequest.headers.environ
        signature = header['HTTP_X_TSIGN_OPEN_SIGNATURE']
        time_stamp = header['HTTP_X_TSIGN_OPEN_TIMESTAMP']

        remote_addr = http.request.httprequest.remote_addr
        if remote_addr != '47.99.80.224':
            return

    @http.route('/signature/process/my_odoo', type='json', auth='none')
    def receive_institution_auth(self, **kw):
        """
        :param kw:
        :return:
        """
        self.safety_judgement()
        request_body = http.request.jsonrequest

        action = request_body['action']
        flow_num = request_body['flowId']
        http_env = http.request.env

        sign_process_id = http_env['sign.process'].sudo().search([('flow_num', '=', flow_num)]).id
        if action == 'SIGN_FLOW_UPDATE':
            third_order = request_body['thirdOrderNo']
            name_id_user_list = third_order.split(',')
            model = name_id_user_list[0]
            record_id = name_id_user_list[1]
            approve_user_id = name_id_user_list[2]

            if approve_user_id != 'p':
                record_obj = http_env[model].sudo(user=int(approve_user_id)).browse(int(record_id))

            sign_result = request_body['signResult']
            result_description = request_body['resultDescription']
            account_num = request_body['accountId']
            org_or_account_num = request_body['authorizedAccountId']

            sign_user_id = http_env['sign.users'].sudo().search([('account_num','=',account_num)]).id
            http_manual_env = http_env['manual.sign'].sudo()
            if account_num == org_or_account_num:
                manual_id = http_manual_env.search([('sign_process_id','=',sign_process_id),
                                                           ('sign_user_id','=',sign_user_id)]).id
            else:
                institution_id = http_env['institution.account'].sudo().search([('org_num','=',org_or_account_num)]).id
                manual_id = http_manual_env.search([('sign_process_id', '=', sign_process_id),
                                                    ('sign_user_id', '=', sign_user_id),
                                                    ('institution_id','=',institution_id)]).id


            if sign_result == 2:
                http_manual_env.browse(manual_id).write({'sign_result':'success'})
                http.request._cr.commit()
                if approve_user_id != 'p':
                    record_obj.approve_action('approved','')
                else:
                    http_env[model].sudo().browse(int(record_id)).write({'partner_sign_state':'success'})

            elif sign_result == 3:
                http_manual_env.browse(manual_id).write({'sign_result':'failed'})
                if approve_user_id == 'p':
                    http_env[model].sudo().browse(int(record_id)).write({'partner_sign_state':'failed'})

            elif sign_result == 4:
                http_manual_env.browse(manual_id).write({'sign_result':'reject'})
                http.request._cr.commit()
                if approve_user_id != 'p':
                    record_obj.approve_action('reject', result_description)
                else:
                    http_env[model].sudo().browse(int(record_id)).write({'partner_sign_state':'reject','partner_reject_reason':result_description})

Solution

  • @Dipen Shah @CoolFlash95 @Charif DZ Hello everyone,I've found a solution to this problem.But as I lay out the solution, I hope we can understand the root cause of the problem, so let's examine odoo's source code.

    from odoo.http import JsonRequest--odoo version 10.0--line598

    from odoo.http import JsonRequest--odoo version 11.0--line609

    In Odoo10 request = self.httprequest.stream.read(),thenself.jsonrequest = json.loads(request)

    In Odoo11 request=self.httprequest.get_data().decode(self.httprequest.charset),thenself.jsonrequest = json.loads(request)

    We find that the self object of JsonRequest has the attribute jsonrequest that the type is dict.Unfortunately, the source code does not allow self to have 'another' attribute, which contains the original string in the request body.However,it is very easy to add the 'another' attribute,why not?

    We can use setattr to dynamically change the methods in the source code.Let's change the method__init__of JsonRequest and add another attribute named stream_str.

    eg.Odoo version is 10,python version is 2.7

    # -*- coding: utf-8 -*-
    import logging
    from odoo.http import JsonRequest
    import werkzeug
    import json
    _logger = logging.getLogger(__name__)
    
    def __init__(self, *args):
        """
        We have copied the method __init__ directly from the source code and added 
        only one line of code to it
        """
        super(JsonRequest, self).__init__(*args)
    
        self.jsonp_handler = None
    
        args = self.httprequest.args
        jsonp = args.get('jsonp')
        self.jsonp = jsonp
        request = None
        request_id = args.get('id')
    
        if jsonp and self.httprequest.method == 'POST':
            # jsonp 2 steps step1 POST: save call
            def handler():
                self.session['jsonp_request_%s' % (request_id,)] = self.httprequest.form['r']
                self.session.modified = True
                headers = [('Content-Type', 'text/plain; charset=utf-8')]
                r = werkzeug.wrappers.Response(request_id, headers=headers)
                return r
    
            self.jsonp_handler = handler
            return
        elif jsonp and args.get('r'):
            # jsonp method GET
            request = args.get('r')
        elif jsonp and request_id:
            # jsonp 2 steps step2 GET: run and return result
            request = self.session.pop('jsonp_request_%s' % (request_id,), '{}')
        else:
            # regular jsonrpc2
            request = self.httprequest.stream.read()
            
            # We added this line of code,a new attribute named stream_str contains the origin string in request body when the request type is json. 
            self.stream_str = request
    
        # Read POST content or POST Form Data named "request"
        try:
            self.jsonrequest = json.loads(request)
        except ValueError:
            msg = 'Invalid JSON data: %r' % (request,)
            _logger.info('%s: %s', self.httprequest.path, msg)
            raise werkzeug.exceptions.BadRequest(msg)
    
        self.params = dict(self.jsonrequest.get("params", {}))
        self.context = self.params.pop('context', dict(self.session.context))
    
    # Replacing the __init__ method in the source code with the new __init__ method, but without changing the source code
    setattr(JsonRequest, '__init__', __init__)
    

    In the definition of the routing function, we can do this.

    # -*- coding: utf-8 -*-
    from odoo.http import Controller,route,request
    class CallbackNotification(http.Controller):
        @route('/signature/process/my_odoo', type='json', auth='none')
            def receive_institution_auth(self, **kw):
                # When the type='json',the request is the object of JsonRequest,we can get the new attribute stream_str very easy!
                stream_str = request.stream_str
    

    Now the problem has been solved.