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 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_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 _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 _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 _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) elif field['type'] == 'binary' and field.get('attachment') 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) else: try: base64.b64decode(line[index], validate=True) except binascii.Error: raise ValueError( _("Found invalid image data, images should be imported as either URLs or base64-encoded data." )) return data
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 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 _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( [('date', '>=', start), ('date', '<', end), ('state', 'not in', ['draft', 'cancel', 'sent']), ('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 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 _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 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 _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)
def test_access_error_http(self, **kwargs): raise AccessError("This is an access http test")
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 _check_user_impersonification(self, user_id=None): if (user_id and request.env.uid != user_id and not request.env.user.has_group('lunch.group_lunch_manager')): raise AccessError( _('You are trying to impersonate another user, but this can only be done by a lunch manager' ))
def test_access_error_json(self, **kwargs): raise AccessError("This is an access rpc test")