def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): """Override read_group to add res_field=False in domain if not present.""" if not fields: raise AccessError( _("Sorry, you must provide fields to read on attachments")) if any('(' in field for field in fields + groupby): raise AccessError( _("Sorry, the syntax 'name:agg(field)' is not available for attachments" )) if not any(item[0] in ('id', 'res_field') for item in domain): domain.insert(0, ('res_field', '=', False)) groupby = [groupby] if isinstance(groupby, str) else groupby allowed_fields = self._read_group_allowed_fields() fields_set = set(field.split(':')[0] for field in fields + groupby) if not self.env.is_system() and ( not fields or fields_set.difference(allowed_fields)): raise AccessError( _("Sorry, you are not allowed to access these fields on attachments." )) return super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
def _make_access_error(self, operation, records): _logger.info( 'Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s', operation, records.ids[:6], self._uid, records._name) model = records._name description = self.env['ir.model']._get(model).name or model if not self.env.user.has_group('base.group_no_one'): return AccessError( _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: "%(document_kind)s" (%(document_model)s), Operation: %(operation)s)' ) % { 'document_kind': description, 'document_model': model, 'operation': operation, }) # This extended AccessError is only displayed in debug mode. # Note that by default, public and portal users do not have # the group "base.group_no_one", even if debug mode is enabled, # so it is relatively safe here to include the list of rules and # record names. rules = self._get_failing(records, mode=operation).sudo() error = AccessError( _("""The requested operation ("%(operation)s" on "%(document_kind)s" (%(document_model)s)) was rejected because of the following rules: %(rules_list)s %(multi_company_warning)s (Records: %(example_records)s, User: %(user_id)s)""") % { 'operation': operation, 'document_kind': description, 'document_model': model, 'rules_list': '\n'.join('- %s' % rule.name for rule in rules), 'multi_company_warning': ('\n' + _('Note: this might be a multi-company issue.') + '\n') if any('company_id' in (r.domain_force or []) for r in rules) else '', 'example_records': ' - '.join([ '%s (id=%s)' % (rec.display_name, rec.id) for rec in records[:6].sudo() ]), 'user_id': '%s (id=%s)' % (self.env.user.name, self.env.user.id), }) # clean up the cache of records prefetched with display_name above for record in records[:6]: record._cache.clear() return error
def company(self): """Return the current company (as an instance). If not specified in the context (`allowed_company_ids`), fallback on current user main company. :raise AccessError: invalid or unauthorized `allowed_company_ids` context key content. :return: current company (default=`self.user.company_id`) :rtype: res.company .. warning:: No sanity checks applied in sudo mode ! When in sudo mode, a user can access any company, even if not in his allowed companies. This allows to trigger inter-company modifications, even if the current user doesn't have access to the targeted company. """ company_ids = self.context.get('allowed_company_ids', []) if company_ids: if not self.su: user_company_ids = self.user.company_ids.ids if any(cid not in user_company_ids for cid in company_ids): raise AccessError(_("Access to unauthorized or invalid companies.")) return self['res.company'].browse(company_ids[0]) return self.user.company_id
def _read(self, fields): # DLE P45: `test_31_prefetch`, # with self.assertRaises(AccessError): # cat1.name if self.search_count([('id', 'in', self._ids), ('name', '=', 'NOACCESS')]): raise AccessError('Sorry') return super(Category, self)._read(fields)
def cancel(self): """ cancel one or more order.line, update order status and unlink existing cashmoves """ if self.user_has_groups("lunch.group_lunch_manager"): self.state = 'cancelled' self.cashmove.unlink() else: raise AccessError(_("Only your lunch manager cancels the orders."))
def _compute_kpi_sale_total_value(self): if not self.env.user.has_group('sales_team.group_sale_salesman_all_leads'): raise AccessError(_("Do not have access, skip this data for user's digest email")) for record in self: start, end, company = record._get_kpi_compute_parameters() all_channels_sales = self.env['sale.report'].read_group([ ('confirmation_date', '>=', start), ('confirmation_date', '<', end), ('company_id', '=', company.id)], ['price_total'], ['price_total']) record.kpi_all_sale_total_value = sum([channel_sale['price_total'] for channel_sale in all_channels_sales])
def _compute_kpi_hr_recruitment_new_colleagues_value(self): if not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'): raise AccessError(_("Do not have access, skip this data for user's digest email")) for record in self: start, end, company = record._get_kpi_compute_parameters() new_colleagues = self.env['hr.employee'].search_count([ ('create_date', '>=', start), ('create_date', '<', end), ('company_id', '=', company.id) ]) record.kpi_hr_recruitment_new_colleagues_value = new_colleagues
def read(self, fields, load='_classic_read'): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self).read(fields, load=load) private_fields = set(fields).difference( self.env['hr.employee.public']._fields.keys()) if private_fields: raise AccessError( _('The fields "%s" you try to read is not available on the public employee profile.' ) % (','.join(private_fields))) return self.env['hr.employee.public'].browse(self.ids).read(fields, load=load)
def _compute_kpi_crm_lead_created_value(self): if not self.env.user.has_group('sales_team.group_sale_salesman'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() record.kpi_crm_lead_created_value = self.env[ 'crm.lead'].search_count([('create_date', '>=', start), ('create_date', '<', end), ('company_id', '=', company.id)])
def create(self, vals_list): records = super(WebsitePublishedMixin, self).create(vals_list) is_publish_modified = any([ set(v.keys()) & {'is_published', 'website_published'} for v in vals_list ]) if is_publish_modified and not all(record.can_publish for record in records): raise AccessError(self._get_can_publish_error_message()) return records
def _compute_project_task_opened_value(self): if not self.env.user.has_group('project.group_project_user'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() record.kpi_project_task_opened_value = self.env[ 'project.task'].search_count([('stage_id.fold', '=', False), ('create_date', '>=', start), ('create_date', '<', end), ('company_id', '=', company.id)])
def _compute_kpi_pos_total_value(self): if not self.env.user.has_group('point_of_sale.group_pos_user'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() record.kpi_pos_total_value = sum(self.env['pos.order'].search([ ('date_order', '>=', start), ('date_order', '<', end), ('state', 'not in', ['draft', 'cancel', 'invoiced']), ('company_id', '=', company.id) ]).mapped('amount_total'))
def _compute_kpi_website_sale_total_value(self): if not self.env.user.has_group('sales_team.group_sale_salesman_all_leads'): raise AccessError(_("Do not have access, skip this data for user's digest email")) for record in self: start, end, company = record._get_kpi_compute_parameters() confirmed_website_sales = self.env['sale.order'].search([ ('date_order', '>=', start), ('date_order', '<', end), ('state', 'not in', ['draft', 'cancel', 'sent']), ('website_id', '!=', False), ('company_id', '=', company.id) ]) record.kpi_website_sale_total_value = sum(confirmed_website_sales.mapped('amount_total'))
def _compute_kpi_crm_opportunities_won_value(self): if not self.env.user.has_group('sales_team.group_sale_salesman'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() record.kpi_crm_opportunities_won_value = self.env[ 'crm.lead'].search_count([('type', '=', 'opportunity'), ('probability', '=', '100'), ('date_closed', '>=', start), ('date_closed', '<', end), ('company_id', '=', company.id)])
def check_access_rule(self, operation): """ Add Access rules of mail.message for non-employee user: - read: - raise if the type is comment and subtype NULL (internal note) """ if self.user_has_groups('base.group_public'): self.env.cr.execute('SELECT id FROM "%s" WHERE website_published IS FALSE AND id = ANY (%%s)' % (self._table), (self.ids,)) if self.env.cr.fetchall(): raise AccessError( _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) ) return super(MailMessage, self).check_access_rule(operation=operation)
def _compute_kpi_account_total_revenue_value(self): if not self.env.user.has_group('account.group_account_invoice'): raise AccessError( _("Do not have access, skip this data for user's digest email") ) for record in self: start, end, company = record._get_kpi_compute_parameters() account_moves = self.env['account.move'].read_group( [('journal_id.type', '=', 'sale'), ('company_id', '=', company.id), ('date', '>=', start), ('date', '<', end)], ['journal_id', 'amount'], ['journal_id']) record.kpi_account_total_revenue_value = sum( [account_move['amount'] for account_move in account_moves])
def check(self, mode, values=None): """Restricts the access to an ir.attachment, according to referred model In the 'document' module, it is overriden to relax this hard rule, since more complex ones apply there. """ # collect the records to check (by model) model_ids = defaultdict(set) # {model_name: set(ids)} require_employee = False if self: self._cr.execute( 'SELECT res_model, res_id, create_uid, public FROM ir_attachment WHERE id IN %s', [tuple(self.ids)]) for res_model, res_id, create_uid, public in self._cr.fetchall(): if public and mode == 'read': continue if not (res_model and res_id): if create_uid != self._uid: require_employee = True continue model_ids[res_model].add(res_id) if values and values.get('res_model') and values.get('res_id'): model_ids[values['res_model']].add(values['res_id']) # check access rights on the records for res_model, res_ids in model_ids.items(): # ignore attachments that are not attached to a resource anymore # when checking access rights (resource was deleted but attachment # was not) if res_model not in self.env: require_employee = True continue elif res_model == 'res.users' and len( res_ids) == 1 and self._uid == list(res_ids)[0]: # by default a user cannot write on itself, despite the list of writeable fields # e.g. in the case of a user inserting an image into his image signature # we need to bypass this check which would needlessly throw us away continue records = self.env[res_model].browse(res_ids).exists() if len(records) < len(res_ids): require_employee = True # For related models, check if we can write to the model, as unlinking # and creating attachments can be seen as an update to the model records.check_access_rights('write' if mode in ( 'create', 'unlink') else mode) records.check_access_rule(mode) if require_employee: if not (self.env.user._is_admin() or self.env.user.has_group('base.group_user')): raise AccessError( _("Sorry, you are not allowed to access this document."))
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 force_storage(self): """Force all attachments to be stored in the currently configured storage""" if not self.env.is_admin(): raise AccessError( _('Only administrators can execute this action.')) # domain to retrieve the attachments to migrate domain = { 'db': [('store_fname', '!=', False)], 'file': [('db_datas', '!=', False)], }[self._storage()] for attach in self.search(domain): attach.write({'datas': attach.datas}) return True
def _compute_kpi_account_total_revenue_value(self): if not self.env.user.has_group('account.group_account_invoice'): raise AccessError(_("Do not have access, skip this data for user's digest email")) for record in self: start, end, company = record._get_kpi_compute_parameters() self._cr.execute(''' SELECT SUM(line.debit) FROM account_move_line line JOIN account_move move ON move.id = line.move_id JOIN account_journal journal ON journal.id = move.journal_id WHERE line.company_id = %s AND line.date >= %s AND line.date < %s AND journal.type = 'sale' ''', [company.id, start, end]) query_res = self._cr.fetchone() record.kpi_account_total_revenue_value = query_res and query_res[0] or 0.0
def _parse_import_data_recursive(self, model, prefix, data, import_fields, options): # Get fields of type date/datetime all_fields = self.env[model].fields_get() for name, field in all_fields.items(): name = prefix + name if field['type'] in ('date', 'datetime') and name in import_fields: index = import_fields.index(name) self._parse_date_from_data(data, index, name, field['type'], options) # Check if the field is in import_field and is a relational (followed by /) # Also verify that the field name exactly match the import_field at the correct level. elif any(name + '/' in import_field and name == import_field.split('/')[prefix.count('/')] for import_field in import_fields): # Recursive call with the relational as new model and add the field name to the prefix self._parse_import_data_recursive(field['relation'], name + '/', data, import_fields, options) elif field['type'] in ('float', 'monetary') and name in import_fields: # Parse float, sometimes float values from file have currency symbol or () to denote a negative value # We should be able to manage both case index = import_fields.index(name) self._parse_float_from_data(data, index, name, options) # DON'T Forward port in >= saas-12.2 elif field['type'] == 'binary' and ( field.get('attachment') or field.get('manual')) and any( f in name for f in IMAGE_FIELDS) and name in import_fields: index = import_fields.index(name) with requests.Session() as session: session.stream = True for num, line in enumerate(data): if re.match( config.get("import_image_regex", DEFAULT_IMAGE_REGEX), line[index]): if not self.env.user._can_import_remote_urls(): raise AccessError( _("You can not import images via URL, check with your administrator or support for the reason." )) line[index] = self._import_image_by_url( line[index], session, name, num) return data
def write(self, vals): """ Synchronize user and its related employee and check access rights if employees are not allowed to update their own data (otherwise sudo is applied for self data). """ hr_fields = { field for field_name, field in self._fields.items() if field.related_field and field.related_field.model_name == 'hr.employee' and field_name in vals } can_edit_self = self.env['ir.config_parameter'].sudo().get_param( 'hr.hr_employee_self_edit') or self.env.user.has_group( 'hr.group_hr_user') if hr_fields and not can_edit_self: # Raise meaningful error message raise AccessError( _("You are only allowed to update your preferences. Please contact a HR officer to update other informations." )) result = super(User, self).write(vals) employee_values = {} for fname in [ f for f in ['name', 'email', 'image_1920', 'tz'] if f in vals ]: employee_values[fname] = vals[fname] if employee_values: if 'email' in employee_values: employee_values['work_email'] = employee_values.pop('email') if 'image_1920' in vals: without_image = self.env['hr.employee'].sudo().search([ ('user_id', 'in', self.ids), ('image_1920', '=', False) ]) with_image = self.env['hr.employee'].sudo().search([ ('user_id', 'in', self.ids), ('image_1920', '!=', False) ]) without_image.write(employee_values) if not can_edit_self: employee_values.pop('image_1920') with_image.write(employee_values) else: self.env['hr.employee'].sudo().search([ ('user_id', 'in', self.ids) ]).write(employee_values) return result
def message_post(self, *, parent_id=False, subtype=None, **kwargs): """ Temporary workaround to avoid spam. If someone replies on a channel through the 'Presentation Published' email, it should be considered as a note as we don't want all channel followers to be notified of this answer. """ self.ensure_one() if kwargs.get('message_type') == 'comment' and not self.can_review: raise AccessError(_('Not enough karma to review')) if parent_id: parent_message = self.env['mail.message'].sudo().browse(parent_id) if parent_message.subtype_id and parent_message.subtype_id == self.env.ref( 'website_slides.mt_channel_slide_published'): if kwargs.get('subtype_id'): kwargs['subtype_id'] = False subtype = 'mail.mt_note' return super(Channel, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs)
def execute(self): self.ensure_one() if not self.env.is_admin(): raise AccessError(_("Only administrators can change the settings")) self = self.with_context(active_test=False) classified = self._get_classified_fields() self.set_values() # module fields: install/uninstall the selected modules to_install = [] to_uninstall_modules = self.env['ir.module.module'] lm = len('module_') for name, module in classified['module']: if int(self[name]): to_install.append((name[lm:], module)) else: if module and module.state in ('installed', 'to upgrade'): to_uninstall_modules += module if to_install or to_uninstall_modules: self.flush() if to_uninstall_modules: to_uninstall_modules.button_immediate_uninstall() self._install_modules(to_install) if to_install or to_uninstall_modules: # After the uninstall/install calls, the registry and environments # are no longer valid. So we reset the environment. self.env.reset() self = self.env()[self._name] # pylint: disable=next-method-called config = self.env['res.config'].next() or {} if config.get('type') not in ('ir.actions.act_window_close',): return config # force client-side reload (update user menu and current view) return { 'type': 'ir.actions.client', 'tag': 'reload', }
def confirm(self): """ confirm one or more order line, update order status and create new cashmove """ if self.user_has_groups("lunch.group_lunch_manager"): if self.state != 'confirmed': values = { 'user_id': self.user_id.id, 'amount': -self.price, 'description': self.product_id.name, 'order_id': self.id, 'state': 'order', 'date': self.date, } self.env['lunch.cashmove'].create(values) self.state = 'confirmed' else: raise AccessError(_("Only your lunch manager sets the orders as received."))
def base_setup_data(self, **kw): if not request.env.user.has_group('base.group_erp_manager'): raise AccessError(_("Access Denied")) cr = request.cr cr.execute(""" SELECT count(*) FROM res_users WHERE active=true AND share=false """) active_count = cr.dictfetchall()[0].get('count') cr.execute(""" SELECT count(u.*) FROM res_users u WHERE active=true AND share=false AND NOT exists(SELECT 1 FROM res_users_log WHERE create_uid=u.id) """) pending_count = cr.dictfetchall()[0].get('count') cr.execute(""" SELECT id, login FROM res_users u WHERE active=true AND share=false AND NOT exists(SELECT 1 FROM res_users_log WHERE create_uid=u.id) ORDER BY id desc LIMIT 10 """) pending_users = cr.fetchall() return { 'active_users': active_count, 'pending_count': pending_count, 'pending_users': pending_users, }
def companies(self): """Return a recordset of the enabled companies by the user. If not specified in the context(`allowed_company_ids`), fallback on current user companies. :raise AccessError: invalid or unauthorized `allowed_company_ids` context key content. :return: current companies (default=`self.user.company_ids`) :rtype: res.company .. warning:: No sanity checks applied in sudo mode ! When in sudo mode, a user can access any company, even if not in his allowed companies. This allows to trigger inter-company modifications, even if the current user doesn't have access to the targeted company. """ company_ids = self.context.get('allowed_company_ids', []) if company_ids: if not self.su: user_company_ids = self.user.company_ids.ids if any(cid not in user_company_ids for cid in company_ids): raise AccessError(_("Access to unauthorized or invalid companies.")) return self['res.company'].browse(company_ids) # By setting the default companies to all user companies instead of the main one # we save a lot of potential trouble in all "out of context" calls, such as # /mail/redirect or /web/image, etc. And it is not unsafe because the user does # have access to these other companies. The risk of exposing foreign records # (wrt to the context) is low because all normal RPCs will have a proper # allowed_company_ids. # Examples: # - when printing a report for several records from several companies # - when accessing to a record from the notification email template # - when loading an binary image on a template return self.user.company_ids
def write(self, values): if 'is_published' in values and not all(record.can_publish for record in self): raise AccessError(self._get_can_publish_error_message()) return super(WebsitePublishedMixin, self).write(values)
def check_user(self, uid=None): if uid is None: uid = request.uid is_admin = request.env['res.users'].browse(uid)._is_admin() if not is_admin: raise AccessError(_("Only administrators can upload a module"))
def _redirect_to_record(cls, model, res_id, access_token=None, **kwargs): # access_token and kwargs are used in the portal controller override for the Send by email or Share Link # to give access to the record to a recipient that has normally no access. uid = request.session.uid user = request.env['res.users'].sudo().browse(uid) cids = False # no model / res_id, meaning no possible record -> redirect to login if not model or not res_id or model not in request.env: return cls._redirect_to_messaging() # find the access action using sudo to have the details about the access link RecordModel = request.env[model] record_sudo = RecordModel.sudo().browse(res_id).exists() if not record_sudo: # record does not seem to exist -> redirect to login return cls._redirect_to_messaging() # the record has a window redirection: check access rights if uid is not None: if not RecordModel.with_user(uid).check_access_rights( 'read', raise_exception=False): return cls._redirect_to_messaging() try: # We need here to extend the "allowed_company_ids" to allow a redirection # to any record that the user can access, regardless of currently visible # records based on the "currently allowed companies". cids = request.httprequest.cookies.get('cids', str(user.company_id.id)) cids = [int(cid) for cid in cids.split(',')] try: record_sudo.with_user(uid).with_context( allowed_company_ids=cids).check_access_rule('read') except AccessError: # In case the allowed_company_ids from the cookies (i.e. the last user configuration # on his browser) is not sufficient to avoid an ir.rule access error, try to following # heuristic: # - Guess the supposed necessary company to access the record via the method # _get_mail_redirect_suggested_company # - If no company, then redirect to the messaging # - Merge the suggested company with the companies on the cookie # - Make a new access test if it succeeds, redirect to the record. Otherwise, # redirect to the messaging. suggested_company = record_sudo._get_mail_redirect_suggested_company( ) if not suggested_company: raise AccessError() cids = cids + [suggested_company.id] record_sudo.with_user(uid).with_context( allowed_company_ids=cids).check_access_rule('read') except AccessError: return cls._redirect_to_messaging() else: record_action = record_sudo.get_access_action(access_uid=uid) else: record_action = record_sudo.get_access_action() if record_action[ 'type'] == 'ir.actions.act_url' and record_action.get( 'target_type') != 'public': return cls._redirect_to_messaging() record_action.pop('target_type', None) # the record has an URL redirection: use it directly if record_action['type'] == 'ir.actions.act_url': return werkzeug.utils.redirect(record_action['url']) # other choice: act_window (no support of anything else currently) elif not record_action['type'] == 'ir.actions.act_window': return cls._redirect_to_messaging() url_params = { 'model': model, 'id': res_id, 'active_id': res_id, 'action': record_action.get('id'), } view_id = record_sudo.get_formview_id() if view_id: url_params['view_id'] = view_id if cids: url_params['cids'] = ','.join([str(cid) for cid in cids]) url = '/web?#%s' % url_encode(url_params) return werkzeug.utils.redirect(url)