def _check_special_access(res_model, res_id, token='', _hash='', pid=False): record = request.env[res_model].browse(res_id).sudo() if token: # Token Case: token is the global one of the document token_field = request.env[res_model]._mail_post_token_field return (token and record and consteq(record[token_field], token)) elif _hash and pid: # Signed Token Case: hash implies token is signed by partner pid return consteq(_hash, record._sign_token(pid)) else: raise Forbidden()
def confirm(self, **kw): tx_id = int(kw.get('tx_id', 0)) access_token = kw.get('access_token') if tx_id: if access_token: tx = request.env['payment.transaction'].sudo().browse(tx_id) secret = request.env['ir.config_parameter'].sudo().get_param( 'database.secret') valid_token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount) valid_token = hmac.new(secret.encode('utf-8'), valid_token_str.encode('utf-8'), hashlib.sha256).hexdigest() if not consteq(ustr(valid_token), access_token): raise werkzeug.exceptions.NotFound else: tx = request.env['payment.transaction'].browse(tx_id) if tx.state in ['done', 'authorized']: status = 'success' message = tx.acquirer_id.done_msg elif tx.state == 'pending': status = 'warning' message = tx.acquirer_id.pending_msg PaymentProcessing.remove_payment_transaction(tx) return request.render('payment.confirm', { 'tx': tx, 'status': status, 'message': message }) else: return request.redirect('/my/home')
def _check_token(cls, token): base_link = request.httprequest.path params = dict(request.params) params.pop('token', '') valid_token = request.env['mail.thread']._notify_encode_link( base_link, params) return consteq(valid_token, str(token))
def _get_record_and_check(self, xmlid=None, model=None, id=None, field='datas', access_token=None): # get object and content record = None if xmlid: record = self._xmlid_to_obj(self.env, xmlid) elif id and model in self.env: record = self.env[model].browse(int(id)) # obj exists if not record or not record.exists() or field not in record: return None, 404 if model == 'ir.attachment': record_sudo = record.sudo() if access_token and not consteq(record_sudo.access_token or '', access_token): return None, 403 elif (access_token and consteq(record_sudo.access_token or '', access_token)): record = record_sudo elif record_sudo.public: record = record_sudo elif self.env.user.has_group('base.group_portal'): # Check the read access on the record linked to the attachment # eg: Allow to download an attachment on a task from /my/task/task_id record.check('read') record = record_sudo # We have prefetched some fields of record, among which the field # 'write_date' used by '__last_update' below. In order to check # access on record, we have to invalidate its cache first. record._cache.clear() # check read access try: record['__last_update'] except AccessError: return None, 403 return record, 200
def check_token(self, access_token, partner_id, amount, currency_id): secret = self.env['ir.config_parameter'].sudo().get_param( 'database.secret') token_str = '%s%s%s' % (partner_id, amount, currency_id) correct_token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest() if consteq(ustr(access_token), correct_token): return True return False
def _stock_picking_check_access(self, picking_id, access_token=None): picking = request.env['stock.picking'].browse([picking_id]) picking_sudo = picking.sudo() try: picking.check_access_rights('read') picking.check_access_rule('read') except exceptions.AccessError: if not access_token or not consteq( picking_sudo.sale_id.access_token, access_token): raise return picking_sudo
def _message_post_helper(res_model='', res_id=None, message='', token='', nosubscribe=True, **kw): """ Generic chatter function, allowing to write on *any* object that inherits mail.thread. If a token is specified, all logged in users will be able to write a message regardless of access rights; if the user is the public user, the message will be posted under the name of the partner_id of the object (or the public user if there is no partner_id on the object). :param string res_model: model name of the object :param int res_id: id of the object :param string message: content of the message optional keywords arguments: :param string token: access token if the object's model uses some kind of public access using tokens (usually a uuid4) to bypass access rules :param bool nosubscribe: set False if you want the partner to be set as follower of the object when posting (default to True) The rest of the kwargs are passed on to message_post() """ record = request.env[res_model].browse(res_id) author_id = request.env.user.partner_id.id if request.env.user.partner_id else False if token: access_as_sudo = _has_token_access(res_model, res_id, token=token) if access_as_sudo: record = record.sudo() if request.env.user._is_public(): if kw.get('pid') and consteq( kw.get('hash'), record._sign_token(int( kw.get('pid')))): author_id = kw.get('pid') else: # TODO : After adding the pid and sign_token in access_url when send invoice by email, remove this line # TODO : Author must be Public User (to rename to 'Anonymous') author_id = record.partner_id.id if hasattr( record, 'partner_id') and record.partner_id.id else author_id else: if not author_id: raise NotFound() else: raise Forbidden() kw.pop('csrf_token', None) kw.pop('attachment_ids', None) return record.with_context( mail_create_nosubscribe=nosubscribe).message_post( body=message, message_type=kw.pop('message_type', "comment"), subtype=kw.pop('subtype', "mt_comment"), author_id=author_id, **kw)
def _document_check_access(self, model_name, document_id, access_token=None): document = request.env[model_name].browse([document_id]) document_sudo = document.with_user(SUPERUSER_ID).exists() if not document_sudo: raise MissingError(_("This document does not exist.")) try: document.check_access_rights('read') document.check_access_rule('read') except AccessError: if not access_token or not document_sudo.access_token or not consteq( document_sudo.access_token, access_token): raise return document_sudo
def _redirect_to_record(cls, model, res_id, access_token=None, **kwargs): """ If the current user doesn't have access to the document, but provided a valid access token, redirect him to the front-end view. If the partner_id and hash parameters are given, add those parameters to the redirect url to authentify the recipient in the chatter, if any. :param model: the model name of the record that will be visualized :param res_id: the id of the record :param access_token: token that gives access to the record bypassing the rights and rules restriction of the user. :param kwargs: Typically, it can receive a partner_id and a hash (sign_token). If so, those two parameters are used to authentify the recipient in the chatter, if any. :return: """ if issubclass(type(request.env[model]), request.env.registry['portal.mixin']): uid = request.session.uid or request.env.ref('base.public_user').id record_sudo = request.env[model].sudo().browse(res_id).exists() try: record_sudo.with_user(uid).check_access_rights('read') record_sudo.with_user(uid).check_access_rule('read') except AccessError: if record_sudo.access_token and access_token and consteq( record_sudo.access_token, access_token): record_action = record_sudo.with_context( force_website=True).get_access_action() if record_action['type'] == 'ir.actions.act_url': pid = kwargs.get('pid') hash = kwargs.get('hash') url = record_action['url'] if pid and hash: url = urls.url_parse(url) url_params = url.decode_query() url_params.update([("pid", pid), ("hash", hash)]) url = url.replace( query=urls.url_encode(url_params)).to_url() return werkzeug.utils.redirect(url) return super(MailController, cls)._redirect_to_record(model, res_id, access_token=access_token)
def _contract_check_access(self, contract_id, access_token=None): contract = request.env['saas.contract'].browse([contract_id]) contract_sudo = contract.sudo() partner = request.env.user.partner_id try: if contract.exists(): if partner.id != contract.partner_id.id: raise AccessError("Not Allowed") else: contract.check_access_rights('read') contract.check_access_rule('read') else: _logger.info("------------------ No Record Found--------") raise AccessError("Not allowed") except AccessError: _logger.info("-------------5-----------") if contract.exists(): if not access_token or not consteq(contract_sudo.access_token, access_token): raise else: raise return contract_sudo
def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas', unique=False, filename=None, filename_field='datas_fname', download=False, mimetype=None, default_mimetype='application/octet-stream', access_token=None, related_id=None, access_mode=None, env=None): """ Get file, attachment or downloadable content If the ``xmlid`` and ``id`` parameter is omitted, fetches the default value for the binary field (via ``default_get``), otherwise fetches the field for that precise record. :param str xmlid: xmlid of the record :param str model: name of the model to fetch the binary from :param int id: id of the record from which to fetch the binary :param str field: binary field :param bool unique: add a max-age for the cache control :param str filename: choose a filename :param str filename_field: if not create an filename with model-id-field :param bool download: apply headers to download the file :param str mimetype: mintype of the field (for headers) :param related_id: the id of another record used for custom_check :param access_mode: if truthy, will call custom_check to fetch the object that contains the binary. :param str default_mimetype: default mintype if no mintype found :param str access_token: optional token for unauthenticated access only available for ir.attachment :param Environment env: by default use request.env :returns: (status, headers, content) """ env = env or request.env # get object and content obj = None if xmlid: obj = cls._xmlid_to_obj(env, xmlid) elif id and model in env.registry: obj = env[model].browse(int(id)) # obj exists if not obj or not obj.exists() or field not in obj: return (404, [], None) # access token grant access if model == 'ir.attachment' and access_token: obj = obj.sudo() if access_mode: if not cls._check_access_mode(env, id, access_mode, model, access_token=access_token, related_id=related_id): return (403, [], None) elif not consteq(obj.access_token or u'', access_token): return (403, [], None) # check read access try: last_update = obj['__last_update'] except AccessError: return (403, [], None) status, headers, content = None, [], None # attachment by url check module_resource_path = None if model == 'ir.attachment' and obj.type == 'url' and obj.url: url_match = re.match("^/(\w+)/(.+)$", obj.url) if url_match: module = url_match.group(1) module_path = get_module_path(module) module_resource_path = get_resource_path( module, url_match.group(2)) if module_path and module_resource_path: module_path = os.path.join( os.path.normpath(module_path), '') # join ensures the path ends with '/' module_resource_path = os.path.normpath( module_resource_path) if module_resource_path.startswith(module_path): with open(module_resource_path, 'rb') as f: content = base64.b64encode(f.read()) last_update = pycompat.text_type( os.path.getmtime(module_resource_path)) if not module_resource_path: module_resource_path = obj.url if not content: status = 301 content = module_resource_path else: content = obj[field] or '' # filename if not filename: if filename_field in obj: filename = obj[filename_field] if not filename and module_resource_path: filename = os.path.basename(module_resource_path) if not filename: filename = "%s-%s-%s" % (obj._name, obj.id, field) # mimetype mimetype = 'mimetype' in obj and obj.mimetype or False if not mimetype: if filename: mimetype = mimetypes.guess_type(filename)[0] if not mimetype and getattr(env[model]._fields[field], 'attachment', False): # for binary fields, fetch the ir_attachement for mimetype check attach_mimetype = env['ir.attachment'].search_read( domain=[('res_model', '=', model), ('res_id', '=', id), ('res_field', '=', field)], fields=['mimetype'], limit=1) mimetype = attach_mimetype and attach_mimetype[0]['mimetype'] if not mimetype: mimetype = guess_mimetype(base64.b64decode(content), default=default_mimetype) # extension _, existing_extension = os.path.splitext(filename) if not existing_extension: extension = mimetypes.guess_extension(mimetype) if extension: filename = "%s%s" % (filename, extension) headers += [('Content-Type', mimetype), ('X-Content-Type-Options', 'nosniff')] # cache etag = bool(request) and request.httprequest.headers.get( 'If-None-Match') retag = '"%s"' % hashlib.md5( pycompat.to_text(content).encode('utf-8')).hexdigest() status = status or (304 if etag == retag else 200) headers.append(('ETag', retag)) headers.append( ('Cache-Control', 'max-age=%s' % (STATIC_CACHE if unique else 0))) # content-disposition default name if download: headers.append( ('Content-Disposition', cls.content_disposition(filename))) return (status, headers, content)
def _valid_unsubscribe_token(self, mailing_id, res_id, email, token): if not (mailing_id and res_id and email and token): return False mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id) return consteq(mailing._unsubscribe_token(res_id, email), token)
def _has_token_access(res_model, res_id, token=''): record = request.env[res_model].browse(res_id).sudo() token_field = request.env[res_model]._mail_post_token_field return (token and record and consteq(record[token_field], token))